《计算机网络》笔记-第3章运输层
[TOC]
0. 前言
Chapter 3 Transport Layer
应用层处理应用之间的通信,而运输层则负责:把同台主机上各进程发送的数据收集起来交给网络层,并将从网络层收到的数据分发给各进程。
与此同时,运输层的TCP协议还向应用层提供了极其重要的可靠数据传输。
1. Introduction and Transport-Layer Services(概述和运输层服务)
运输层接收应用层传来的应用报文,划分为较小的块,转换成运输层分组,称为运输层报文段(segment)。
运输层 vs 网络层:
- 运输层提供不同主机上进程之间的通信
- 网络层提供主机之间的通信
将主机间交付扩展到进程间交付,被称为运输层的多路复用(transport-layer multiplexing)和多路分解(demultiplexing)。
因特网运输层的主要协议和提供的服务如下:
- UDP(用户数据报协议):
- 进程到进程的数据交付
- 差错检查
- TCP(传输控制协议):
- 进程到进程的数据交付
- 差错检查
- 可靠数据传输
- 拥塞控制
2. Multiplexing and Demultiplexing(多路复用和多路分解)
2.1. 套接字
在第2章,我们知道,套接字是应用层与运输层之间的接口。
发送数据时,应用层通过套接字将数据交付给运输层;运输层从网络层接收数据时,它需要将所接收的数据发给对应的套接字,从而到达应用层。
任一时刻,主机上可能有不止一个套接字,每个套接字都有唯一的标识符,其格式取决于它是UDP还是TCP。
将运输层报文段中的数据交付到正确的套接字的工作称为多路分解(demultiplexing);从不同套接字接收数据,并为每个数据块封装上首部信息从而生成报文段,然后将报文段传给网络层,这些工作称为多路复用(multiplexing)。
值得注意的是:一个进程通常有一个或多个套接字,例如:当今高性能Web服务器(HTTP服务器)只使用一个进程,但为每个新的客户连接创建一个具有套接字的新线程。
2.2. 无连接(UDP)的多路复用和多路分解
一个 UDP套接字 由 (目的IP地址,目的端口号)组成的二元组 标识。
因此:如果两个UDP报文段有不同的源IP地址或源端口号,但具有相同的目的IP地址和目的端口号,那么这两个报文段将通过相同的目的套接字,定向到相同的进程。
例如:主机A和主机B,都向主机C的99端口发送UDP报文段,两个报文段将到达主机C上的同一个套接字。
2.3. 面向连接(TCP)的多路复用和多路分解
一个 TCP套接字 由 (源IP地址,源端口号,目的IP地址,目的端口号)组成的四元组 标识。
因此:与UDP不同的是,两个具有不同源IP地址或源端口号的TCP报文段,即便目的IP地址和目的端口号相同,也将被定向到两个不同的套接字。
以使用TCP服务的HTTP为例:同台主机上不同的HTTP会话(源IP地址相同,源端口不相同),将对应服务器上不同的套接字;不同主机上的HTTP会话(源IP地址不相同,源端口可能不相同),更对应服务器上不同的套接字。如下:
3. Connectionless Transport: UDP(无连接运输:UDP)
UDP的工作:
- 多路复用/分解
- 少量的差错检测
UDP的特点:
- 不可靠数据传输,不保证数据到达目的地
- 将接收到的数据立即发送,不会因链路拥塞而等待
- 无须建立连接,不会引入连接时延
- 无连接状态,不需要额外存储状态数据
- 分组首部开销小
使用UDP的运输层协议:DNS等。
3.1. UDP报文段结构
UDP报文段由 首部字段(8字节) 和 数据 组成。
- 源端口号(Source port):源主机上发送UDP报文段的进程所在的端口。
- 目的端口号(Dest port):目的主机上UDP服务器进程所在的端口。
- 长度(Length):UDP报文段中的字节数。
- 校验和(Checksum):用来检查该报文段是否出现差错。
- 应用数据(Application data)
3.2. UDP校验和
发送方:
首先将UDP报文段的校验和字段置为0。
将UDP报文段中所有的16比特字相加,求和时遇到任何溢出都要回卷(将溢出加到结果的低位上)。示例:
对和的结果进行反码运算。
将最后结果放在UDP报文段的校验和字段中。
接收方:
将UDP报文段中所有的16比特字相加。
如果没有出现差错,则和的结果为:
1111 1111 1111 1111
。
虽然UDP提供差错检测,但它对差错恢复无能为力。
4. Principles of Reliable Data Transfer(可靠数据传输原理)
在介绍TCP之前,我们需要先了解可靠数据传输的原理——网络中最为重要的问题之一。
其服务模型和服务实现概况如下:
实现这种服务抽象是可靠数据传输协议(reliable data transfer protocol, rdt)。
注意:此处使用的术语为分组,而不是运输层的报文段。
4.1. 构造可靠数据传输协议
由浅入深完善一个可靠数据传输协议,此书的独到之处,好评!!!
4.1.1. 经完全可靠信道的可靠数据传输:rdt1.0
最简单情况下,底层信道是完全可靠的。
发送方和接收方只需要发送和接收数据即可。
发送方的状态转换图(有限状态机)如下:
接收方的状态转换图(有限状态机)如下:
图释:
- 圆表示一个状态,箭头表示状态变迁。
- 横线上为引起变迁的事件,横线下为事件发生时所采取的动作。
rdt_send(data)
:发送高层传来的数据。其动作包括:make_pkt(data)
将数据封装为分组,udt_send(packet)
发送分组(udt表示不可靠数据传输)。rdt_rcv(packet)
:接收底层接收一个分组。其动作包括:extract(packet, data)
从分组中取出数据,deliver_data(data)
将数据传递给高层。
4.1.2. 经具有比特差错信道的可靠数据传输:rdt2.x - 自动重传请求协议ARQ
此情况下,数据在信道中传输,有可能发生比特差错。
为了让接收方最终得到无差错的数据,我们可以如下操作:
- 差错检测。接收方检测接收的数据是否出现比特差错,通过分组中的校验和字段实现。
- 接收方反馈。如果无差错,则反馈 肯定确认
ACK
;反之,则反馈 否定确认NAK
。 - 重传。如果发送方收到否定确认
NAK
,则重传该分组。
基于这样重传机制的可靠数据传输协议,被称为自动重传请求协议(Automatic Repeat reQuest protocols, ARQ)。
4.1.2.1. rdt2.0 - 停等协议
发送方的状态图:
checksum
:用于差错检测的数据。Wait for ACK or NAK
:等待ACK或NAK。isNAK(rcvpkt)
:接收到的为否定确认。isACK(rcvpkt)
:接收到的为肯定确认∧
:不进行任何动作。
接收方的状态图:
corrupt(rcvpkt)
:接受的分组存在差错。nocorrupt(rcvplt)
:接收的分组不存在差错。
发送方在发送完一个分组后,并不会发送新的分组,除非发送方确信接收方已正确接收当前分组。由于这种行为,rdt2.0
这样的协议又被称为停等协议。
4.1.2.2. rdt2.1
rdt2.0看似完美,但它存在一个致命的缺陷:没有考虑ACK或NAK分组受损的可能性!!!
处理受损ACK或NAK有3种方法:
当发送方接收到“含糊不清”的ACK或NAK时,它将反问接收方:“你在说神魔?”。但是,如果发送方的“你在说神魔?”也发生了差错,那将出现更大问题!
增加足够的检验和比特,使发送方不仅可以检测差错,还可以恢复差错。但,这样将花费很多比特。
当发送方收到受损的ACK或NAK时,直接重传当前分组。但,问题在于接收方并不能区分:这是重传的分组,还是新的分组。
为了解决第3种方法产生的问题,有一个简单的办法:序号(sequence number)。让发送方对其分组编号,接收方只需要检查序号,即可知道这是重传还是新的分组。
而对于rdt2.0这种简单情况,只需要0
和1
两个序号就足够了。
发送方状态图:
Wait for call 0 from above
:等待高层对发送0号分组的调用。sndpkt=make_pkt(0,data,checksum)
中的0
:分组序号。(corrupt(rcvpkt) || isNAK(rcvpkt))
表示:接收到受损的ACK/NAK分组,或者 接收方返回NAK
。(nocorrupt(rcvpkt) && isACK(rcvpkt))
表示:接收到无损的ACK/NAK分组,且 接收方返回ACK
。- 在
Wait for ACK or NAK 0
等待接收方返回0号分组的ACK/NAK分组时:- 如果接收到受损的ACK/NAK分组,或者 接收方返回的是
NAK
,则重传0号分组。 - 如果接收到无损的ACK/NAK分组,且 接收方返回的是
ACK
,则转入Wait for call 1 from above
状态。
- 如果接收到受损的ACK/NAK分组,或者 接收方返回的是
接收方状态图:
has_seq0(rcvpkt)
:分组序号是否为0。sndpkt=make_pkt(ACK,checksum)
中的checksum
:用于ACK/NAK分组的差错检测数据。- 当在
Wait for 1 from below
状态等待1号分组时:- 如果接收到受损的分组,则返回
NAK
分组; - 如果接收到无损的0号分组,则返回
ACK
分组; - 如果接收到无损的1号分组,则返回
ACK
分组,并转入状态Wait for 0 from below
。
- 如果接收到受损的分组,则返回
4.1.2.3. rdt2.2
在rdt2.1的基础上,我们考虑能否不需要NAK呢?
以rdt2.1
中接收方的Wait for 1 from below
状态为例:
- 接收到无损的0号分组,则返回对0号分组的
ACK
; - 接收到无损的1号分组,则返回对1号分组的
ACK
,并转入下一状态; - 接收到受损的分组,则返回
NAK
。如果不发送NAK
,而是对上次正确接收的分组发送ACK
,我们也能实现与NAK
相同的效果。在Wait for 1 from below
状态中,即返回对0号分组的ACK
。
而发送方在Wait for ACK or NAK 1
状态下,接收到0号分组的ACK
,则相当于接收到NAK
,将重传1号分组。
对ACK
编号,这就是rdt2.2的改进。
发送方如下:
isACK(rcvpkt,1)
:接收到1号分组的ACK
。isACK(rcvpkt,0)
:接收到0好分组的ACK
。
接收方如下:
sndpkt=make_pkt(ACK,0,checksum)
:封装0号ACK
分组。sndpkt=make_pkt(ACK,0,checksum)
:封装1号ACK
分组。
4.1.3. 经具有比特差错的丢包信道的可靠数据传输:rdt3.0 - 比特交替协议
在此情况下,信道不仅会发生比特差错,还会丢包。
那怎么解决丢包问题呢?重传呗。
发送方如果在一段时间后,还没有收到对应分组的ACK
,则重传该分组。
发送方状态转换图如下:
start_timer
:表示开始计时器。- 在
Wait for ACK 0
状态下:- 如果接收到受损的ACK分组,或1号ACK分组,则啥都不干,坐等超时;
- 如果
timeout
超时事件发生,则将重传0号分组,并重置计时器; - 如果接收到正确的0号ACK分组,则暂停计时器,转入下一状态。
- 在
Wait for call 1 from above
状态下,接收到任何分组都置之不理,因为可能是由于延时而产生的冗余分组。
接收方同rdt2.2。
rdt3.0在各种情况下的运行过程:
rdt3.0有时被称为比特交替协议(alternating-bit protocol)。
至此,我们得到了一个可靠数据传输协议!!!
4.2. 流水线可靠数据传输协议
rdt3.0虽然是一个可靠数据传输协议,但性能并不令人满意。其性能问题的核心在于它是一个停等协议。
我们定义发送方(或信道)的利用率为:发送方实际忙于将发送比特送进信道的那部分时间与发送时间之比:
$$
U_{sender} = \frac {L/R} {RTT + L/R}
$$
- $L$:分组字节长度
- $R$:发送方发送速率
- $RTT$:往返传播时延
可以看出,当$RTT$较大时,利用率将会非常低。
为了解决这个问题,我们可以:不以停等协议运行,允许发送方同时发送多个分组而无须等待确认,即流水线技术。
但新的技术总伴随着新的问题:
- 必须增加序号范围,因为每个发送的分组必须有一个唯一的序号。
- 发送方和接收方不得不缓存多个分组。
- 连续发送的分组中,如何解决分组的差错恢复、丢包重传等问题。两种解决方法:
- 回退N步(Go-Back-N,GBN)/ 滑动窗口协议
- 选择重传(Selective Repeat,SR)
4.2.1. 回退N步(GBN)/ 滑动窗口
在回退N步协议中,发送方可以同时发送多个分组,但它受限于某个最大数N。
发送方所维护的GBN协议的序号空间和窗口如下:
图示:
base
:基序号,指向第一个发送但未收到确认ACK
的分组。永远指向窗口头部。nextseqnum
:下一个序号,指向第一个待发送的分组。会在窗口中前后滑动。Window size N
:滑动窗口的长度N
。- 包含四种状态的分组:
Already ACK'd - 深蓝
:已收到ACK
确认的分组。Sent, not yet ACK'd - 浅蓝
:已发送,但还未收到ACK
确认的分组。Usable, not yet send - 灰色
:待发送的分组。Not Usable - 白色
:还未准备好的分组。
随着协议的运行,窗口在序号空间中向前滑动。实际中,N
的大小,受信道拥塞程度的影响。
在GBN协议中,发送方需要响应三种类型的事件:
上层调用其发送数据。发送方首先检查发送窗口是否已满,即
nextseqnum - base = N
,是否有N个已发送但未收到ACK
确认的分组:- 如果窗口未满,则产生一个分组并发送,更新相应变量;
- 如果窗口已满,则将数据返回上一层,并指示窗口已满。
收到一个ACK。
-GBN协议采用累计确认的方式:收到序号为n
的ACK
分组(对n
号分组的确认),表明接收方已正确接收到序号 $\leq n$ 的所有分组;- 重启定时器,如果所有分组都已发送和确认,则停止该定时器
超时事件。如果出现超时,发送方将重传所有已发送但未被确认的分组,这就像协议的名字“回退N步”所说的那样。
在GBN协议中,接收方的工作也很简单:
- 如果一个序号为
n
的分组被正确接收,**并且按序,即上次接收到的分组的序号为n-1
**,接收方则返回序号为n
的ACK
分组。 - 而对于其它情况,接收方则丢弃接收到的分组,并发送最近按序接收到的分组的
ACK
分组。比如,序号为n
的分组被正确接收,但上次收到的分组的序号为n-2
,则丢弃n
号分组,并发送序号为n-2
的ACK
分组。
下图是一个GBN协议运行的例子:
4.2.2. 选择重传(SR)
GBN协议的缺点在于:单个分组的差错将会导致大量分组的重传,而许多分组根本没必要重传。
而选择重传协议,通过让发送方仅重传那些出现差错的分组,而避免了不必要的重传。
发送方和接收方的序号空间和窗口如下:
图示:
send_base
:指向发送方第一个发送但未收到确认ACK
的分组,永远指向发送方窗口头部。rcv_base
:指向接收方第一个期待接收的分组,永远指向接收方窗口头部。- 接收方四种状态的分组:
Out of order but already ACK'd - 深蓝
:失序但已背确认的分组;Expexted, not yet rec - 浅蓝
:期待接收的分组;Acceptable - 灰色
:可接受的分组;Not usable - 白色
:不可用的分组;
发送方的事件和动作:
上层调用其发送数据。与GBN协议一样。
收到一个ACK。发送方将该
ACK
对应的分组标记为已接收,如果该分组的序号为send_base
,则窗口向前滑动到具有最小序号的未确认分组处。超时事件。每个分组都必须拥有自己的逻辑定时器,超时发生后只能发送一个分组。
接收方的事件和动作:
滑动窗口内的分组(序号在
[rcv_base, rcv_base+N-1]
内)被正确接收:- 如果该分组以前没收到过,则缓存该分组;
- 如果该分组的序号等于
rcv_base
,则将该序号之后连续的已缓存分组交付给上层,并将窗口向前移动到具有最小序号的期待接受分组处。
滑动窗口前的分组(序号在
[rcv_base-N, rcv_base-1]
内)被正确接收。产生一个ACK,即使接收方已经确认接收过该分组(防止ACK未到达发送方)。其他情况,忽略该分组。
下图是一个SR协议运行的例子:
4.2.3. 有限序号范围的问题
当面对有限序号范围时,由于发送方和接收方窗口之间不可能同步,所以,接收方面临的困境就是:无法判断该序号的分组是一个新分组还是一次重传。
包括4个分组序号、窗口长度为3的示例如下:
接收方收到的0号分组为一次重传:
接收方收到的0号分组为一个新分组:
解决方法:窗口长度必须小于或等于序号空间大小的一半。
4.3. 可靠数据传输机制及其用途的总结
5. Connection-Oriented Transport: TCP (面向连接的运输:TCP)
TCP/IP协议(传输控制协议/网际协议,Transmission Control Protocol/Internet Protocol),是当今因特网的支柱性协议。
TCP概述:
- 面向连接:在发送数据之前,客户端需要与服务端建立一个连接。三次握手、四次挥手。
- 可靠传输:TCP提供可靠数据传输。
- 全双工:TCP连接提供全双工服务。
- TCP报文段(TCP segments):TCP报文的称呼。
5.1. TCP 报文结构
TCP报文段由两部分组成:
- 首部字段:一般20字节
- 数据部分:数据部分大小受限于最大报文段长度(MSS,maximum segment size)。MSS又受限于最大链路层帧长度/最大传输单元(MTU,maximum transmission unit),MTU = TCP/IP首部(一般40字节)+ MSS。以太网和PPP链路层协议的MTU都为1500字节,因此MSS典型值为1460字节。当TCP发送一个大文件时,例如某Web页面的一个图像,TCP会将该文件划分长度为MSS的若干块。
源端口号(Source Port) 和 目的端口号(Dest Port):用于标识源主机和目的主机上的进程。
序号(Sequence Numbers) 和 确认号(Acknowledgment Numbers):用于实现可靠数据传输。
序号:TCP将数据看成有序的字节流,序号是TCP报文段中数据部分首字节的字节流编号。如下图,500000字节的文件,MSS为1000字节,文件被划分成500个TCP报文段,第一个报文段序号为0,第二个报文段序号为1000,以此类推。示例中初始序号为0,实际上初始序号是随机产生的——由于网络中有可能存活着旧连接的TCP报文段,这样可以防止新连接阴差阳错地接收到旧连接残留的报文。
确认号:由于TCP是全双工的,因此主机A在向主机B发送数据时,也会接收来自主机B的数据。主机A报文段中的确认号,就是主机A期望从主机B收到的下一个字节的序号。同时,表明主机A已经成功收到确认号之前的数据,这样可以实现可靠数据传输中累计确认的功能。
示例(
Seq
:序号,ACK
:确认号):
4比特的首部长度(Header length):TCP首部的长度,以4字节为单位。
8比特位的标志字段:
URG
:紧急数据标志位ACK
:确认标志位PSH
:请求推送位,接收端应尽快把数据传送给应用层RST
:连接复位,通常,如果TCP收到的一个分段明显不属于该主机的任何一个连接,则向远程发送一个复位包SYN
:建立连接时使用FIN
:释放连接时使用
接收窗口(Recieve window):用于流量控制。
检验和(Internet checksum):与UDP检验和字段一样。
紧急数据指针(Urgent data pointer):只有当紧急标志置位时URG,该16位的字段才有效。
可选与变长的选项字段(Options)
数据(Data)
5.2. TCP 可靠数据传输
TCP在IP不可靠的尽力而为服务之上创建了一种可靠数据传输服务,TCP可靠数据传输服务 = rdt3.0 + 流水线。
发送方:
上层调用其发送数据。生成具有序号的TCP报文段,并启动定时器(只有一个,如果已经启动则不需重启)。
收到一个ACK。
- 采用累计确认的方式:收到确认号为
n
的ACK分组,表明接收方已正确接收到序号n
之前的所有分组; - 重启定时器。如果所有分组都已发送和确认,则停止该定时器。
- 采用累计确认的方式:收到确认号为
收到3个冗余ACK(冗余ACK是对已确认报文段的再次确认)。一旦收到3个冗余ACK,发送方则快速重传冗余ACK报文确认号对应的报文段。
超时事件。如果出现超时,发送方将重传第一个(序号最小)已发送但未被确认的分组(与滑动窗口不同的是:只重传一个!),并启动定时器。
接收方:
报文段按序到达。延迟的ACK,对下一个按序报文段等待500ms,如果没有到达,则发送一个ACK;如果到达,则立即发送累积ACK。
比期望序号大的报文段到达。立即发送冗余ACK,即期望序号的ACK。
中间缺失的报文段到达。立即发送ACK。
TCP是回退N步还是选择重传呢?
TCP更类似于回退N步(滑动窗口),但不同点在于:GBN中,如果超时,则重传所有已发送但未被确认的报文段;但TCP中,超时只重传第一个已发送但未被确认的报文段。
5.3. TCP 流量控制
问题:如果某应用读取数据时相对缓慢,而发送方又发送得太多、太快,接收方的接受缓存就会出现溢出。
为此,TCP为它的应用程序提供了 流量控制(flow-control) 服务。
发送方维护一个 接收窗口的变量 rwnd
来实现流程控制,该变量表示接收方还有多少可用的缓存空间。发送方已发送但未被确认报文段的总字节数,不能超过接收窗口的值。
接收方会将当前剩余的缓存空间,通过TCP确认报文中的接收窗口字段告诉发送方。
新的问题:假如接收方缓存空间已满,它通过TCP确认报文告诉发送方,发送方接收窗口的变量变为0,发送方将不再发送报文段。此时,若接收方缓存出现空余,它将不能告诉发送方。
解决:接收窗口变量为0时,发送方将继续发送只有一个字节的报文段,如果接收方缓存开始清空,则会返回确认报文,并将接收窗口字段设为非0值。
5.4. TCP 拥塞控制
众所周知,网络是存在拥塞的。如果拥塞时,还向网络发送数据,那将加剧拥塞。这就像交通堵塞一样。
那么,TCP是如何进行交通管制的呢?它首先要解决三个问题:
- TCP发送方如何限制其发送速率?
- TCP如何感知路径上存在拥塞的呢?
- 当感知到拥塞时,采用何种算法来改变发送速率呢?
跟流量控制一样,发送方也维护着一个**拥塞窗口的变量 cwnd
**,发送方已发送但未被确认报文段的总字节数,不能超过min{rwnd, cwnd}
。
但在讨论拥塞控制时,我们假设接收方缓存是无限大的,即发送方的已发送但未被确认报文段的总字节数,只取决于拥塞窗口变量。
并且,我们需要知道网络中没有明确的拥塞状态信号,TCP通常通过隐式地感知拥塞:超时事件 和 3个冗余ACK。
接下来,我们将介绍广受赞誉的TCP拥塞控制算法(TCP congestion-control algorithm),它包含3个主要部分:
- 慢启动
- 拥塞避免
- 快速恢复
其中,慢启动和拥塞避免最为关键。有时,也算上快速重传算法,即4个部分。
5.4.1. 慢启动(Slow start)
思想:从一个较小值开始,逐渐增加拥塞窗口值。
具体:
- 初始拥塞窗口值设置为 1~4 个MSS(最大报文段长度);
- 每收到一个按序的确认报文后,则将拥塞窗口值增加 1 MSS:
cwnd = cwnd + 1MSS
。
由于每次接收到的报文数,即为拥塞窗口值/MSS,所以,拥塞窗口值呈倍数增长,一点也不慢!
考虑慢启动中的3种特殊情况:
- 当拥塞窗口值
cwnd
达到慢启动阈值ssthresh
时,将进入拥塞避免状态; - 当遇到超时事件时,慢启动阈值
ssthresh
将被设置为cwnd/2
,再将拥塞窗口值cwnd
重置为1 MSS
; - 当遇到3个冗余ACK时,慢启动阈值
ssthresh
也将被设置为cwnd/2
,但拥塞窗口值cwnd
被设为ssthresh
,并执行快速重传,然后进入快速恢复状态。
5.4.2. 拥塞避免(Congestion Avoidance)
思想:缓慢增加拥塞窗口值。
具体:
- 当每一轮发送的所有报文段,都收到确认报文时,拥塞窗口值加 1 MSS:
cwnd = cwnd + 1MSS
。
考虑拥塞避免中的2种特殊情况:
- 当遇到超时事件时,慢启动阈值
ssthresh
将被设置为cwnd/2
,拥塞窗口值cwnd
将被重置为1 MSS
,并进入慢启动状态; - 当遇到3个冗余ACK时,慢启动阈值
ssthresh
将被设置为cwnd/2
,拥塞窗口值cwnd
被设为ssthresh
,并快速重传冗余ACK指定的报文段,然后进入快速恢复状态。
5.4.3. 快速恢复(Fast Recovery)
思想:收到3个冗余ACK说明网络并不像超时那么糟糕。
具体:
- 在快速恢复状态中,也会发送报文段。
- 如果收到冗余ACK,那么拥塞窗口值增加 1 MSS:
cwnd = cwnd + 1MSS
。由于进入快速恢复状态时,已经收到 3 个冗余ACK,所以进入快速恢复状态的初始拥塞窗口值为:cwnd = ssthresh + 3MSS
。
考虑快速恢复中的2中特殊情况:
- 当遇到超时事件时,慢启动阈值
ssthresh
将被设置为cwnd/2
,拥塞窗口值cwnd
将被重置为1 MSS
,并进入慢启动状态; - 当遇到新的ACK时,拥塞窗口值
cwnd
被设为ssthresh
,并进入拥塞避免状态。
5.4.4. 总结
TCP拥塞控制算法概括:加性增,乘性减。
状态图:
拥塞窗口值cwnd
变化示例(在TCP Reno
版本中加入了快速恢复状态):
5.5. TCP 三次握手与四次挥手
TCP是面向连接的,那么TCP是如何建立、释放连接的呢?
5.5.1. 三次握手
第一步:客户端TCP首先向服务端TCP发送一个特殊的TCP报文段,不包含应用层数据,报文段首部的一个标志位
SYN
被置为 1 ,序号字段seq
被置为一个随机值client_isn
。这个特殊报文段被称为SYN 报文段。第二步:服务端收到 SYN 报文段后,也返回一个特殊的TCP报文段,不包含应用层数据,首部的标志位
SYN
被置为 1 ,确认号字段ack
被置为client_isn + 1
,序号字段seq
被置为一个随机值server_isn
。这个特殊报文段被称为SYNACK 报文段。第三步:客户端收到 SYNACK 报文段后,可以返回普通的TCP报文段,可以包含应用层数据,首部的标志位
SYN
被置为 0 ,确认号字段ack
被置为server_isn + 1
,序号字段seq
被置为client_isn + 1
。
为什么需要三次握手呢?
TCP连接是双向的,第一次和第二次的成功能够保证服务端听得到客户端的声音,第二次和第三次的成功能够保证客户端听得到服务端的声音。这可以类比打电话:
1 | “喂,你听得到吗?” |
为什么不是两次握手呢?
因为,握手阶段结束后,服务端将会为新连接分配变量和缓存。如果两次握手中第二次的 SYNACK 报文段丢失,客户端接收不到确认报文段,不会发送数据,这导致服务器会白白分配变量和缓存、苦苦等待,浪费空间和时间。
为什么不是四次握手呢?
多余。通过第二次握手,服务端不仅可以告诉客户端自己听得到,也可以验证客户端是否听得到自己。
5.5.2. 四次挥手
天下没有不散的宴席,TCP通过四次挥手断开连接:
客户端/服务端发送 FIN 报文段,首部
FIN
字段被置为 1 ,表明自己已经发送完所有数据。服务端/客户端返回 ACK 报文段,表明自己知道对方已经发送完数据了。
服务端/客户端发送 FIN 报文段,首部
FIN
字段被置为 1 ,表明自己已经发送完所有数据。客户端/服务端返回 ACK 报文段,表明自己知道对方已经发送完数据了。
为什么四次挥手呢?
很简单,因为TCP是全双工的,一方发送完数据,不代表另一方也发送完数据。
6. socket 套接字编程
- UDP 客户端:
1 | from socket import * |
- UDP 服务端:
1 | from socket import * |
- TCP 客户端:
1 | from socket import * |
- TCP 服务端:
1 | from socket import * |
7. 补充
7.1. IP 分片 与 TCP 分段
由于受链路层 MTU(Maximum Transmission Unit,最大传输单元) 的影响,网络层IP会将数据报分片传输,而这对运输层是透明的,当这些数据报的片到达目的端时有可能会失序,但是在IP首部中有足够的信息让接收端能正确组装这些数据报片。
尽管IP分片过程看起来透明的,但有一点让人不想使用它:即使只丢失一片数据也要重新传整个数据报。因为TCP报文段,对应于一份IP数据报(而不是一个分片),TCP没有办法只重传数据报中的一个数据分片。
因此,TCP试图避免IP分片。TCP是如何避免IP分片的呢?一旦TCP数据过大,超过了MSS(MSS = MTU - TCP/IP首部),则在运输层就会对TCP数据进行分段,这样到了IP层的数据报,自然不会超过MTU,也就不用分片了。
而使用UDP很容易导致IP分片。