TCP协议全称: 传输控制协议(面向字节流)

Image.png

  • 源端口号: 16位的源端口中包含初始化通信的端口。源端口和源IP地址的作用是标识报文的返回地址。
  • 目的端口号: 16位的目的端口域定义传输的目的。这个端口指明报文接收计算机上的应用程序地址接口。
  • 32位序号字段:TCP是面向字节流的,TCP连接传送的数据流中的每个字节都编上一个序号。序号字段的值指的是本报文段所发送的数据的第一个字节的序号
    • ex:一报文段的序号字段值是301,而携带的数据共有100B,表明本报文段的数据的最后一个字节的序号是400,故下一个报文段的数据序号应从401开始
  • 32位确认序号字段:期望收到对方的下一个报文段的数据的第一个字节的序号。若确认号为N,则表明到序号N-1为止的所有数据都已正确收到
  • 4位首部长度(即数据偏移):表示首部长度,它指出TCP报文段的数据起始处距离TCP报文段的起始处有多远
  • 保留(6位):6位值域,这些位必须是0。为了将来定义新的用途而保留。
  • 标志位:
    • 紧急位URG:URG=1,表示紧急指针字段有效,它高速系统报文段中有紧急数据,应尽快传送(相当于高优先级的数据)。但URG需要和紧急指针配套使用,即数据从第一个字节到紧急指针所指字节就是紧急数据。
    • 确认位ACK:只有当ACK=1时确认号字段才有效。当ACK=0时,确认号无效。TCP规定,在连接建立后所有传送的报文段都必须把ACK置1。
    • 推送位PSH(Push):接受TCP收到PSH=1的报文段,就尽快地交付给接受应用进程,而不再等到整个缓存都填满后再向上交付。
    • 复位位RST(Reset):RST=1时,表明TCP连接中出现严重差错(如主机崩溃或其他原因),必须释放连接,然后再重新建立运输连接
    • 同步位SYN: SYN=1时,表示这是一个连接请求或连接接受报文。
      • 当SYN=1,ACK=0时,表明这是一个连接请求报文,对方若同意建立连接,则在响应报文中使用SYN=1,ACK=1。
    • 终止位FIN(Finish):用来释放一个连接。FIN=1表明此报文段的发送方的数据已发送完毕,并要求释放传输连接
  • 16位窗口字段:指出现在允许对方发送的数据量,接收方的数据缓存空间是有限的,故用窗口值作为接收方让发送方设置其发送窗口的依据,即接收窗口控制发送窗口大小,单位字节。
    • ex:设确认号是701,窗口字段是1000。表明,从701号算起,发送此报文段的一方还有接受1000B数据(字节序号为701~1700)的接收缓存空间
  • 16位校验和:校验和字段检验的范围包括首部和数据两部分。计算校验和时,和UDP一样,要在TCP报文段的前面加上12B的伪首部(只需要将UDP伪首部的第4个字段,即协议字段的17改成6,其他和UDP一样)。
  • 16位紧急指针字段:指出在本报文段中紧急数据共有多少字节(紧急数据放在本报文段数据的最前面)
  • 选项字段:长度可变,TCP最初只规定了一种选项,即最大报文段长度(Maximum Segment Size,MSS)。MSS是TCP报文段中数据字段的最大长度。
  • 填充字段:为了使整个首部长度是4B的整数倍 ```cpp

    include

int main() { return 0; } ```


TCP的连接

Image [1].png

TCP服务器创建TCB,进入LISTEN(监听)状态,准备接受客户端进程的连接请求;客户端进程也创建TCB随后发出连接请求

  1. 客户端:SYN=1,seq=x (x为随机选择起始序号)连接请求报文不携带数据,但要消耗一个序号。(私认为:此时ACK=0,因为当SYN=1,ACK=0时,表示连接请求报文)
  2. 服务器:分配TCP缓存和变量。回复报文中SYN=1,ACK=1,确认号字段ack=x+1,服务器随机产生序号seq=y(确认报文不携带数据,但要消耗一个序号)。
  3. 客户端:分配缓存和变量。ACK=1,seq=x+1,ack=y+1。该报文段可以携带数据,若不携带数据,则不消耗序号

服务器资源在第二次握手分配,而客户端在第三次握手分配,故服务器易收到SYN洪泛攻击

为什么不用两次?
主要是为了防止已经失效的连接请求报文突然又传送到了服务器,从而产生错误。如果使用的是两次握手建立连接,假设有这样一种场景,客户端发送的第一个请求连接并且没有丢失,只是因为在网络中滞留的时间太长了,由于TCP的客户端迟迟没有收到确认报文,以为服务器没有收到,此时重新向服务器发送这条报文,此后客户端和服务器经过两次握手完成连接,传输数据,然后关闭连接。此时之前滞留的那一次请求连接,因为网络通畅了, 到达了服务器,这个报文本该是失效的,但是,两次握手的机制将会让客户端和服务器再次建立连接,这将导致不必要的错误和资源的费。
如果采用的是三次握手,就算是那一次失效的报文传送过来了,服务端接受到了那条失效报文并且回复了确认报文,但是客户端不会再次发出确认。由于服务器收不到确认,就知道客户端并没有请求连接。


TCP连接释放

  1. TCP连接的两个进程中的任何一个都能终止该链接<br />![Image [2].png](https://cdn.nlark.com/yuque/0/2020/png/376635/1592544492141-b5661dca-b5e1-41b0-b1b0-91c90777657e.png#align=left&display=inline&height=358&margin=%5Bobject%20Object%5D&name=Image%20%5B2%5D.png&originHeight=358&originWidth=660&size=37591&status=done&style=none&width=660)
  1. 客户端:FIN=1,seq=u(它等于前面已传送过得数据的最后一个字节的序号加1),FIN报文段不携带数据,也要消耗一个序号。TCP为全双工,发送FIN的一段停止发送数据,但对方还在发送
  2. 服务器:收到连接释放报文段后即发出确认,确认号是ack=u+1,而该报文段自己的序号为seq=v(等于它前面已经传颂过的数据的最后一个字节的序号加1),此时从客户机→服务器连接已释放,TCP处于半关闭状态。服务器若发送数据,客户机仍要接收(即服务器→客户端未关闭)
  3. 服务器:若服务器已经没有要发送的数据,通知TCP释放连接,此时发出FIN=1,此时要有一个seq=w(应该是等于前面已传送过得数据的最后一个字节的序号加1)
  4. 客户端:收到连接释放报文段后,必须发送确认。ACK=1,ack=w+1,seq=u+1,此时TCP连接未释放,必须经过时间等待计时器设置的2*MSL(最长报文段寿命)时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
  5. 服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。

为什么最后客户端还要等待 2*MSL的时间呢?

  • MSL(Maximum Segment Lifetime),TCP允许不同的实现可以设置不同的MSL值。
  • 第一,保证客户端发送的最后一个ACK报文能够到达服务器,因为这个ACK报文可能丢失,站在服务器的角度看来,我已经发送了FIN+ACK报文请求断开了,客户端还没有给我回应,应该是我发送的请求断开报文它没有收到,于是服务器又会重新发送一次,而客户端就能在这个2MSL时间段内收到这个重传的报文,接着给出回应报文,并且会重启2MSL计时器。
  • 第二,防止类似与“三次握手”中提到了的“已经失效的连接请求报文段”出现在本连接中。客户端发送完最后一个确认报文后,在这个2MSL时间中,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样新的连接中不会出现旧连接的请求报文。

为什么建立连接是三次握手,关闭连接确是四次挥手呢?

  • 建立连接的时候, 服务器在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。
  • 而关闭连接时,服务器收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,而自己也未必全部数据都发送给对方了,所以己方可以立即关闭,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送,从而导致多了一次。

如果已经建立了连接, 但是客户端突发故障了怎么办?

  • TCP设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75分钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。

TCP可靠传输

1.序号
TCP把数据视为一个无结构但有序的字节流,序号建立在传送的字节流上,而不建立在报文段上。
TCP为每个字节都编上一个序号,序号字段的值是指本报文段所发送的数据的第一个字节的序号。

Image [3].png

每个ACK都带有对应的确认序号,告诉发送者已收到哪些数据,下次要从哪开始发
ex:客户端向服务器发送了1005字节的数据,服务器返回给客户端的ack=1003,说明服务器只收到了1-1002的数据,此时客户端从1003开始重发

2.确认
TCP默认采用累积确认

3.重传
当发生超时和冗余ACK,会导致TCP重传

  • 超时
    • TCP每发送一个报文段,就对这个报文段设置一次计时器,计时器设置的重传时间到期望但还未收到确认,就重传
    • 缺点:超时周期往往过长
  • 冗余ACK(冗余确认)
    • 连发3个ack

TCP流量控制

TCP提供一种基于滑动窗口协议的流量控制机制。

  • 接收方:根据自己的接收缓存的大小,动态调整发送方的窗口大小,称为接收窗口(rwnd)。通过调整TCP中的“窗口”字段值,来限制发送方向网络注入报文的速率
  • 发送方:根据其对当前网络拥塞程序的估计而确定的窗口值,成为拥塞窗口(cwnd)
  • 发送方窗口的实际大小:min(rwnd,cwnd)

TCP拥塞控制

防止过多的数据注入网络,以防路由器或链路过载。端点不了解拥塞细节,仅表现为通信时延的增加,通过控制发送方速率解决拥塞

一、慢开始和拥塞避免
**Image [4].png

1.慢开始算法

  • “慢开始” 只是指初使时慢, 但是增长速度非常快(指数)。
  • 开始时,先令cwnd=1(一个最大报文长度MSS),每经过一个RTT,cwnd加倍,直到≥ssthresh(慢开始门限)后开始线性增长

2.拥塞避免算法

  • 每经过一个RTT,cwnd就增加一个MSS的大小,而不是加倍,使cwnd按线性缓慢增大。
  • 出现一次超时(网络拥塞),便将ssthresh=cwnd/2

3.网络拥塞的处理

  • 当TCP开始启动的时候, 慢启动阈值等于窗口最大值
  • 在每次超时重发的时候, 慢启动阈值会变成原来的一半, 同时拥塞窗口置回1
  • 注意:在第17次,cwnd=12,而非16

二、快重传和快恢复
Image [5].png

1.快重传
发送方连续收到三个重复的ACK报文时,直接重传,不必等待哪个报文段设置的重传计时器超时

2.快恢复

  • 发送端收到三个ACK时,执行“乘法减小”算法,设置ssthresh=cwnd/2
  • 然后开始拥塞避免算法

延迟应答

如果接收数据的主机立刻返回ACK应答, 这时候返回的窗口可能比较小.
假设接收端缓冲区为1M. 一次收到了500K的数据;
如果立刻应答, 返回的窗口大小就是500K;
但实际上可能处理端处理的速度很快, 10ms之内就把500K数据从缓冲区消费掉了; 在这种情况下, 接收端处理还远没有达到自己的极限, 即使窗口再放大一些, 也能处理过来;
如果接收端稍微等一会儿再应答, 比如等待200ms再应答, 那么这个时候返回的窗口大小就是1M

窗口越大, 网络吞吐量就越大, 传输效率就越高.
TCP的目标是在保证网络不拥堵的情况下尽量提高传输效率;

延迟应答条件:

  • 数量限制: 每隔N个包就应答一次
  • 时间限制: 超过最大延迟时间就应答一次

捎带应答

在延迟应答的基础上, 我们发现, 很多情况下
客户端和服务器在应用层也是 “一发一收” 的
意味着客户端给服务器说了 “How are you”
服务器也会给客户端回一个 “Fine, thank you”
那么这个时候ACK就可以搭顺风车, 和服务器回应的 “Fine, thank you” 一起发送给客户端


面向字节流

  • 创建一个TCP的socket, 同时在内核中创建一个 发送缓冲区 和一个 接收缓冲区;
  • 调用write时, 数据会先写入发送缓冲区中;
    • 如果发送的字节数太大, 会被拆分成多个TCP的数据包发出;
    • 如果发送的字节数太小, 就会先在缓冲区里等待, 等到缓冲区大小差不多了, 或者到了其他合适的时机再发送出去;
  • 接收数据的时候, 数据也是从网卡驱动程序到达内核的接收缓冲区;
  • 然后应用程序可以调用read从接收缓冲区拿数据;

全双工:既有发送缓冲区,也有接收缓冲区

由于缓冲区的存在, 所以TCP程序的读和写不需要一一匹配

  • 写100个字节的数据, 可以调用一次write写100个字节, 也可以调用100次write, 每次写一个字节;
  • 读100个字节数据时, 也完全不需要考虑写的时候是怎么写的, 既可以一次read 100个字节, 也可以一次read一个字节, 重复100次;

粘包问题

首先要明确, 粘包问题中的 “包”, 是指应用层的数据包.
在TCP的协议头中, 没有如同UDP一样的 “报文长度” 字段
但是有一个序号字段.
站在传输层的角度, TCP是一个一个报文传过来的. 按照序号排好序放在缓冲区中.
站在应用层的角度, 看到的只是一串连续的字节数据.
那么应用程序看到了这一连串的字节数据, 就不知道从哪个部分开始到哪个部分是一个完整的应用层数据包.
此时数据之间就没有了边界, 就产生了粘包问题

那么如何避免粘包问题呢?

  • 对于定长的包

保证每次都按固定大小读取即可
例如上面的Request结构, 是固定大小的, 那么就从缓冲区从头开始按sizeof(Request)依次读取即可

  • 对于变长的包

可以在数据包的头部, 约定一个数据包总长度的字段, 从而就知道了包的结束位置
还可以在包和包之间使用明确的分隔符来作为边界(应用层协议, 是程序员自己来定的, 只要保证分隔符不和正文冲突即可)


TCP状态转换

进一步探究TCP三路握手和四次挥手过程中的状态变迁

TCP状态图.png
Image [6].png

上半部分是TCP三路握手过程的状态变迁,下半部分是TCP四次挥手过程的状态变迁。

  1. CLOSED:起始点,在超时或者连接关闭时候进入此状态,这并不是一个真正的状态,而是这个状态图的假想起点和终点。
  2. LISTEN:服务器端等待连接的状态。服务器经过 socket,bind,listen 函数之后进入此状态,开始监听客户端发过来的连接请求。此称为应用程序被动打开(等到客户端连接请求)。
  3. SYN_SENT:第一次握手发生阶段,客户端发起连接。客户端调用 connect,发送 SYN 给服务器端,然后进入 SYN_SENT 状态,等待服务器端确认(三次握手中的第二个报文)。如果服务器端不能连接,则直接进入CLOSED状态。
  4. SYN_RCVD:第二次握手发生阶段,跟 3 对应,这里是服务器端接收到了客户端的 SYN,此时服务器由 LISTEN 进入 SYN_RCVD状态,同时服务器端回应一个 ACK,然后再发送一个 SYN 即 SYN+ACK 给客户端。状态图中还描绘了这样一种情况,当客户端在发送 SYN 的同时也收到服务器端的 SYN请求,即两个同时发起连接请求,那么客户端就会从 SYN_SENT 转换到 SYN_REVD 状态。
  5. ESTABLISHED:第三次握手发生阶段,客户端接收到服务器端的 ACK 包(ACK,SYN)之后,也会发送一个 ACK 确认包,客户端进入 ESTABLISHED 状态,表明客户端这边已经准备好,但TCP 需要两端都准备好才可以进行数据传输。服务器端收到客户端的 ACK 之后会从 SYN_RCVD 状态转移到 ESTABLISHED 状态,表明服务器端也准备好进行数据传输了。这样客户端和服务器端都是 ESTABLISHED 状态,就可以进行后面的数据传输了。所以 ESTABLISHED 也可以说是一个数据传送状态。

上面就是 TCP 三次握手过程的状态变迁。结合第一张三次握手过程图,从报文的角度看状态变迁:SYN_SENT 状态表示已经客户端已经发送了 SYN 报文,SYN_RCVD 状态表示服务器端已经接收到了 SYN 报文。

下面看看TCP四次挥手过程的状态变迁。结合第一张四次挥手过程图来理解。

  1. FIN_WAIT_1:第一次挥手。主动关闭的一方(执行主动关闭的一方既可以是客户端,也可以是服务器端,这里以客户端执行主动关闭为例),终止连接时,发送 FIN 给对方,然后等待对方返回 ACK 。调用 close() 第一次挥手就进入此状态。
  2. CLOSE_WAIT:接收到FIN 之后,被动关闭的一方进入此状态。具体动作是接收到 FIN,同时发送 ACK。之所以叫 CLOSE_WAIT 可以理解为被动关闭的一方此时正在等待上层应用程序发出关闭连接指令。前面已经说过,TCP关闭是全双工过程,这里客户端执行了主动关闭,被动方服务器端接收到FIN 后也需要调用 close 关闭,这个 CLOSE_WAIT 就是处于这个状态,等待发送 FIN,发送了FIN 则进入 LAST_ACK 状态。
  3. FIN_WAIT_2:主动端(这里是客户端)先执行主动关闭发送FIN,然后接收到被动方返回的 ACK 后进入此状态。
  4. LAST_ACK:被动方(服务器端)发起关闭请求,由状态2 进入此状态,具体动作是发送 FIN给对方,同时在接收到ACK 时进入CLOSED状态。
  5. CLOSING:两边同时发起关闭请求时(即主动方发送FIN,等待被动方返回ACK,同时被动方也发送了FIN,主动方接收到了FIN之后,发送ACK给被动方),主动方会由FIN_WAIT_1 进入此状态,等待被动方返回ACK。
  6. TIME_WAIT:从状态变迁图会看到,四次挥手操作最后都会经过这样一个状态然后进入CLOSED状态。共有三个状态会进入该状态
    • 由CLOSING进入:同时发起关闭情况下,当主动端接收到ACK后,进入此状态,实际上这里的同时是这样的情况:客户端发起关闭请求,发送FIN之后等待服务器端回应ACK,但此时服务器端同时也发起关闭请求,也发送了FIN,并且被客户端先于ACK接收到。
    • 由FIN_WAIT_1进入:发起关闭后,发送了FIN,等待ACK的时候,正好被动方(服务器端)也发起关闭请求,发送了FIN,这时客户端接收到了先前ACK,也收到了对方的FIN,然后发送ACK(对对方FIN的回应),与CLOSING进入的状态不同的是接收到FIN和ACK的先后顺序。
    • 由FIN_WAIT_2进入:这是不同时的情况,主动方在完成自身发起的主动关闭请求后,接收到了对方发送过来的FIN,然后回应 ACK

常规流程
常规流程就是指,客户端连接服务器,交互完成后,客户端主动关闭连接

常规状态-客户关.png

服务端主动关闭连接
服务器先调用close关闭连接,客户端被动关闭

服务器关.png

服务端和客户端同时关闭
当服务端和客户端同时调用close时,那么这个TCP连接两端的socket就都进入了FIN_WAIT_1状态

1.如果两端的FIN包也同时到达

同时关.png

2. 如果一方的FIN先到达,假设客户端先收到服务端的FIN

同时关-一个FIN先到.png

  1. 先考虑这样的一个情况,假如这个最后回应的ACK丢失了,也就是服务器端接收不到这个ACK,那么服务器将继续发送它最终的那个FIN,因此客户端必须维护状态信息(TIME_WAIT)允许它重发最后的那个ACK。<br /> 如果没有这个TIME_WAIT状态,客户端处于CLOSED状态(开头就说了CLOSED状态实际并不存在,是我们为了方便描述假想的),那么客户端将响应RST,服务器端收到后会将该RST分节解释成一个错误,也就不能实现最后的全双工关闭了(可能是主动方单方的关闭)。<br /> 所以要实现TCP全双工连接的正常终止(两方都关闭连接),必须处理终止过程中四个分节任何一个分节的丢失情况,那么主动关闭连接的主动端必须维持TIME_WAIT状态,最后一个回应ACK的是主动执行关闭的那端。从变迁图可以看出,如果没有TIME_WAIT状态,我们将没有任何机制来保证最后一个ACK能够正常到达。前面的FINACK正常到达均有相应的状态对应。<br />RST表示复位,用来异常的关闭连接,在TCP的设计中它是不可或缺的。就像上面说的一样,发送RST包关闭连接时,不必等缓冲区的包都发出去(不像上面的FIN包),直接就丢弃缓存区的包发送RST包。而接收端收到RST包后,也不必发送ACK包来确认。

TCP处理程序会在自己认为的异常时刻发送RST包。例如,A向B发起连接,但B之上并未监听相应的端口,这时B操作系统上的TCP处理程序会发RST包。

又比如,AB正常建立连接了,正在通讯时,A向B发送了FIN包要求关连接,B发送ACK后,网断了,A通过若干原因放弃了这个连接(例如进程重启)。网通了后,B又开始发数据包,A收到后表示压力很大,不知道这野连接哪来的,就发了个RST包强制把连接关了,B收到后会出现connect reset by peer错误

  1. 还有这样一种情况,如果目前的通信双方都已经调用了 close(),都到达了CLOSED状态,没有TIME_WAIT状态时:<br />现在有一个新的连接被建立起来,使用的IP地址和端口和这个先前到达了CLOSED状态的完全相同,假定原先的连接中还有数据报残存在网络之中,这样新的连接建立以后传输的数据极有可能就是原先的连接的数据报,为了防止这一点,TCP不允许从处于TIME_WAIT状态的socket 建立一个连接。
  2. 处于TIME_WAIT状态的 socket 在等待了两倍的MSL时间之后,将会转变为CLOSED状态。这里TIME_WAIT状态持续的时间是2MSLMSL是任何IP数据报能够在因特网中存活的最长时间),足以让这两个方向上的数据包被丢弃(最长是2MSL)。通过实施这个规则,我们就能保证每成功建立一个TCP连接时,来自该连接先前化身的老的重复分组都已经在网络中消逝了。<br />MSLMaximum Segment Lifetime英文的缩写,中文可以译为“报文最大生存时间”,他是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。因为tcp报文(segment)是ip数据报(datagram)的数据部分,而ip头中有一个TTL域,TTLtime to live的缩写,中文可以译为“生存时间”,这个生存时间是由源主机设置初始值但不是存的具体时间,而是存储了一个ip数据报可以经过的最大路由数,每经过一个处理他的路由器此值就减1,当此值为0则数据报将被丢弃,同时发送ICMP报文通知源主机。RFC 793中规定MSL2分钟,实际应用中常用的是30秒,1分钟和2分钟等。

2MSL即两倍的MSL,TCP的TIME_WAIT状态也称为2MSL等待状态,当TCP的一端发起主动关闭,在发出最后一个ACK包后,即第3次握手完成后发送了第四次握手的ACK包后就进入了TIME_WAIT状态,必须在此状态上停留两倍的MSL时间,等待2MSL时间主要目的是怕最后一个ACK包对方没收到,那么对方在超时后将重发第三次握手的FIN包,主动关闭端接到重发的FIN包后可以再发一个ACK应答包。在TIME_WAIT状态时两端的端口不能使用,要等到2MSL时间结束才可继续使用。当连接处于2MSL等待阶段时任何迟到的报文段都将被丢弃。

综上来看:TIME_WAIT存在的两个理由就是

  1. 可靠地实现TCP全双工连接的终止;
  2. 允许老的重复分节(数据报)在网络中消逝。

TCP保活机制

在需要长连接的网络通信程序中,经常需要心跳检测机制,来实现检测对方是否在线或者维持网络连接的需要。这一机制是在应用层实现的,对应的,在TCP协议中,也有类似的机制,就是TCP保活机制。

TCP保活的作用

1.探测连接的对端是否存活:在应用交互的过程中,可能存在以下几种情况:

  • 客户端或服务器端意外断电、死机、崩溃、重启
  • 中间网络已经中断,而客户端与服务器端并不知道

利用保活探测功能,可以探知这种对端的意外情况,从而保证在意外发生时,可以释放半打开的TCP连接。

2.防止中间设备因超时删除连接相关的连接表
中间设备如防火墙等,会为经过它的数据报文建立相关的连接信息表,并为其设置一个超时时间的定时器,如果超出预定时间,某连接无任何报文交互的话,中间设备会将该连接信息从表中删除,在删除后,再有应用报文过来时,中间设备将丢弃该报文,从而导致应用出现异常,这个交互的过程大致如下图所示:
TCP保活.png

这种情况在有防火墙的应用环境下非常常见,这会给某些长时间无数据交互但是又要长时间维持连接的应用(如数据库)带来很大的影响,为了解决这个问题,应用本身或TCP可以通过保活报文来维持中间设备中该连接的信息

过程描述

连接中启动保活功能的一端,在保活时间内连接处于非活动状态,则向对方发送一个保活探测报文,如果收到响应,则重置保活计时器,如果没有收到响应报文,则经过一个保活时间间隔后再次向对方发送一个保活探测报文,如果还没有收到响应报文,则继续,直到发送次数到达保活探测数,此时,对方主机将被确认为不可到达,连接被中断。

TCP保活功能工作过程中,开启该功能的一端会发现对方处于以下四种状态之一:

  • 对方主机仍在工作,并且可以到达。此时请求端将保活计时器重置。如果在计时器超时之前应用程序通过该连接传输数据,计时器再次被设定为保活时间值。
  • 对方主机已经崩溃,包括已经关闭或者正在重新启动。这时对方的TCP将不会响应。请求端不会接收到响应报文,并在经过保活时间间隔指定的时间后超时。超时前,请求端会持续发送探测报文,一共发送保活探测数指定次数的探测报文,如果请求端没有收到任何探测报文的响应,那么它将认为对方主机已经关闭,连接也将被断开。
  • 客户主机崩溃并且已重启。在这种情况下,请求端会收到一个对其保活探测报文的响应,但这个响应是一个重置报文段RST,请求端将会断开连接。
  • 对方主机仍在工作,但是由于某些原因不能到达请求端(例如网络无法传输,而且可能使用ICMP通知也可能不通知对方这一事实)。这种情况与状态2相同,因为TCP不能区分状态2与状态4,结果是都没有收到探测报文的响应。

保活机制的弊端
**
理解了上面的实现,就可以发现其存在以下两点主要弊端:

  • 在出现短暂的网络错误的时候,保活机制会使一个好的连接断开;
  • 保活机制会占用不必要的带宽;

所以,保活机制是存在争议的,主要争议之处在于是否应在TCP协议层实现,有两种主要观点:其一,保活机制不必在TCP协议中提供,而应该有应用层实现;其二,认为大多数应用都需要保活机制,应该在TCP协议层实现。