文章目录
3 传输控制协议TCP
在TCP/IP协议中,TCP协议是基于IP协议的。IP协议是对应于网络层的协议,它是一个不可靠的协议。TCP协议的可靠性保证给IP协议提供了可靠环境,从而使得IP协议可以不必考虑传输的可靠性,专注于网络层的功能。这也是协议分层的初衷。
IP协议解决了数据包的路由和传输,上层的TCP就可以不再关注路由和寻址;TCP协议解决了传输的可靠性和顺序问题,上层的应用层就可以直接使用TCP协议进行数据传输,不再需要关心数据段的丢失和重复。HTTP是要基于TCP连接基础上的,简单的说,TCP就是单纯的建立连接,不涉及任何我们需要请求的数据。http协议使用来收发数据,就是为实际应用而来的。
3.1 TCP的特点
TCP是TCP/IP系统中非常复杂的协议。主要特点有:
- 是面向连接的传输层协议。也就是说,在使用TCP协议之前,必须建立TCP连接。传输数据完毕后,释放连接。
- 每条TCP连接只能有两个端点(endpoint),即是点对点(一对一)。
- 提供可靠交付的服务。通过TCP连接传送的数据,无差错,不丢失,不重复,并且按序到达。
- TCP提供全双工通信。TCP允许通信双方的应用进程在任何时候都能发送数据。TCP连接两端都设有发送缓存和接收缓存,用来临时存放双方通信的数据。
- 在发送时,应用程序在把数据传送给TCP的缓存后,就可以做自己的事情,而TCP在合适的时候把数据发送出去。
- 在接收时,TCP把收到的数据放入缓存,上层的应用进程在合适的时候读取缓存中的数据。
- 面向字节流。TCP中的“流”(stream)指的是流入到进程或从进程流出到字节序列。
3.2 TCP的连接
每一条TCP有两个端点。这个端点叫做套接字(socket)或插口。根据RFC793定义:端口号拼接到IP地址即构成了套接字。
套接字的表示方法:
套接字 socket = (IP地址:端口号)
TCP连接就可以表示为:
TCP连接 ::= {socket1,socket2} = {(IP1:port1),(IP2:port2)}
3.3 TCP可靠传输的原理
IP层只能提供尽最大努力服务,因此,TCP必须采取适当的措施才能保证两个传输层之间通信的可靠。理想的传输条件有以下两个特点:
- 传输信道不产生差错
- 不管发送方以多快的速度发送数据,接收方总是来得及处理收到的数据。
实际的网络都不具备以上两个理想条件,但是我们可以使用一些可靠传输协议,当出现差错时让发送方重传出现差错的数据,同时在接收方来不及处理收到的数据时,及时告诉发送方适当降低发送数据的速度,从而实现可靠的传输。而基于这样重传机制的可靠传输协议称为自动重传请求ARQ。
3.3.1 自动重传请求ARQ(Automatic Repeat reQuest)
ARQ协议,即自动重传请求(Automatic Repeat-reQuest),是OSI模型中的错误纠正协议之一。它通过使用确认和重传这两个机制,在不可靠服务的基础上实现可靠的信息传输。
- 如果发送方在发送后一段时间之内没有收到确认帧,它通常会重新发送。
- 重传的请求是自动进行的,接收方不需要请求发送方重传某个出错的分组
ARQ包括停止等待ARQ协议和连续ARQ协议。先从停止等待ARQ讲起,理解ARQ的原理。
3.3.2 停止等待ARQ
全双工通信的双方既是发送方也是接收方。这里仅介绍A发送到B的情况,同理B发送到A可知。“停止等待”就是每发送完一个分组就停止发送,等待对方的确认。在收到确认后再发送下一个分组。
3.3.2.1 无差错情况
这是最简单的无差错情况。A发送完分组M1,发完就暂停发送,等待B的确认。
3.3.2.2 传输出现差错
传输出现差错分两种情况:一是M1丢失,二是M1确认丢失。
1. M1丢失
出现差错可能是以下两种情况:
- B接收M1时检测出了差错,就丢弃M1,其他什么也不做(不通知A收到有差错的分组)。
- M1在传输过程中丢失了,这时B当然什么都不知道,也什么都不做。
在这两种情况下,B都不会发送任何信息。A只要超过一段时间任然没有收到确认,就认为刚才的M1丢失,因而重传M1。这就叫超时重传。
要实现超时重传,必须做到:
- 每发送完一个分组时设置一个超时计时器。如果超时计时器到期之前收到对方的确认,就撤销超时计时器。
- A在发送完一个分组后,必须暂时保留已发送的分组的副本,超时重传时使用。
- 分组和确认分组都必须进行编号,这样才能确认是哪一个分组收到了确认。
- 超时计时器设置的重传时间应当比数据在分组传输的平均往返时间更长一些。
2. M1确认丢失
另外一种情况就是B发送的对M1的确认丢失。这时超过了A设定的超时时间后,A重传M1,此时B采取两个动作:
- 丢失这个重复的分组M1,不向上层交付。
- 向A发送确认,不能认为已经发送过确认就不再发送,因为A之所以重传M1,就是表示A没有收到M1的确认。
3.3.2.3 确认延迟
还有一种可能出现的情况,就是传输过程中没有出现差错,但B对分组M1的确认迟到了。A会收到重读的确认,此时A收下确认就丢弃,B任然会收到重复的M1,并且同样要丢弃重复的M1。
3.3.2.4 信道利用率
停止等等协议的优点是简单,但是信道利用率太低。其示意图如下:
此时的信道利用率公式为:
U
=
T
D
T
D
+
R
T
T
+
T
A
U = \frac{T_D}{T_D+RTT+T_A}
U=TD+RTT+TATD
- T D T_D TDA发送发布需要的时间
- R T T RTT RTT往返时间
- T A T_A TAB发送确认分组需要的时间
3.3.3 连续ARQ协议
3.3.3.1 流水线传输
由于停止等待ARQ协议信道利用率非常低的。因此为了提供传输效率,发送方采用流水线传输。流水线传输就是发送方可以连续发送多个分组,不必每发完一个分组就停下来等待对方确认。这就需要用到连续ARQ协议和滑动窗口协议。如下图所示:
流水线传输通常要求
- 必须增加序号范围,因为每个输送中的分组(不计算重传的)必须有一个唯一的序号,因为可能存在有多个在输送中未确认的报文。
- 协议的发送方和接收方要能缓存多个分组。发送方至少要能缓存那些已发送但没有确认的分组,接收方可能要缓存那些已正确接收的分组。
- 所需序号范围和对缓冲的要求取决于数据传输协议如何处理丢失、损坏及延时过大的分组。
- 解决流水线传输的差错恢复有两种方法:回退N(Go-Back- N,GBN) 和选择重传(Selective Repeat,SR) 。
3.3.3.2 连续ARQ协议
连续ARQ协议通常是结合滑动窗口协议来使用的,发送方需要维持一个发送窗口,如下图所示:
图(a)是发送方维持的发送窗口,它的意义是:位于发送窗口内的5个分组都可以连续发送出去,而不需要等待对方的确认,这样就提高了信道利用率。
连续ARQ协议规定,发送方每收到一个确认,就把发送窗口向前滑动一个分组的位置。例如上面的图(b),当发送方收到第一个分组的确认,就把发送窗口向前移动一个分组的位置。如果原来已经发送了前5个分组,则现在可以发送窗口内的第6个分组。
3.3.3.3 累积确认
接收方一般都是采用累积确认的方式。也就是说接收方不必对收到的分组逐个发送确认。而是在收到几个分组后,对按序到达的最后一个分组发送确认。如果收到了这个分组确认信息,则表示到这个分组为止的所有分组都已经正确接收到了。
累积确认的优点是容易实现,即使确认丢失也不必重传。但缺点是,不能正确的向发送方反映出接收方已经正确收到的所以分组的信息。
3.3.4 滑动窗口协议
滑动窗口协议,也称为GBN,是在在发送方和接收方之间各自维持一个滑动窗口,发送发是发送窗口,接收方是接收窗口,而且这个窗口是随着时间变化可以向前滑动的。它允许发送方发送多个分组而不需等待确认。TCP的滑动窗口是以字节为单位的。
3.3.4.1 窗口建立
假定A收到了B发来的确认报文段,其中窗口是20字节,而确认号是31(表明B期望收到的下一个序号是31,而序号30为止的数据已经收到了)。根据这两个数据,A就构造出自己的发送窗口如下图所示:
注意:发送窗口的大小一定不能超过接收方窗口数值,而且还有收到网络拥塞程度的制约。
3.3.4.2 滑动窗口原理
发送窗口中有四个概念:
- 已发送并收到确认的数据(不在发送窗口和发送缓冲区之内)
- 已发送但未收到确认的数据(位于发送窗口之内)
- 允许发送但尚未发送的数据(位于发送窗口之内)
- 发送窗口之外的缓冲区内暂时不允许发送的数据
接收窗口中也有四个概念:
- 已发送确认并交付主机的数据(不在接收窗口和接收缓冲区之内)
- 未按序收到的数据(位于接收窗口之内)
- 允许的数据(位于接收窗口之内)
- 不允许接收的数据(位于发送窗口之内)
当A的发送窗口内的序号都已用完,但还没有再收到确认时,必须停止发送。
为了解决流水线传输的差错,ARQ有多种实现方式:回退N(Go-Back- N,GBN) 和选择重传(Selective Repeat,SR) 来解决这个问题。
3.3.5 回退N(GBN)
比如发送方发送了前5个分组,而中间的第3个分组丢失了,这时候接收方只能对前2个发出确认。而不知道后面3个分组的下落,因此只能把后面的3个分组都重传一次,这种机制叫Go-back-N(回退N GBN),表示需要再退回来重传已发送过的N个分组。
如果下面的网络层协议丢失了很多分组,那么返回N协议的效率就会很低。因为每当一个分组损坏,发送方就需要重传所有待确认的分组,虽然其中有些分组实际上已经完整地被接收了。
3.3.6 超时重传时间
由于TCP的下层网络的速率各不相同,而且每个IP数据报经过的路由也是各不相同,这就导致超时重传时间具体该设置多长时间呢?
TCP采用了一种自适应算法,它记录一个报文段发出的时间,以及收到相应的确认的时间。这两个时间之差就是报文段的往返时间RTT。TCP保留了RTT的一个加权平均往返时间RTTs(又称为平滑的往返时间)。
每当第一次测量到RTT样本时,RTTs值就取为所测量到的RTT样本值。但以后每测量到一个新的RTT样本,就按下式重新计算一次RTTs:
R
T
T
s
=
(
1
−
α
)
∗
R
T
T
s
+
α
∗
R
T
T
RTT_s = (1-α) * RTT_s+ α*RTT
RTTs=(1−α)∗RTTs+α∗RTT
- 0 < α < 1 0<α<1 0<α<1,根据RFC6298推荐α值为1/8,即0.125
超时计时器设置的超时重传时间RTO应略大于RTTs。RFC6298推荐使用下式计算RTO:
R
T
O
=
R
T
T
s
+
4
∗
R
T
T
d
RTO = RTTs + 4*RTT_d
RTO=RTTs+4∗RTTd
R
T
T
d
RTT_d
RTTd是RTT的偏差的加权平均值。RFC6298推荐计算方法,当第一次测试量时,
R
T
T
d
RTT_d
RTTd值取为测量到的RTT样本值的一半。在以后的测量中,则使用下式计算:
R
T
T
d
=
(
1
−
β
)
∗
R
T
T
d
+
β
∗
∣
R
T
T
s
−
R
T
T
∣
RTT_d = (1-β) * RTT_d+ β*|RTT_s - RTT|
RTTd=(1−β)∗RTTd+β∗∣RTTs−RTT∣
- β的推荐值0.25
3.3.7 选择确认SACK(Selective Acknowledgement)
ARQ协议和滑动窗口协议确实保障了数据的可靠传输,随之而来的问题就是通信效率不高。因为在tcp通信时,计算机在传输连续数据第1,2,3,4分组时,中间的第2分组数据在网络中丢失了,tcp会重传最后确认的第2分组和后面的第3,4分组,导致第3,4分组被重复发送,降低了tcp性能,于是根据这种情况发展出了选择确认。
TCP收到乱序数据后,会将其放入乱序队列中,然后发送重复ACK给对端。对端如果收到多个重复的ACK,认为发生丢包,TCP会重传最后确认的包开始的后续包。这样原先已经正确传输的包可能会重复发送,降低了TCP性能。为改善这种情况,发展出SACK技术,使用SACK选项可以告知发包方收到了哪些数据,发包方收到这些信息后就会知道哪些数据丢失,然后立即重传丢失的部分。
需要注意的是只有收到失序的分组时才会可能会发送SACK,TCP的ACK还是建立在累积确认的基础上的。也就是说如果收到的报文段与期望收到的报文段的序号相同就会发送累积的ACK,SACK只是针对失序到达的报文段的。
格式
SACK包括了两个TCP选项,一个选项用于标识是否支持SACK(SACK_permitted),是在TCP连接建立时时发送;另一种选项则包含了具体的SACK信息。
- SACK_permitted选项 - 该选项只允许在TCP连接建立时,有SYN标志的包中设置,也即TCP握手的前两个包中,分别表示通信的两方各自是否支持SACK。
0 8 16
+--------+--------+
|Kind=4 |Length=2|
+--------+--------+
- SACK信息选项 - 用于通告对端接收数据的信息。该选项参数告诉对方已经接收到并缓存的不连续的数据块,注意都是已经接收的,发送方可根据此信息检查究竟是哪个块丢失,从而发送相应的数据块。
0 16 32
+--------+--------+--------+--------+
| | |Kind=4 |Length=2|
+--------+--------+--------+--------+
| Left Edge of 1st Block |
+--------+--------+--------+--------+
| Right Edge of 1st Block |
+--------+--------+--------+--------+
/ ... /
+--------+--------+--------+--------+
| Left Edge of nth Block |
+--------+--------+--------+--------+
| Right Edge of nth Block |
+--------+--------+--------+--------+
- Left Edge of Block - 不连续块的第一个数据的序列号
- Right Edge of Block - 不连续块的最后一个数据的序列号之后的序列号
3.3.6 选择重传(SR)
3.4 TCP报文格式
TCP虽然是面向字节流的,但TCP传送的数据单元却是报文段。TCP报文段分为首部和数据两部分。其格式如下:
TCP报文段首部的前20个字节是固定的,后面4n字节是根据需要而增加的选项(n是整数)。
3.4.1 源端口和目的端口字段
- TCP源端口(Source Port):源计算机上的应用程序的端口号,占16位。
- TCP目的端口(Destination Port):目标计算机的应用程序端口号,占16位。
3.4.2 序号
占32位(即4个字节),序号范围 [ 0 , 2 32 − 1 ] [0,2^{32}-1] [0,232−1],共4294967296个序号,当增加到 2 32 − 1 2^{32}-1 232−1后,下个序号就又回到0。
TCP是面向字节流的,在一个TCP连接中的字节流中的每一个字节都按顺序编号。整个要传送的字节流的起始序号必须在连接建立时设置。
首部中的序号字段值则指的是本报文段所发送的数据的第一个字节的序号。
例如:一个报文段的序号字段值为301,而携带的数据共有100字节。说明本报文段的第一个字节的序号为301,最后一个字节的序号为400。而下一个报文段的数据序号应当从401开始,即下一个报文段的序号字段值应为401。
当SYN标记不为1时,这是当前数据分段第一个字母的序列号;如果SYN的值是1时,这个字段的值就是初始序列值(ISN),用于对序列号进行同步。这时,第一个字节的序列号比这个字段的值大1,也就是ISN加1。
由于序号字段长度32位,可对4GB(即4千兆字节)的数据进行编号。在一般情况下可保证当序号重复使用时,旧序号的数据早已通过网络到达重点了。
3.4.3 确认号
占32位(即4个字节),是期望收到对方下一个报文段的第一个数据字节的序号。
例如:B正确收到了A发送过来的一个报文段,其序号字段值是501,而数据长度是200字节(序号501~700),这表明B正确收到了A发送的到序号700为止的数据。因此,B期望收到A的下一个数据序号是701,于是B在发送给A的确认报文段中把确认号置为701。
若确认号 = N,则表明:到序号N-1为止的所有数据都已正确收到。
3.4.4 数据偏移
占4位,指出TCP报文段的数据起始处距离TCP报文段的起始处有多远。实际上是指出TCP报文段的首部长度。由于首部中还有长度不确定的选项字段,因此数据偏移字段是必要的。
应注意:数据偏移的单位是32位字(即以4字节的字为计算单位)。由于4位二进制数能否表示的最大十进制数字为15,因此数据偏移的最大值是60字节,这也是TCP首部的最大长度(即选项长度不能超过40字节)。
3.4.5 保留
占6位,保留给今后使用,目前置为0。
以下6~11位报文性质的控制位:
3.4.6 紧急URG(URGent)
当URG=1时,表明紧急指针字段有效。告知系统此报文段中有紧急数据,应当尽快传送(相当于高优先级的数据),而不要按原来的排队顺序来传送。
当URG置为1时,发送应用进程就告诉发送方的TCP有紧急数据要传送。于是发送方TCP就把紧急数据插入到本报文数据的最前面,而在紧急数据后面的数据仍是普通数据。这时要与首部中紧急指针(Urgent Pointer)字段配合使用。
3.4.7 确认ACK(ACKnowledgment)
仅当ACK=1时确认号字段才有效。当ACK=0时,确认号字段无效。
TCP规定,在连接建立后所有传送的报文段都必须把ACK置为1。
3.4.8 推送PSH(PuSH)
当两个应用进程进行交互式的通信时,有时在一端的应用程序希望在键入一个命令后立即就能够收到对方的响应。在这种情况下,TCP就可以使用推送操作。
这时,发送方TCP把PSH置为1,并立即创建一个报文段发送出去。接收方TCP收到PSH=1的报文段,就尽快的(即推送向前)交付接收应用进程,而不再等到整个缓存都填满了后再向上交付。
虽然应用程序可以选择推送操作,但推送操作很少使用。
3.4.9 复位RST(ReSeT)
当RST=1时,表明TCP连接中出现严重差错(如由于主机崩溃或其他原因),必须释放连接,然后在重新建立传输连接。
RST置1还用来拒绝一个非法的报文或拒绝打开一个连接。
RST也可以称为重建位或重置位。
3.4.10 同步SYN(Synchronization)
在连接建立时用来同步序列号。当SYN=1而ACK=0时,表明这是一个连接请求报文段。对方若同意建立连接,则应在响应的报文段中使用SYN=1和ACK=1。
因此,SYN置为1就表示一个连接请求或连接接收报文。
3.4.11 终止FIN
用来释放一个连接。当FIN=1时,表明此报文段的发送方的数据已经发送完毕,并要求释放传输连接。
3.4.12 窗口
占16位(2个字节)。范围 [ 0 , 2 16 − 1 ] [0,2^{16}-1] [0,216−1]之间的整数。窗口指的是发送本报文段的一方的接收窗口(而不是自己的发送窗口)。
窗口值告诉对方:从本报文段首部中的确认号算起,接收方目前允许对方发送的数据量(以字节为单位)。之所以要有这个限制,是因为接收方的数据缓存空间是有限的。总之,窗口值作为接收方让发送方设置其发送窗口的依据。
例如:发送一个报文段,其确认号是701,窗口字段1000.这就告知对方,从701号算起,我(即发送此报文段的一方)的接收缓存空间还可以接收1000个字节数据(字节序号是701~1700),你(接收此报文的一方)在给我发送数据时,必须考虑到这一点。
3.4.13 校验和
占16位(2个字节)。校验和字段检验的范围包括首部和数据这两部分。
和UDP用户数据报一样,计算校验和时,要在TCP报文段的前面加上12字节的伪首部。伪首部的格式与UDP一致。但是应该把伪首部第4个字段中的17改为6。把第5个字段中的UDP长度改为TCP长度。接收方收到此报文段后,仍要加上这个伪首部来计算校验和。
3.4.14 紧急指针
占16位(2个字节)。紧急指针仅在URG=1时才有意义,它指出本报文段中的紧急数据的字节数(紧急数据结束后就是普通数据)。因此,紧急指针指出了紧急数据的末尾在报文段中的位置。当所有紧急数据都处理完毕时,TCP就告诉应用程序恢复到正常操作。
注意,即时窗口为0时也是可以发送紧急数据。
3.4.15 选项
长度不定,但长度必须是32bits的整数倍,如果不足,就填充0。
3.5 TCP流量控制
所谓流量控制(flow control)就是让发送发送速率不要过快,让接收方来得及接收。
3.5.1 利用滑动窗口实现流量控制
利用滑动窗口机制可以很方便的在TCP连接上实现对发送方的流量控制。其原理就是运用TCP报文段中的窗口大小字段来控制,发送方的发送窗口不能超过接收方给出的接收窗口的数值。
- 接收端通过将自己的窗口大小放入首部中的“窗口大小”字段,然后通过ack报文通知客户端。
- 发送端根据这个大小来调整自己的发送速度。如果接收区满了,那就将窗口设置为0,这时发送端不再发送,然后定期通过一个窗口探测来试探接收端的窗口大小。
考虑一种特殊的情况,就是接收方若没有缓存足够使用,就会发送零窗口大小的报文,此时发送放将发送窗口设置为0,停止发送数据。之后接收方有足够的缓存,发送了非零窗口大小的报文,但是这个报文在中途丢失的,那么发送方的发送窗口就一直为零导致死锁。
解决这个问题,TCP为每一个连接设置一个持续计时器(persistence timer)。只要TCP的一方收到对方的零窗口通知,就启动该计时器,周期性的发送一个零窗口探测报文段。对方就在确认这个报文的时候给出现在的窗口大小(注意:TCP规定,即使设置为零窗口,也必须接收以下几种报文段:零窗口探测报文段、确认报文段和携带紧急数据的报文段)。
3.5.2 TCP的传输效率
应用程序把数据传送到TCP的发送缓存后,剩下的发送任务就由TCP来控制了。为了提高传输效率,针对可能影响传输效率的情况,有几种不能的方案
3.5.2.1 Nagle算法(发送方)
有几种机制可以控制TCP报文段的发送时机:
- TCP维持一个变量,即最大报文段长度MSS,只要缓存中存放的数据达到MSS字节时,就组装成一个TCP报文段发送出去。
- 是由发送方的应用进程指明要求发送报文段,即TCP支持的推送(push)操作。
- 是发送方的一个计时器期限到了,就把当前已有的缓冲数据装入报文段(但长度不能超过MSS)发送出去。
有时TCP的报文数据非常少,而报文首部就需要40字节,可能远远大于报文数据。这样网络的传输效率就很低,为了减少网络中的小分组问题,在TCP的实现中广泛使用Nagle算法。
- 算法如下:
TCP总是尽可能发送最大大小的分组,Nagle在于防止一个连接在任何时刻都有多个小分组待确认。
若发送应用进程把要发送的数据逐个地送到TCP的发送缓存,则发送方就把第一个数据字节先发送出去,把后面到达的数据字节都缓存起来。当发送方收到对第一个数据字符的确认后,再把发送缓存中的所有数据组装成一个报文段发送出去,同时继续对随后到达的数据进行缓存。只有收到对前一个报文段的确认后才继续发送下一个报文段。当数据到达较快而网络速率较慢时,用这样的方法可明显的减少所用的网络带宽。Nagle算法还规定:当到达的数据已到发送窗口大小的一半或已到达报文段的最大长度(MSS)时,就立即发送一个报文段。
补充:默认情况下Nagle算法是开启的,TCP的套接字选项TCP_NODELY可用来禁止Nagle算法。
3.5.2.2 ACK延滞算法(接收方)
还有另外一种问题即糊涂窗口综合征(silly window syndrome),有时也会使TCP的性能变坏。
当发送端应用进程产生数据很慢、或接收端应用进程处理接收缓冲区数据很慢,或二者兼而有之;就会使应用进程间传送的报文段很小,特别是有效载荷很小。极端情况下,有效载荷可能只有1个字节;而传输开销有40字节(20字节的IP头+20字节的TCP头) 这种现象就叫糊涂窗口综合症。发送端和接收端都可能引起糊涂窗口结合症。
当发送方产生的糊涂窗口综合征可以由Nagle算法解决,而接收方引发的糊涂窗口结合症,则由ACK延滞算法解决。
- 算法如下:
TCP在接收到数据后不立即发送ACK,而是等待一小段时间(典型50~200ms),然后才发送ACK。TCP期待在这一段等待时间内自身有数据发送给对端,被延滞的ACK就可以由这些数据捎带,从而省掉一个分节。
(接收方等待一段时间,使得或者接收缓存已有足够空间容纳一个最长的报文段,或者等到接收缓存已有一半空闲的空间。出现以上两种情况,接收方就发出确认报文,并向发送方通知当前的窗口的大小。此外,发送方也不要发送太小的报文段,而是把数据累积成足够大的报文段,或达到接收方缓存的空间的一般大小。)
- 存在问题:
对于不在相反方向产生数据(就不能携带ACK给客户)的服务器来说,Nagle算法存在问题,客户要等到服务器的ACK延滞定时器超时才继续给服务器发送数据。这些客户则需要使用TCP_NODELY选项禁止Nagle算法。
3.6 TCP拥塞控制
3.6.1 拥塞控制的一般原理
在计算机网络中的链路容量(即带宽),交换结点中的缓存和处理机等,都是网络的资源。在某段时间,若对网络中某一资源的需求超过了改资源所能提供的可用部分,网络的性能就要变坏。这种情况就叫做拥塞(congestion)。
所谓拥塞控制就是防止过多的数据注入到网络中,这样就可以使网络中的路由器或链路不至过载。
拥塞控制与流量控制的关系密切,它们之间也存在着一些差别。拥塞控制是一个全局性的过程,涉及到所有的主机,所有的路由器,以及与降低网络传输性能有关的所有因数。而流量控制往往是指点对点通信量的控制,是个端到端问题,流量控制所要做的就是抑制发送端发送数据的速率,以便接收端来得及接收。
若出现拥塞而不进行控制,整个网络的吞吐量将随输入负荷的增大而下降。下图就是拥塞控制所起的作用:
当输入的负载到达一定程度 吞吐量不会增加,即一部分网络资源会丢失掉,网络的吞吐量维持在其所能控制的最大值,转发节点的缓存不够大这造成分组的丢失是拥塞的征兆。
3.6.2 TCP的拥塞控制方法
TCP进行拥塞控制的算法有四种:
- 慢开始(slow-start)
- 拥塞避免(congesiton avoidance)
- 快重传(fast retransmit)
- 快恢复(fast recovery)
为了讨论算法,假定
- 数据是单方向传送,而另一个方向只传送确认
- 接收方总是有足够大的缓存空间,因而发送发发送窗口的大小由网络的拥塞程度来决定
- 以TCP报文段的个数为讨论问题的单位,而不是以字节为单位
3.6.2.1 拥塞窗口
拥塞控制也是基于窗口的,为此,发送方维持一个叫做拥塞窗口cwnd(congestion window)的状态变量。拥塞窗口的大小取决于网络的拥塞成都,并且动态变化。发送方让自己的发送窗口等于拥塞窗口。
发送方控制拥塞窗口的原则是:只有网络没有出现拥塞,拥塞窗口就可以再增大一些,以便把更多的分组发送出去,这样就可以提高网络的利用率。但只要网络出现拥塞或有可能出现拥塞,就必须把拥塞窗口减小一些,以减少注入到网络中的分组数,以便缓解网络出现的拥塞。
判断网络拥塞的依据就是出现了超时。
3.6.2.2 慢开始
慢开始算法的原理:当主机开始发送数据时,由于不清楚网络的负荷情况,所以如果立即把大量数据字节注入到网络,那么就有可能引起网络发生拥塞。最好,就是先探测下,即由小到大增大发送窗口,也就是由小到大逐渐增大拥塞窗口数值。
在刚开始发送报文段时,设定初始拥塞窗口cwnd为2到4个SMSS(最大报文段 Sender Maximum Segment Size)的数值。后续每收到一个对新的报文段的确认后,可以把拥塞窗口增加最大一个SMSS的数值。即:
拥塞窗口cwnd每次的增加量 = min(N,SMSS)
- N是原先未被确认的,但现在被刚收到的确认报文段所确认的字节数。
用这样的方法逐步增大发送方的拥塞窗口cwnd,可以使分组注入到网络的速率更加合理。
在一开始发送方先设置cwnd=1,发送第一个报文段M1,接收方收到后确认M1。发送方收到M1的确认后,把cwnd增大到2,于是发送方接着发送M2和M3两个报文段。接收方收到后发回对M2和M3的确认。发送方每收到一个对新报文段的确认后,cwnd就从2增大到为4,并可发送M4~M7共4个报文段。因此使用慢开始算法后,每经过一个传输轮次,拥塞窗口cwnd就加倍。
慢开始算法并不是指cwnd增长速率慢,而是指在TCP开始发送时先设置cwnd=1,当慢算法执行到一定时间后,cwnd也会增大的一定的程度。为了防止拥塞窗口cwnd增大到引起网络拥塞,还需要设置一个慢开始门限ssthresh状态变量。
- 当cwnd<ssthresh时,使用慢开始算法
- 当cwnd>ssthresh时,停止使用慢开始算法而改用拥塞避免算法。
- 当cwnd=ssthresh时,即可以使用慢开始算法,也可以用拥塞避免算法。
3.6.2.3 拥塞避免
拥塞避免算法的原理:让拥塞窗口cwnd缓慢地增大,即每经过一个往返时间RTT(传输轮次)就把发送方的拥塞窗口cwnd加1,而不是像慢开始那样加倍增长。因此在拥塞避免阶段就有加法增大(AI)的特点。
这样在拥塞避免阶段,拥塞窗口cwnd按线性规律缓慢增大,比慢开始算法的拥塞窗口增大速率缓慢得多。
3.6.2.4 快重传
有时,个别报文段会在网络中丢失,但实际上网络并未发送拥塞。如果发送方迟迟收不到确认,就会产生超时,就会误认为网络发送拥塞。这就会导致发送方错误地启动慢开始,把拥塞窗口cwnd又设置为1,因而降低了传输效率。
采用块重传算法可以让发送方尽早知道发生了个别报文段的丢失。块重传算法首先要求接收方不要等等自己发送数据时才进行捎带确认,而是要立即发送确认,即使收到了失序的报文段也要立即发出对已收到的报文段的重复确认。
3.6.2.5 块恢复
3.7 TCP连接管理
TCP是面向连接的协议。TCP连接有三个阶段:连接建立,数据传送和连接释放。
TCP连接的建立采用客户服务器方式,主动发起连接建立的应用进程叫做客户,而被动等待连接建立的应用进程叫做服务器。
3.7.1 TCP的连接建立
TCP建立连接的过程叫做握手,握手需要在客户和服务器之间交换三个TCP报文段,其示意图如下:
- 第一次握手:SYN=1,seq=x,进入SYN-SENT状态,等待确认;
- 第二次握手:SYN=1, ACK=1, seq=y, ack=x+1(确认对方的,发送自己),进入SYN-RECV状态;
- 第三次握手:ACK=1, seq=x+1, ack=y+1(确认对方,发送自己),客户端服务器进入ESTABLISHED状态;
3.7.2 TCP的连接释放
TCP连接释放过程比较复杂,其示例如如下:
- 客户端发出连接释放报文,停止发送数据,FIN=1,seq=u(等于前面传过来的最后一个序列号+1,TCP规定FIN报文段即使不携带数据,也消耗一个序号),此时客户端进入FIN-WAIT-1(终止等待1)状态。
- 服务器收到连接释放报文,发出报文确认,ACK=1,ack=u+1,并且带上自己的序列号,seq=v,此时服务器进入CLOSE-WAIT状态,这时候客户端不发了,但是服务端还要发,客户端依然要接收。客户端收到确认请求后,进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文。
- 服务器发送完毕后,向客户端发送连接释放报文,FIN=1,ACK=1,ack=u+1,seq=w,此时,服务器进入LAST-WAIT(最后确认)状态,等待客户端确认。
- 客户端收到连接释放报文,发出报文确认,ACK=1,ack=w+1,自己的序列号仍是seq=u+1,此时,客户端进入TIME-WAIT(时间等待)状态,注意此时TCP连接没有释放,必须等待2MSL(最长报文段寿命)时间后,才会进入CLOSE(关闭)状态。服务器只要收到了确认后,立马进入CLOSED状态。所以服务器结束时间要比客户端早一些。
3.7.3 TCP的有限状态机
TCP连接的各种状态之间的关系如下图所示: