TCP协议精讲
转发自 https://mp.weixin.qq.com/s/5-Mt-IScPJ6KXTwIX_VDbA
01简介
TCP(Transmission Control Protocol 传输控制协议)是一种基于IP的传输层协议,TCP协议面向连接、正面确认与重传、缓冲机制、流量控制、差错控制、拥塞控制,可保证高可靠性(数据无丢失、数据无失序、数据无错误、数据无重复到达)传输层协议。
上图形象展示了TCP协议是基于IP协议的传输层协议,对于IP协议的详解,请看《IP协议详解》。
02 TCP协议头
TCP协议头数据个数如下:
端口号[16bit]
我们知道,网络实现的是不同主机的进程间通信。在一个操作系统中,有很多进程,当数据到来时要提交给哪个进程进行处理呢?这就需要用到端口号。在TCP头中,有源端口号(SourcePort)和目标端口号(DestinationPort)。源端口号标识了发送主机的进程,目标端口号标识接受方主机的进程。端口是由互联网分配号码管理局(IANA)分配的,具体请看《UDP协议详解》。
序号[32bit]
序号分为发送序号(SequenceNumber)和确认序号(AcknowledgmentNumber)。
发送序号:用来标识从TCP源端向TCP目的端发送的数据字节流,它表示在这个报文段中的第一个数据字节的顺序号。如果将字节流看作在两个应用程序间的单向流动,则 TCP用顺序号对每个字节进行计数。序号是32bit的无符号数,序号到达2∧32- 1后又从 0开始。当建立一个新的连接时,SYN标志变1,顺序号字段包含由这个主机选择的该连接的初始顺序号ISN(Initial Sequence Number)。
确认序号:包含发送确认的一端所期望收到的下一个顺序号。因此,确认序号应当是上次已成功收到数据字节顺序号加 1。只有ACK标志为1时确认序号字段才有效。TCP为应用层提供全双工服务,这意味数据能在两个方向上独立地进行传输。因此,连接的每一端必须保持每个方向上的传输数据顺序号。
在wireshark的抓包文件中,Seq表示发送序列号,Ack表示确认序列号。
偏移[4bit]
这里的偏移实际指的是TCP首部的长度,它用来表明TCP首部中32bit字的数目,通过它可以知道一个TCP包它的用户数据是从哪里开始的。这个字段占4bit,如4bit的值是0101,则说明TCP首部长度是5* 4 = 20字节。所以TCP的首部长度最大为15* 4 = 60字节。然而没有可选字段,正常长度为20字节。
Reserved [3bit]
目前没有使用,它的值都为0。注意:在比较旧的资料中显示6bit的保留字节,因为新的TCP协议使用了3个位作为标志,所以只剩下3个保留位。
标志[9bit]
上面说到增加了3位作标志位,增加的是:
NS: "nonce sum"简写。随机和,该标签用来保护不受发送者发送的突发的恶意隐藏报文的侵害。
CWR: "Congestion WindowReduced"简写。拥塞窗口减,发送方降低它的发送速率,发送者在接收到一个带有ECEflag包时,将会使用CWRflag。
ECE: "ECN-Echo"简写。ECN表示ExplicitCongestion Notification(显式拥塞通知),发送方接收到了一个更早的拥塞通告。表示TCPpeer有ECN能力。
其他6个标志位
URG: "urgent"简写。通知接收端处理在处理其他包前优先处理接收到的紧急报文(urgentpackets),紧急指针(urgentpointer)有效。
ACK: "Acknowledgment"简写。表示包已经被成功接收,确认序号有效。
PSH: "push"简写。通知接收端处理接收的报文,而不是将报文缓存到buffer中。
RST:"reset"简写。重置连接标志,用于重置由于主机崩溃或其他原因而出现错误的连接。复位通讯请求,一般表示断开一个连接。我们把含有RST标识的报文称为复位报文段。
SYN:"Synchronisation"简写。表示三次握手建立连接的第一步,在建立连接时发送者发送的第一个包中设置flag值为SYN。我们把含有SYN标识的报文称为同步报文段。
FIN: "finished"简写。表示发送者以及发送完数据,通常用在发送者通知对端,本端即将关闭。我们把含有FIN标识的报文称为结束报文段
注意:他们中的多个可同时被置为1。
窗口大小(window)[16bit]
指的是接收窗口,窗口的大小,表示源方法最多能接受的字节数。
校验和[16bit]
校验和覆盖了整个的TCP报文段:TCP首部和TCP数据。这是一个强制性的字段,一定是由发端计算和存储,并由收端进行验证。
紧急指针[16bit]
只有当URG标志置为1时紧急指针才有效。紧急指针是一个正的偏移量,和序号字段中的值相加表示紧急数据最后一个字节的序号。TCP的紧急方式是发送端向另一端发送紧急数据的一种方式。
TCP选项
长度不定,但长度必须是32bits的整数倍。TCP头部的最后一个选项字段(options)是可变长的可选信息。这部分最多包含40字节,因为TCP头部最长是60字节(其中还包含前面讨论的20字节的固定部分)。典型的TCP选项头部结构如图所示。
选项的第一个字段kind说明选项的类型,有的TCP选项没有后面两个字段,仅包含1字节的kind字段。
第二个字段length(如果有的话)指定该选项的总长度,该长度包括kind字段和length字段占据的2字节。
第三个字段info(如果有的话)是选项的具体信息。
常见的TCP选项有7种,如图所示
1、kind=0,选项表结束(EOP)选项
一个报文段仅用一次。放在末尾用于填充,用途是说明:首部已经没有更多的消息,应用数据在下一个32位字开始处。
2、kind=1,空操作(NOP)选项
没有特殊含义,一般用于将TCP选项的总长度填充为4字节的整数倍。
3、kind=2,最大报文段长度(MSS)选项
TCP连接初始化时,通信双方使用该选项来协商最大报文段长度。TCP模块通常将MSS设置为(MTU-40)字节(减掉的这40字节包括20字节的TCP头部和20字节的IP头部)。这样携带TCP报文段的IP数据报的长度就不会超过MTU(假设TCP头部和IP头部都不包含选项字段,并且这也是一般情况),从而避免本机发生IP分片。对以太网而言,MSS值是1460(1500-40)字节。
4、kind=3,窗口扩大因子选项
TCP连接初始化时,通信双方使用该选项来协商接收窗口的扩大因子。在TCP的头部中,接收窗口大小是用16位表示的,故最大为65535字节,但实际上TCP模块允许的接收窗口大小远不止这个数(为了提高TCP通信的吞吐量)。窗口扩大因子解决了这个问题。
假设TCP头部中的接收通告窗口大小是N,窗口扩大因子(移位数)是M,那么TCP报文段的实际接收通告窗口大小是N*(2^M),或者说N左移M位。注意,M的取值范围是0~14。我们可以通过修改/proc/sys/net/ipv4/tcp_window_scaling内核变量来启用或关闭窗口扩大因子选项。
和MSS选项一样,窗口扩大因子选项只能出现在同步报文段中,否则将被忽略。但同步报文段本身不执行窗口扩大操作,即同步报文段头部的接收窗口大小就是该TCP报文段的实际接收窗口大小。当连接建立好之后,每个数据传输方向的窗口扩大因子就固定不变了。
5、kind=4,选择性确认(SelectiveAcknowledgment,SACK)选项
TCP通信时,如果某个TCP报文段丢失,则TCP会重传最后被确认的TCP报文段后续的所有报文段,这样原先已经正确传输的TCP报文段也可能重复发送,从而降低了TCP性能。SACK技术正是为改善这种情况而产生的,它使TCP只重新发送丢失的TCP报文段,而不用发送所有未被确认的TCP报文段。选择性确认选项用在连接初始化时,表示是否支持SACK技术。我们可以通过修改/proc/sys/net/ipv4/tcp_sack 内核变量来启用或关闭选择性确认选项。
6、kind=5,SACK实际工作的选项
该选项的参数告诉发送方本端已经收到并缓存的不连续的数据块,从而让发送端可以据此检查并重发丢失的数据块。每个块边沿(edgeofblock)参数包含一个4字节的序号。其中块左边沿表示不连续块的第一个数据的序号,而块右边沿则表示不连续块的最后一个数据的序号的下一个序号。这样一对参数(块左边沿和块右边沿)之间的数据是没有收到的。因为一个块信息占用8字节,所以TCP头部选项中实际上最多可以包含4个这样的不连续数据块(考虑选项类型和长度占用的2字节)。
7、kind=8,时间戳选项。
该选项提供了较为准确的计算通信双方之间的回路时间(RoundTrip Time,RTT)的方法,从而为TCP流量控制提供重要信息。我们可以通过修改/proc/sys/net/ipv4/tcp_timestamps内核变量来启用或关闭时间戳选项。
以SYN的TCP选项的MSS为例的wireshark分析,其他的大家可以自行分析。
整个TCP协议头部的wireshark解析。
03 TCP数据包的编号(SEQ)
一个包1400字节,那么一次性发送大量数据,就必须分成多个包。比如,一个10MB 的文件,需要发送7100多个包。
发送的时候,TCP协议为每个包编号(sequencenumber,简称SEQ),以便接收的一方按照顺序还原。万一发生丢包,也可以知道丢失的是哪一个包。
第一个包的编号是一个随机数。为了便于理解,这里就把它称为1号包。假定这个包的负载长度是100字节,那么可以推算出下一个包的编号应该是101。这就是说,每个数据包都可以得到两个编号:自身的编号,以及下一个包的编号。接收方由此知道,应该按照什么顺序将它们还原成原始文件。
这里的编号就是TCP头中的确认号。wireshark显示的Seq和Ack是wireshark重新编号的。
数据包1:发送序号:532420307(1),确认序号:2978637660(1)。数据包长6
数据包2:发送序号:2978637660(1),确认序号:532420313(7)。
备注:括号里是wireshark的编号。
可以发现:
数据包2的发送序号是数据包1的确认序号。
数据包2的确认序号是数据包1的发送序号+6,也就是加上数据包长。
符合上面的文字描述。
04 三次握手建立连接
三次握手建立连接过程:
a.请求端(通常称为客户)发送一个SYN段指明客户打算连接的服务器的端口,以及初始序号(ISN,在这个例子中为1415531521)。这个SYN段为报文段1。
b.服务器发回包含服务器的初始序号的SYN报文段(报文段2)作为应答。同时,将确认序号设置为客户的ISN加1以对客户的SYN报文段进行确认。一个SYN将占用一个序号。
c.客户必须将确认序号设置为服务器的ISN加1以对服务器的SYN报文段进行确认(报文段3)。
这三个报文段完成连接的建立。这个过程也称为三次握手(three-wayhandshake)。
用wirshark抓包如下:
可以看到三次握手确定了双方间包的序号、最大接受数据的大小(window)以及MSS(MaximumSegment Size)。
MSS = MTU - IP头-TCP头,MTU表示最大传输单元,我们在IP头分析的时候会讲到,它一般为1500个字节。IP头和TCP头部带可选选项的时候都是20个字节。这样的话MSS=1500- 20 -20 = 1460。
MSS限制了TCP包携带数据的大小,它的意思就是当应用层向传输层提交数据通过TCP协议进行传输时,如果应用层的数据大于MSS就必须分段,分成多个段,逐个的发过去。这部分内容是不是IP分片,不要和IP分片混淆了,IP分片是IP协议层的数据报分片,这是TCP的分片,IP协议分片详细请看《IP协议详解》。
我们wireshar抓包显示MSS都是1460,这样显示不出来握手的协商机制。假设客户端的MSS是4312,服务器的MSS是1460,那么握手过程中的协商可以下图形象表示。
其中,第1 次和第2 次握手包的TCP 首部包含MSS 选项,互相通知对方网络接口能够适应的MSS 的大小,然后双方会使用较小的MSS 值进行传输。
前面讲解TCP头中flg中就有SYN标志,在wireshark抓包中也有显示。
读到这里,好像一切顺理成章,决定既然互联网“先驱”定义了三次握手建立,那么就是三次握手建立连接。可有些人会有疑问,为什么两次握手不能。
比如A给B东西,
A说:我要和你建立,你准备好了吗?
B说:好的,我准备好了。
A直接把东西给B。
这样的逻辑在生活中好像一点毛病也没有,但其实这样是不行,3次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。
现在把三次握手改成仅需要两次握手,死锁是可能发生的。其实上面有个“坑”,那就是一开始我们限制了A给B东西,但实际的TCP通信中,连接建立了,可以客户端主动和服务器通信,也可以服务器主动和客户端通信,如果两次握手,B收到A的握手申请,发送好的,我准备好了。这时候B在想,A如果收不到怎么办,A到底有没有收到啊,我(B)能不能向A发数据???
所以需要三次握手。
A说:我和你建立,你准备好了吗?
B说:好的,我准备好了。
A说:我知道你准备好了(我也准备好了)。
开始愉快的相互传输数据。
05 四次挥手断开连接
四次挥手断开连接过程:
a.现在的网络通信都是基于socket实现的,当客户端将自己的socket进行关闭时,内核协议栈会向服务器自动发送一个FIN置位的包,请求断开连接。我们称首先发起断开请求的一方称为主动断开方。
b.服务器端收到请客端的FIN断开请求后,内核协议栈会立即发送一个ACK包作为应答,表示已经收到客户端的请求。
c.服务器运行一段时间后,关闭了自己的socket。这个时候内核协议栈会向客户端发送一个FIN置位的包,请求断开连接。
d.客户端收到服务端发来的FIN断开请求后,会发送一个ACK做出应答,表示已经收到服务端的请求。
用wirshar抓包分析如下:
前面讲解TCP头中flg中就有FIN标志,在wireshark抓包中也有显示。
下图类比四次挥手过程:
这里有个问题,如果有同学自己wireshark抓包分析的话(我提供的wireshark文件第一次通信也是这种情况),会发现下面情况:
怎么只有3次挥手,应用程序出问题了?wirshark自行”合并“了?为什么别人抓包就有四次挥手断开?
这跟Wireshark没有关系,跟实现有关。四次挥手,都知道是客户端和服务器之间交互的四个报文,FIN、ACK、FIN、ACK。但抓包来看,却不是每次如教科书说的那样。首先要搞明白这个FIN报文的真正用途,FIN报文用在本端没有数据发送给对方时,关闭从本端到对端的连接。但是并不影响从对方到本端的连接,也就是说本端仍然可以接收对方的数据。即发送通道关闭,接收通道正常。如果对方收到本端FIN报文时,对方的接收通道就会关闭。此时,如果对方也没有数据发给本端,那么对方也会发送FIN给本端,用于关闭从对方到本端的连接,这时候就可能出现ACK和FIN合在一起的情况。当然,如果对方仍然有数据发送,那么就等数据发完,再发FIN来关闭连接,这时候就是四次挥手了。因此,四次挥手变成三次,跟wireshark没关系,跟数据的收发双方才有关系,从这也能看出tcp是双工通信了。现在的很多的实现都是合并在一起,三个过程,主要是为了效率和安全。
TCP 连接必须经过时间2MSL 后才真正释放掉(2MSL的时间的用意 --- 为了保证A 发送的最后一个ACK 报文段能够到达B.防止“已失效的连接请求报文段”出现在本连接中.A在发送完最后一个ACK 报文段后,再经过时间2MSL,就可以使本连接持续的时间内所产生的所有报文段,都从网络中消失.这样就可以使下一个新的连接中不会出现这种旧的连接请求报文段)。
06 TCP可靠性的保证
TCP采用一种名为“带重传功能的肯定确认(positiveacknowledge withretransmission)”的技术作为提供可靠数据传输服务的基础。这项技术要求接收方收到数据之后向源站回送确认信息ACK。发送方对发出的每个分组都保存一份记录,在发送下一个分组之前等待确认信息。发送方还在送出分组的同时启动一个定时器,并在定时器的定时期满而确认信息还没有到达的情况下,重发刚才发出的分组。
下图a表示带重传功能的肯定确认协议传输数据的情况,下图a表示分组丢失引起超时和重传。为了避免由于网络延迟引起迟到的确认和重复的确认,协议规定在确认信息中稍带一个分组的序号,使接收方能正确将分组与确认关联起来。
下图a可以看出,虽然网络具有同时进行双向通信的能力,但由于在接到前一个分组的确认信息之前必须推迟下一个分组的发送,简单的肯定确认协议浪费了大量宝贵的网络带宽。为此, TCP使用滑动窗口的机制来提高网络吞吐量,同时解决端到端的流量控制。
07 滑动窗口技术
TCP的滑动窗口主要有两个作用,一是提供TCP的可靠性,二是提供TCP的流控特性。同时滑动窗口机制还体现了TCP面向字节流的设计思路。
TCP的Window是一个16bit位字段,它代表的是窗口的字节容量,也就是TCP的标准窗口最大为2^16-1=65535个字节。另外在TCP的选项字段中还包含了一个TCP窗口扩大因子,option-kind为3,详细请看上文。
滑动窗口技术是简单的带重传的肯定确认机制的一个更复杂的变形,它允许发送方在等待一个确认信息之前可以发送多个分组。
所以,TCP的滑动窗口的可靠性也是建立在“确认重传”基础上的。
TCP 滑动窗口分为:发送窗口和接收窗口。
发送方的发送缓存内的数据都可以被分为4类:
已发送,已收到ACK
已发送,未收到ACK
未发送,但允许发送
未发送,但不允许发送
其中类型2和3都属于发送窗口。
接收方的缓存数据分为3类:
已接收
未接收但准备接收
未接收而且不准备接收
如下图所示,发送方要发送一个分组序列,滑动窗口协议在分组序列中放置一个固定长度的窗口,然后将窗口内的所有分组都发送出去;当发送方收到对窗口内第一个分组的确认信息时,它可以向后滑动并发送下一个分组;随着确认的不断到达,窗口也在不断的向后滑动。
上面的解释,对于不熟悉滑动窗口的同学,可能看不太明白。下面将详细讲述一下。
上面讲解三次握手建立连接时说到,握手过程中商议了MSS,也就是每一包的数据长度。抓包中也显示的确是1460字节传输的。
但是1460字节不是整数,不方便我们快速计算,下面讲解将MSS假设为1000,这样方面快速理解。
在进行数据传输时,如果传输的数据比较大(大于1000),就需要拆分为多个数据包进行发送。TCP协议需要对数据进行确认后,才可以发送下一个数据包,
从上图中可以看到,发送端每发送一个数据包,都需要得到接收端的确认应答以后,才可以发送下一个数据包。这样一来,就会在等待确认应答包环节浪费时间。为了避免这种情况,TCP引入了窗口概念。窗口大小指的是不需要等待确认应答包而可以继续发送数据包的最大值。
例如,窗口大小为3,数据包的传输如图所示。
从上图中可以看到,发送端发送第一个数据包(1-1000),没有等待对应的确认应答包,就继续发送第二个数据包(1001-2000)和第三个包(2001-3000)。当收到第3个数据包的确认应答包时,会连续发送3个数据包(3001-4000,4001-5000,5001-6000)。当收到第6个数据包的确认应答包时,又会发送3个数据包(6001-7000,7001-8000,8001-9000)。
以这种方式发送,就可以省去多个数据包(第1、2、4、5、7、8个)的确认应答包时间,从而避免了网络的吞吐量的降低。
这样就引出了窗口的概念,窗口大小指的是可以发送数据包的最大数量。建议读到这里,刚才对窗口不太理解的同学,向上翻翻,再理解一下滑动窗口的图示。
那么,此时窗口就通过滑动的方式,向后移动,确保下一次发送仍然可以发送窗口大小的数据包。这样的发送方式被称为滑动窗口机制。设置窗口大小为3,滑动窗口机制原理如图所示。
上图中,每1000 个字节表示一个数据包。发送端同时发送了3个数据包(2001-5000),接收端响应的确认应答包为“下一个发送4001”,表示接收端成功响应了前两个数据包,没有响应最后一个数据包。此时,最后一个数据包要保留在窗口中。
由于窗口大小为3,发送端除了最后一个包以外,还可以继续发送下两个数据包(5001-6000和6001-7000)。窗口滑动到7001 处。
08 窗口滑动的数据重发
在进行数据包传输时,难免会出现数据丢失情况。这种情况一般分为两种。
第一种,如果未使用滑动窗口机制,发送的数据包没有收到确认应答包,那么数据都会被重发;如果使用了滑动窗口机制,即使确认应答包丢失,也不会导致数据包重发。
第二种,发送的数据包丢失,将导致数据包重发。
下面详细介绍使用滑动窗口机制的两种情况。
确认应答包丢失
这种情况指的是前面发送的数据包没有收到对应的确认应答。当收到后面数据包的确认应答包,表示前面的数据包已经成功被接收端接收了,发送端不需要重新发送前面的数据包了。如图所示。
下面分为5 部分对上图进行讲解。
发送端第1 次发送数据包:这里设置的窗口大小为3,可以最大发送3 个数据包。发送端同时发送3 个数据包1-1000、1001-2000和2001-3000。
接收端返回确认应答包:接收端接收到这些数据,并给出确认应答包。数据包1-1000 和数据包2001-3000 的确认应答包没有丢失,但是数据包1001-2000 的确认应答包丢失了。
发送端第2 次发送数据包:发送端收到接收端发来的确认应答包,虽然没有收到数据包1001-2000 的确认应答包,但是收到了数据包2001-3000 的确认应答包。判断第一次发送的3 个数据包都成功到达了接收端。再次发送3 个数据包3001-4000、4001-5000和5001-6000。
接收端返回确认应答包:接收端接收到这些数据,并给出确认应答包。数据包3001-4000 和数据包4001-5000 的确认应答包丢失了,但是数据包5001-6000 没有丢失。
发送端第3 次发送数据包:发送端收到接收端发来的确认应答包,查看到数据包5001-6000 收到了确认应答包。判断第2 次发送的3 个数据包都成功到达了接收端。再次发送3 个数据包6001-7000、7001-8000和8001-9000。
发送数据包丢失
这种情况指的是发送端发送的部分数据包没有达到接收端。那么,如果在接收端收到的数据包,不是本应该要接收的数据包,那么就会给发送端返回消息,告诉发送端自己应该接收的数据包。
如果发送端连续收到3 次这样的数据包,就认为该数据包成功发送到接收端,这时就开始重发该数据包。如图所示。
下面分为7 部分对上图进行讲解。
发送端发送数据包:这里窗口大小为4,发送端发送4 个数据包,分别为1-1000、1001-2000、2001-3000和3001-4000。
接收端返回确认应答包:接收端接收到这些数据,并给出确认应答包。接收端收到了数据包1-1000,返回了确认应答包;收到了数据包1001-2000,返回了确认应答包;但是数据包2001-3000,在发送过程中丢失了,没有成功到达接收端。数据包3001-4000 没有丢失,成功到达了接收端,但是该数据包不是接收端应该接收的数据包,数据包2001-3000 才是真正应该接收的数据包。因此收到数据包3001-4000 以后,接收端第一次返回下一个应该发送2001 的数据包的确认应答包。
发送端发送数据包:发送端仍然继续向接收端发送4 个数据包,分别为4001-5000、5001-6000、6001-7000和7001-8000。
接收端返回确认应答包:接收端接收到这些数据,并给出确认应答包。当接收端收到数据包4001-5000 时,发现不是自己应该接收的数据包2001-3000,第二次返回下一个应该发送2001 的数据包的确认应答包。当接收端收到数据包5001-6000 时,仍然发现不是自己应该接收的数据包2001-3000,第三次返回下一个应该发送2001 的数据包的确认应答包。以此类推直到接收完所有数据包,接收端都返回下一个应该发送2001 的数据包的确认应答包。
发送端重发数据包:发送端连续3 次收到接收端发来的下一个应该发送2001 的数据包的确认应答包,认为数据包2001-3000 丢失了,就进行重发该数据包。
接收端收到重发数据包:接收端收到重发数据包以后,查看这次是自己应该接收的数据包2001-3000,并返回确认应答包,告诉发送端,下一个该接收8001 的数据包了。
发送端发送数据包:发送端收到确认应答包后,继续发送窗口大小为4 的数据包,分别为8001-9000、9001-10000、10001-11000和11001-12000。
09 TCP流控制
在使用滑动窗口机制进行数据传输时,发送方根据实际情况发送数据包,接收端接收数据包。但是,接收端处理数据包的能力是不同的。
- 如果窗口过小,发送端发送少量的数据包,接收端很快就处理了,并且还能处理更多的数据包。这样,当传输比较大的数据时需要不停地等待发送方,造成很大的延迟。
- 如果窗口过大,发送端发送大量的数据包,而接收端处理不了这么多的数据包,这样,就会堵塞链路。如果丢弃这些本应该接收的数据包,又会触发重发机制。
- 为了避免这种现象的发生,TCP提供了流控制。所谓的流控制就是使用不同的窗口大小发送数据包。发送端第一次以窗口大小(该窗口大小是根据链路带宽的大小来决定的)发送数据包,接收端接收这些数据包,并返回确认应答包,告诉发送端自己下次希望收到的数据包是多少(新的窗口大小),发送端收到确认应答包以后,将以该窗口大小进行发送数据包。
TCP 流控制过程如图所示。
为了方便讲解,将上图以发送端发送数据包进行分隔,将其分为3 部分进行讲解。
第一部分
发送端根据当前链路带宽大小决定发送数据包的窗口大小。这里,窗口大小为3,表示可以发送3 个数据包。因此发送端发送了3 个数据包,分别为1-1000、1001-2000和2001-3000。
接收端接收这些数据包,但是只能处理2 个数据包,第3 个数据包2001-3000 没有被处理。因此返回确认应答包,设置窗口大小为2,告诉发送端自己现在只能处理2 个数据包,下一次请发送2 个数据包。
第二部分
发送端接收到确认应答包,查看到接收端返回窗口大小为2,知道接收端只处理了2 个数据包。发过去的第3 个数据包2001-3000 没有被处理。这说明此时接收端只能处理2 个数据包,第3 个数据包还需要重新发送。
因此发送端发送2 个数据包2001-3000 和3001-4000。接收端收到这两个数据包并进行了处理。此时,还是只能处理2 个窗口,继续向发送端发送确认应答包,设置窗口为2,告诉发送端,下一个应该接收4001 的数据包。
第三部分
发送端接收到确认应答包,查看到接收端返回窗口大小为2。说明接收端接收了上次发送的2 个数据包。此时仍然可以处理2 个数据包,继续发送数据包4001-5000 和5001-6000。
如果在接收端返回的确认应答包中,窗口设置为0,则表示现在不能接收任何数据。这时,发送端将不会再发送数据包,只有等待接收端发送窗口更新通知才可以继续发送数据包。
如果这个更新通知在传输中丢失了,那么就可能导致无法继续通信。为了避免这样的情况发生,发送端会时不时地发送窗口探测包,该包仅有1个字节,用来获取最新的窗口大小的信息。
原理如图所示。
下面介绍上图所示的获取窗口更新数据包的原理。
发送端发送数据。发送端以窗口大小为2,发送了2 个数据包,分别为4001-5000 和5001-6000。接收端接收到这些数据以后,缓冲区满了,无法再处理数据,于是向发送端返回确认应答包,告诉它下一个接收6001 的数据,但是现在处理不了数据,先暂停发送数据,设置窗口大小为0。
发送端暂停发送数据。发送端收到确认应答包,查看到下一次发送的是6001 的数据,但窗口大小为0,得知接收端此时无法处理数据。此时,不进行发送数据,进入等待状态。
3)接收端发送窗口大小更新包。当接收端处理完发送端之前发来的数据包以后,将会给发送端发送一个窗口大小更新包,告诉它,此时可以发送的数据包的数量。这里设置窗口大小为3,表示此时可以处理3 个数据包,但是该数据包丢失了,没有发送到发送端。
发送端发送窗口探测包。由于窗口大小更新包丢失,发送端的等待时间超过了重发超时时间。此时,发送端向接收端发送一个窗口探测包,大小为1 字节,这里是6001。
接收端再次发送窗口大小更新包。接收端收到发送端发来的探测包,再次发送窗口大小更新包,窗口大小为3。
发送端发送数据。发送端接收到窗口大小更新包,查看到应该发的是6001 的数据包,窗口大小为3,可以发送3 个数据包。因此发送了数据包,分别为6001-7000、7001-8000和8001-9000。
10 网线“断”了怎么办
对于TCP链接来说,他们之间一旦建立了连接,那么可以一直没有消息通讯。TCP连接的双方都没有向对方发送数据,则在两个TCP模块之间不交换任何信息。
只要两端的主机没有被重启,则连接依然保持建立,不管中间路由器可以崩溃和重启,还是电话线被挂断再连通。这意味着我们可以启动一个客户与服务器建立一个连接,然后离去数小时、数天、数个星期或者数月,而连接依然保持。
这对于客户端来说,倒还好一点,毕竟不会有那么多的连接被占用,对于服务器来说,就是一个很糟糕的事情,这种连接无疑是一种僵尸连接,平白无辜的占用着服务器的资源,一旦这种连接非常多,服务器往往会因为连接数量的限制,导致没有办法接入新的客户端。
这个时候,其实就需要一种定时探测对端连接是否还存活的机制存在,如此以来彼此都能知道对方的状态,是否还能继续使用。
这种机制,对于TCP来说,就是TCP的保活机制。TCP还设有一个保活计时器,服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次,俗称“心跳”。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。
TCP具有保活器,但我建议在应用层最好还要设计一个“心跳”用来维持TCP连接,时间间隔可自行确定。再插一嘴,具有保活器的TCP就是长连接。
*长连接:建立一个连接,多个请求复用这个连接,一直用同一个链接传输数据,最后再关闭连接。
短连接:建立一个连接,传输一个请求,发送完数据后就关闭连接。*
TCP具有保活器优点:
1.在连接两个端系统的网络出现临时故障的时候,保活选项会引起一个 实际上很好的连接终止。例如,如果在一个中间路由器崩溃并重新启动时发送保活探查,那么TCP会认为客户的主机已经崩溃,而实际上所发生的并非如此。
2.保活功能主要是为服务器应用程序提供的。服务器应用程序希望知道客户主机是否崩溃,从而可以代表客户使用资源,及时回收这些资源。
TCP具有保活器缺点:
保活并不是TCP规范中的一部分。HostRequirements RFC提供了3个不使用保活定时器的理由:
1)在出现短暂差错的情况下,这可能会使一个非常好的连接释放掉;
2)它们耗费不必要的带宽;
3)在按分组计费的情况下会在互联网上花掉更多的钱。