当前位置: 首页 > article >正文

TCP协议原理

TCP协议原理

本篇介绍

前面已经基本介绍了TCP编程的接口以及基本的步骤,但是并没有其中的原理进行解释。本篇主要聚焦于TCP原理部分,对TCP中重要的内容进行解释

TCP协议报格式

基本示意图如下:

在这里插入图片描述

下面针对每一个字段的作用进行简要的概括:

  1. 16位源端口号和16位目的端口号:作用和UDP一样,因为二者都是传输层协议,需要将从下层收到的内容解包分用给上层应用层,为了找到是哪一个应用,就需要使用到端口号
  2. 32位序号和32位确认序号:二者的作用就是为了实现数据报按序到达以及确认应答机制(在接下来会具体讲述)
  3. 4位首部长度:这个部分标记了TCP数据报+选项一共占用多少字节,但是如果4位二进制最大只能表示到16,所以如果单位是1字节,那么连最基本TCP字段都无法完整描述,更何况还要包括选项,所以实际上这里的单位是4字节,也就是说,4位二进制表示的值需要再乘以4,例如当前为8(0100),那么实际上表示的值就是 4 × 8 = 32 4 \times 8 = 32 4×8=32字节,而根据TCP基本字段大小规定是20字节,所以可以推出此时选项占12字节
  4. 6位标志位:其作用是表示当前数据报的特点,这一点会在连接管理部分具体讲述
  5. 16位窗口大小:这里规定了每一次TCP数据报可以发送的数据大小,这个值会随着接收方接收缓冲区的能力改变,具体内容会在滑动窗口机制部分具体讲述
  6. 16位校验和:用于校验数据传输完整性字段,发送端填充,进行CRC校验,如果接收端校验不通过,则认为数据有问题。注意此处的检验和不光包含TCP首部,也包含TCP数据部分
  7. 16位紧急指针:用于标记数据部分中紧急数据的具体位置

TCP中的机制

TCP三大特点中其中一个特点就是可靠性,那么TCP是如何实现可靠性的?实际上TCP实现可靠性采用了很多的方案,下面针对以下几点对如何实现可靠性进行分析:

  1. 确认应答机制
  2. 序号机制
  3. 流量控制
  4. 连接管理
  5. 超时重发
  6. 拥塞控制

但是保证可靠性的同时还需要确保效率,所以在上面机制的基础之上还有一些确保效率的机制:

  1. 捎带应答机制
  2. 延迟应答机制
  3. 快速重传机制
  4. 滑动窗口机制

确认应答机制

前面在介绍UDP时了解到,UDP只处理发送,但是数据是否到达目标主机无法确定,这就导致了UDP本身是不可靠的,而TCP为了解决这个问题就引入了确认应答机制。所谓确认应答,就是发送方需要通过接收方成功接收数据后发送一条确认应答报头来确认自己发送的数据已经正常到达接收方。例如以客户端为发送方,服务端为接收方为例,基本过程如下:

在这里插入图片描述

通过确认应答机制,客户端就知道了服务端已经收到了消息

但是,确认应答机制最大的问题就是最新的一条数据总是无法保证对方是否收到,如果按照上面的方式,那么必然会出现服务端给客户端应答,客户端收到了服务端的应答后又需要给服务端应答,周而复始,导致二者一直处于交换确认应答的状态。出现这个问题的本质还是因为网络传输本质上是长距离传输,所以无法100%确保自己发送的消息被对方收到

那么如果解决上面的问题呢?实际上,从上面的过程中可以看到,客户端给服务端发送消息,客户端作为发送方,服务端作为接收方,只有客户端需要保证自己的数据被接收方收到即可,而服务端完全不需要担心自己的数据是否被对方收到,反之,如果服务端是发送方,客户端是接收方,那么客户端就不需要关心自己的确认消息被服务端收到。这样一来,发送方只要收到了数据,在不考虑异常情况下就一定会收到确认消息,在确认应答机制中,接收方给发送方的应答是对接收到发送方数据的确认(即新应答是对上一次数据的确认),一旦发送方只要收到了确认消息,就代表数据发送成功,这样发送方就可以继续发送消息了。而因为TCP是全双工的,客户端给服务端成功发送消息(即客户端收到确认应答),服务端给客户端成功发送消息(即服务端收到确认应答),这样两个方向上都确保自己发送的数据被对方收到,也就意味着两个方向上都保证了可靠性,所以说TCP是可靠的

捎带应答机制

捎带应答机制是对确认应答机制的一种补充,在确认应答机制中,接收方每一次都只给客户端发送一条确认消息,如果接收方此时又作为发送方,那岂不是要发两次消息,第一次发确认消息,第二次发数据?实际上,大部分情况下,服务端不会只给客户端响应一个确认消息,因为确认消息本身也是一个完整的TCP报头,是TCP报头就可以携带数据,这样一来,只需要在发送确认消息的同时携带数据就可以尽可能保证使用一次发送既实现了确认又实现了数据传输。这便是捎带应答机制

同样,如果原来的接收方发送了数据给原来的发送方,此时接收方就变成了发送方,发送方就变成了接收方,原来的发送方此时再发送数据就需要携带确认消息和数据一起给原来的接收方

序号机制

了解完确认应答机制后,接下来介绍一下TCP如何确保指定的消息有对应的回应

首先,如果只使用前面的确认应答机制,那么为了发送方为了保证自己发送的消息被接收方收到,就必须等待接收方应答才能继续发送下一条消息,但是这样的话就会导致整个客户端和服务端之间的通信都是同步的,从而影响到了整体的传输效率。所以,针对这个问题,最直接的解决方案就是让发送方尽管去发数据,接收方接收到数据后依次应答即可。但是这里存在一个很大的问题,发送方怎么知道接收方发送的应答是对哪一条数据的确认?所以,为了解决这个问题,在TCP报头中存在两个字段:32位序号和32位确认序号

在发送方发送数据之前,先使用32位序号对发送的数据进行编号,这样一来,一是可以确保发送的数据可以是有序的,二是可以确保接收方收到数据时可以根据序号判断是否存在丢包导致的不连续数据,接着接收方收到数据后,向发送方应答当前收到的数据的序号+1代表确认序号,这个确认序号准确来说不是对当前数据的应答,而是对当前数据及之前的所有数据进行应答

有了这一点,假设现在有1000,2000,3000,4000四个数据,依次将其发送给接收方,那么接收方应答时的确认序号只需要写入4001即可

但是,这个序号具体如何确定呢?可以这样理解:将TCP发送缓冲区看做一个char类型的数组,每填充该数组中的一个元素就对序号+1,形成序号和数组下标+1的一一对应,这样序号的值就代表了最后一个数据在缓冲区的位置

但是现在还有一个问题,为什么在TCP报头中不只使用一个序号而需要一个序号和一个确认序号呢?这就是因为捎带应答机制的存在,原来的发送方可能要作为新的接收方,原来的接收方可能要作为新的发送方,如果只使用一个序号就无法处理这个问题

超时重传机制

前面的确认应答机制是建立在双方以及网络一切正常时的分析,但是如果存在网络波动甚至丢包的情况,此时接收方可能就无法接收到完整的数据或者根本无法接收到数据。对这个问题,一共存在两种情况:

  1. 发送方发送数据,但是数据在没有到达接收方时产生了丢包,如下图所示:

    在这里插入图片描述

  2. 发送方发送数据,接收方接收到了数据并做出应答,但是应答在传输过程中丢包,如下图所示:

    在这里插入图片描述

对于上面两种情况,对于发送方来说无法分辨到底数据有没有被接收方接收,所以只能使用一种方法处理,即超时重传。超时重传直接点就是超过一段时间后发送方重新发送相同的数据

但是这里存在一个问题:如何确定超时时间?最理想的情况就是可以找到一个恰好或者最小的时间,保证在这段时间内应答刚好能够返回,但是实际上因为网络或者接收方的问题,这个时间一般都是不同的,如果给定一个长时间,那么整体的传输效率就会降低,如果给定一个短时间,可能会出现频繁发送重复的数据,所以TCP为了保证无论在任何环境下都能比较高性能的通信,因此会动态计算这个最大超时时间

在现在的操作系统中,超时以500ms为一个单位进行控制,每次判定超时重发的超时时间都是500ms的整数倍,并且倍数乘指数形式递增。例如如果重发一次之后,仍然得不到应答,等待 2 × 500 m s 2 \times 500ms 2×500ms后再进行重传,如果仍然得不到应答,等待 4 × 500 m s 4 \times 500ms 4×500ms进行重传,累计到一定的重传次数,TCP认为网络或者对端主机出现异常,强制关闭连接

延迟应答机制

有的时候,可能服务端并没有数据要发送,但是又需要给客户端做出应答,此时如果对每一个立即对每个收到的数据发送ACK确认,整体的传输效率就会比较低,所以,为了尽可能保证效率,TCP会考虑延迟一段时间,再发送ACK。在Linux下,这个延迟时间最小是40ms,最大是200ms

延迟应答机制有三种应答方式:

  1. 等待200ms接收方没有数据需要发送,直接发送ACK
  2. 等待时间在200ms之内存在数据需要发送,将数据和ACK立即发送
  3. 等待时间在200ms之内收到新的数据,直接返回ACK

连接机制

有了确认应答机制之后,TCP就可以基于这个机制对整个过程进行更细致的描述。前面提到过,TCP的其中一个特点是有连接的,所谓的有连接就是存在对应的连接结构,每一个连接结构都会被操作系统进行管理,本次不会具体谈论到操作系统如何对这些连接进行管理,而是针对TCP的连接过程进行叙述

在TCP中,客户端和服务端为了可以进行通信,必须先要建立连接,此时就需要进行三次握手,一旦连接创建完成后就会形成对应的连接结构,因为一开始创建了连接,那么最后不再通信时必须要断开连接,此时就需要进行四次挥手,四次挥手结束后,操作系统就会释放对应的连接结构,基本示意图如下:

在这里插入图片描述

在客户端和服务端建立连接的前两步过程中,因为也属于发送方给接收方发送信息,但是这个信息不能携带任何数据,所以为了让接收方知道是要建立连接,就需要使用到SYN(Synchronize)标志位(TCP报文的SYN标记置为1,后续没有具体说明都是具体标记位为1的TCP报文),表示同步连接,服务端接收到客户端的连接请求后会对信息进行应答并向客户端发送建立连接的信息,此时就需要使用到捎带应答,即SYNACK(Acknowledge),一旦客户端收到了服务端的应答,客户端就进入了连接建立成功的状态

完成上面两步后,客户端就可以向服务端发送数据,但是此处先不考虑携带数据的情况,一旦客户端连接建立成功之后就会向服务端发送应答ACK。最后,服务端接收到客户端的ACK后就会进入连接建立成功状态。此时就完成了三次握手阶段

在断开连接时,考虑客户端请求断开连接,首先客户端向服务端请求断开连接,此时客户端会向服务端发送FIN(Finish)标记位为1的TCP报文,服务端接收到该请求后向客户端应答ACK,此时客户端就进入等待状态,但是服务端还没有完全断开连接,因为刚才只是客户端请求断开连接,所以接下来就是服务端向客户端发送请求断开连接,同样,服务端会向客户端发送FIN,客户端收到该报文后应答服务器ACK,此时客户端从最后的等待状态进入断开状态,服务端接收到最后一次ACK后正式断开

下面,结合前面的TCP编程过程在具体细化三次握手:

在这里插入图片描述

从上图可以看出,调用acceptlisten实际上并没有参与到三次握手阶段,这两个接口实际上完成的任务就是启动三次握手,而客户端的listen在客户端收到服务器的应答之后返回,accept在服务端接收到客户端的应答之后返回

完成上面的步骤之后,三次握手就结束了

接着是四次挥手,四次挥手分别涉及到一些状态标识,以下图为例:

在这里插入图片描述

在上图中,当客户端请求断开连接时首先进入的状态就是FIN_WAIT1,一旦服务端应答,服务端进入CLOSE_WAIT状态,当客户端接收到ACK,此时客户端就会进入FIN_WAIT2。接着,因为TCP是全双工的,除了客户端需要和服务端断开连接外,服务端也要和客户端断开连接,所以接着服务端需要调用close向客户端发送断开连接,一旦资源关闭,那么服务端的状态就会由CLOSE_WAIT转换为LAST_ACK,客户端接收到断开应答后,进入TIME_WAIT状态,一旦服务端接收到ACK,服务端的状态就转换为CLOSED,过一段时间后客户端彻底断开,状态转换为CLOSED。至此,四次挥手结束

整体过程的示意图如下:

在这里插入图片描述

上面已经基本介绍了三次握手和四次挥手的过程,但是考虑下面的问题:

  1. 为什么是三次握手,而不是四次握手
  2. 四次挥手中,如果服务端没有关闭资源,是否会一直保持在CLOSE_WAIT状态
  3. 四次回收中,如果服务端提前退出,会出现什么现象

考虑第一个问题:实际上,三次握手本质也还是四次握手,只不过利用到了捎带应答机制,即发送SYN的同时也发送ACK。这里就衍生出一个新的问题,为什么四次挥手不能是三次挥手,实际上,虽然标准规定的是四次挥手,但是大部分情况下还是会变成三次挥手

首先,不能变成四次挥手的原因是因为有时关闭连接可能存在一方还在发送或者接收数据,例如服务端在收到客户端的FIN后,可能还有未完成的数据需要传输给客户端,因此服务端必须先单独发送ACK确认客户端的关闭请求,然后等待自己的数据发送完毕后,再单独发送FIN包。而大部分情况下会变成三次挥手是因为两点:

  1. 没有剩余数据需要发送
  2. TCP默认开启的延迟应答机制

如果关闭TCP默认开启的延迟应答机制,就可以触发TCP的四次挥手,这里不具体演示,下面给出关闭方法:

// 关闭延迟应答
int quickack = 1;
setsockopt(sockfd, IPPROTO_TCP, TCP_QUICKACK, &quickack, sizeof(int));

需要注意的是,TCP_QUICKACK选项是一次性的,每次收到数据后(在read返回0时)都需要重新设置。如果需要持续禁用延迟应答,可以在每次读操作后重新设置此选项

但是,一般不需要关闭延迟应答,毕竟是四次挥手变成三次挥手是为了提高效率,关闭反而降低了效率

考虑第二个问题:如果服务端在客户端请求关闭响应了ACK,客户端状态变为FIN_WAIT2时,服务端没有关闭对应的资源(例如文件描述符)就会导致服务端持续保持CLOSE_WAIT状态,此时也称为资源泄露问题

以前面网络计算器的代码为例,删除read_write_msg最后的close(ac_socketfd)一行:

// 读写函数
void read_write_msg(struct sockaddr_in peer, int ac_socketfd)
{
    // ...

    // 删除下面一行
    // close(ac_socketfd);
}

分别编译执行客户端和服务端,再立即终止客户端,使用netstat查看服务端状态:

在这里插入图片描述

如果启动和终止多次客户端,再使用netstat查看服务端状态:

在这里插入图片描述

从上面的结果可以验证服务端如果不关闭资源,的确会导致服务端持续保持CLOSE_WAIT状态

考虑第三个问题:如果服务端提前退出,那么根据四次挥手的过程,此时就是服务端向客户端发送断开请求,此时服务端就会进入FIN_WAIT2状态,当客户端断开后,此时进入第二次挥手阶段因为客户端会关闭对应的文件描述符,所以客户端会将进入LAST_ACK状态,此时双方就进入了第二次挥手阶段,导致客户端就进入了TIME_WAIT状态,这种状态下因为当前端口还在被占用,这就导致了服务器想再使用同一个端口启动就会出现绑定失败的问题

在这里插入图片描述

解决这个问题的方式很简单,只需要在绑定之后通过setsockopt设置端口可重用即可:

// 设置端口重用
int reuse = 1;
setsockopt(socketfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int))

需要注意,这个设置必须在bind函数之前执行

但是,如果不考虑开启端口可重用一般需要等待多久才能再次使用同一个端口呢?这就涉及到TIME_WAIT的时间,一般这个时间被设置为了2MSL,在Linux下可以使用下面的命令查看一个MSL的值:

cat /proc/sys/net/ipv4/tcp_fin_timeout

在Ubuntu下,这个值即为60,但是实际上,MSL在RFC1122中规定为两分钟。但是为什么这个值需要是2MSL,实际上MSL是TCP报文的最大生存时间,因此TIME_WAIT持续存在2MSL的话,就能保证在两个传输方向上的尚未被接收或迟到的报文段都已经消失(否则服务器立刻重启, 可能会收到来自上一个进程的迟到的数据(游离报文), 但是这种数据很可能是错误的),同时也是在理论上保证最后一个报文可靠到达

流量控制

所谓流量控制,就是根据接收方的接收缓冲区能力决定当前可以发送数据的大小,如果接收方的接收缓冲区无法接受大量的数据时,那么此时发送方就会减少下一次的发送流量,防止出现接收方丢弃报文的情况,如果接收方的接收缓冲区可以接收大量数据时,那么此时发送方就会增加下一次的发送流量,保证最大程度上的传输效率

如果接收方缓冲区剩余空间为0,此时发送方就会阻塞在写端,直到接收方缓冲区重新有空间,但是这个判断并不是在TCP数据中传递,而是通过TCP进行窗口探测(发送方发送)和窗口更新通知(接收方发送)来决定,这里一定要清楚TCP缓冲区的作用:缓冲区只是保存数据,并不会保存TCP报头信息,所以缓冲区为0不代表不能接受裸的TCP报头

滑动窗口机制

在TCP字段中存在一个字段:16位窗口大小。这个值流量控制机制的核心,它表示接收方当前能够接收的最大数据量(以字节为单位)

在发送方的缓冲区中实际上可以形成三部分区域,如下图所示:

在这里插入图片描述

其中的可发送区域就是滑动窗口所在的区域,所谓的滑动窗口就是由可发送区域的起点和可发送区域的终点围成一个一个范围区域:

在这里插入图片描述

而这个范围会随着接收方缓冲区的大小的改变而改变(此观点暂时如此,在接下里的拥塞控制中会纠正),而发送方想要知道接收方缓冲区的大小就需要接收方将发送缓冲区的值填写到16位窗口大小,在下一次应答时由发送方读取并动态调整滑动窗口的大小

根据上面的原理,下面提出三个问题:

  1. 为什么叫滑动窗口
  2. 滑动窗口的移动方向如何
  3. 滑动窗口移动过程中会不会越界

考虑第一个问题:之所以叫滑动窗口是因为滑动窗口的起点是根据确认序号来决定的,前面提到发送方收到来自接受方的确认序号时会判断出确认序号之前的内容全部成功接收,既然已经成功接收了数据,那么当前位置的数据就可以被覆盖(删除)了,但是只确定了起点而不改变终点,那么只是在缩小滑动窗口的大小范围,整体还是“静止”的,而终点的确定就由接收到的接收方缓冲区大小+起点来决定,所以滑动窗口的大小=终点-起点。在接下来的一次又一次的更新中,不断更新起点和终点覆盖待发送数据,体现出的效果就是整个窗口在滑动

考虑第二个问题:如果左边为已发送数据,右边为待发送数据,那么滑动窗口的移动方向只能是从左往右,因为待发送的数据在最右边

考虑第三个问题:滑动窗口移动过程中不会越界,因为如果到达了缓冲区的最大值,那么此时就会回到缓冲区的最开始继续从右向左滑动,即形成一个环形结构

前面提到,缓冲区可以想象成一个char类型的数组,所以整体的示意图如下:

在这里插入图片描述

上面讨论的都是不存在丢包的情况,假如说丢包了,滑动窗口又是如何处理的呢?

在滑动窗口中,丢包存在三种情况:

  1. 最左侧的数据出现丢包
  2. 中间部分的数据出现丢包
  3. 最右侧的数据出现丢包

考虑第一种情况,如果一次发送四个数据,分别是1-1000,1001-2000,2001-3000,3001-4000,那么最左侧丢包就是1-1000丢包,此时根据确认序号的特点:确认序号之前的内容全部成功接收,因为第一个数据就已经丢了,所以接收方就必须返回1,代表1-1000这个数据需要重传,如下图所示:

在这里插入图片描述

对应到滑动窗口中如下图所示:

在这里插入图片描述

这种情况下,因为客户端只能确定1-1000这个数据丢了,但是不能确定后面三个数据是否正常到达,所以首先要做的事情就是依次传输上面四个数据,每一次都判断确认序号,直到全部确认序号为4001为止

考虑第二种情况,还是发送上面三个数据,如果丢失的数据是1001-2000,此时确认序号为1001,所以滑动窗口的起点可以向后移动到1001,因为1001-2000丢包,所以发送方只能确定1001-2000需要重传,此时1001-2000又变为了滑动窗口最左侧数据,执行第一种情况的逻辑(不考虑接收方接受缓冲区大小改变的情况),如下图所示:

在这里插入图片描述

考虑第三种情况,即3001-4000的数据丢失,前三个数据正常传送,此时接收方返回的确认序号为3001,说明3001之前的数据正常发送,3001-4000数据丢失,同样移动窗口起点变为第一种情况处理

在上面的过程中,以第一种情况为例,发送方能确定1-1000丢包,所以重传1-1000这个数据,但是后面几个数据本身是到达的,如果发送方再次发送就会在接收方出现数据冗余的问题,所以接收方还要处理数据去重的问题

上面这段过程也被称为ACK机制,除了ACK机制以外,还有快速重传机制

TCP快速重传机制是一种不依赖超时计时器的丢包恢复技术,其核心原理是:当接收方收到乱序数据时,会立即发送重复ACK,表明期望接收的下一个序号;当发送方连续收到3个相同序号的重复ACK时,不等待重传计时器超时,立即重传确认号指示的数据包

拥塞控制机制

TCP除了有滑动窗口机制控制每一次发送的数据,还有拥塞控制机制决定发送方可以发送的数据,在上面谈论滑动窗口机制是建立在接收方缓冲区的大小之上,而在发送方和接收方进行数据交换时还存在网络,网络的情况也决定了下一次发送方可以发送的数据大小,而通过网络情况控制下一次发送数据大小的方式就是拥塞控制

TCP中实现拥塞控制是通过慢启动的方式实现的,所谓的慢启动就是「先发少量的数据探探路,摸清当前的网络拥堵状态,再决定按照多大的速度传输数据」,基本示意图如下:

在这里插入图片描述

在TCP底层会维护一个拥塞窗口值,这个值代表了下一次可以发送的数据大小,第一次出现拥塞时,这个值为1,随后呈指数形式增长,这样可以保证刚开始发送数据的量较小,到达一定程度后发送的数据量大,但是因为指数后期增长太快,所以到达一个阈值后指数型增长会退化为线性增长。需要注意的是,如果拥塞窗口值大于了当前接收方缓冲区的大小,那么此时拥塞窗口再增大也只会取接受方缓冲区大小作为下一次可以发送的数据量,所以实际上 滑动窗口的大小 = m i n ( 接收方缓冲区大小 , 拥塞窗口值 ) 滑动窗口的大小=min(接收方缓冲区大小, 拥塞窗口值) 滑动窗口的大小=min(接收方缓冲区大小,拥塞窗口值)。尽管如此,拥塞窗口值并不会在大于接收方缓冲区大小之后停止增长,而是会继续增长直到下一次重新遇到网络拥塞情况

拥塞窗口值改变过程如下图所示:

在这里插入图片描述

粘包问题

TCP粘包是指多个独立的数据包在接收时被合并成一个数据包的现象,这会导致接收方难以正确区分消息边界

一般情况下,产生粘包问题的原因有下面几点:

  1. TCP面向流特性:TCP是面向流的协议,它没有消息边界的概念,只是简单地把数据看作字节流
  2. Nagle算法:为提高传输效率,TCP会将多个小数据包合并成一个大数据包发送
  3. 接收方缓冲区机制:接收方可能会一次性读取缓冲区中的多条消息

粘包问题表现出的结果就是:

  1. 接收方一次接收到两个或多个发送方的数据包(粘包)
  2. 一个数据包被分割成多次接收(拆包)

应对粘包问题一般有以下几种方式:

  1. 消息定长:每个消息固定长度,不足部分补充空字符
  2. 包头标识:消息头部声明消息的长度
  3. 特殊分隔符:在消息之间添加特殊字符作为边界
  4. 应用层协议:使用HTTP、WebSocket等应用层协议自带的消息边界机制

TCP粘包本质上不是协议的缺陷,而是TCP流式传输的特性导致的。应用层必须自行解决消息边界问题

其他标志位

URG (Urgent) - 紧急指针标志:其作用是表示数据包中包含紧急数据。当URG=1时,TCP首部中的紧急指针字段生效。一般用于发送需要优先处理的数据,接收方可以跳过普通数据直接处理紧急数据,但是在现代网络应用中较少使用

PSH (Push) - 推送标志:其作用是要求接收方立即将数据推送给应用层处理,不等待缓冲区填满,当PSH=1时,接收方TCP实现会立即将已接收的数据交付给应用层。一般用于需要实时响应的场景,如交互式应用、命令行终端,减少数据传输延迟,但可能增加处理开销

RST (Reset) - 复位标志:其表示立即终止连接,当RST=1时,连接会立即断开,不执行正常的四次挥手,一般有下面三种场景:

  • 拒绝非法连接请求
  • 响应连接到不存在的端口
  • 异常情况下强制断开连接

其特点是接收到RST标志的包后,不会发送ACK确认

TCP连接保活机制

TCP连接保活(Keepalive)是一种检测空闲连接状态的机制,用于维持长时间空闲的TCP连接并及时发现失效连接,其工作原理如下:

  1. 定时探测:当连接空闲超过一定时间(默认通常为2小时),TCP自动发送不包含数据的探测包
  2. 连接维持:如果对端正常响应ACK,则认为连接仍然有效,重置空闲计时器
  3. 异常检测:如果连续多次探测无响应,则判定连接已断开,自动释放相关资源

连接保活一般有下面的几种应用场景:

  • 长连接服务:数据库连接、消息推送等需要保持长期连接的场景
  • 及时发现故障:检测网络中断或对端主机崩溃的情况
  • 资源回收:避免维护大量实际已失效的"僵尸连接"

在Linux中主要通过三个内核参数控制:

  • tcp_keepalive_time:空闲多久开始发送探测包
  • tcp_keepalive_intvl:两次探测间隔时间
  • tcp_keepalive_probes:探测失败次数阈值

在Linux中启用连接保活可以使用下面的方式:

int keepalive = 1;
setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &keepalive, sizeof(int));

保活机制有效解决了长连接应用中连接异常断开难以及时发现的问题,但会产生少量额外的网络流量

基于TCP的应用层协议

常见的有下面几种:

  • HTTP
  • HTTPS
  • SSH
  • Telnet
  • FTP
  • SMTP

http://www.kler.cn/a/598904.html

相关文章:

  • LeetCode(704):二分查找
  • 剖析C++中继承、多态的底层原理
  • 前端会话控制技术:cookie/session/token
  • 哈尔滨工业大学DeepSeek公开课人工智能:大模型原理 技术与应用-从GPT到DeepSeek|附视频下载方法
  • Sql Server数据迁移易错的地方
  • HarmonyOS Next~鸿蒙系统功耗优化体系解析:前台交互与后台任务的全场景节能设计
  • 红数码影视(RED Digital Cinema)存储卡格式化后的恢复方法
  • AF3 rot_matmul 和 rot_vec_mul函数解读
  • 跨平台数据集成:从SQLServer到MySQL的高效实践
  • QT 图表(拆线图,栏状图,饼状图 ,动态图表)
  • Python散点密度图(Scatter Density Plot):数据可视化的强大工具
  • SysVinit和Systemd的系统运行级别
  • others-rustdesk远程
  • 卷积神经网络(Convolutional Neural Network,CNN)
  • c++二分查找模板
  • 【详细解决】pycharm 终端出现报错:“Failed : 无法将“Failed”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。
  • 爬虫案例-爬取某站视频
  • 13、STL中的unordered_set使用方法
  • 单片机写的小液晶屏驱动+汉字滚屏
  • 前端性能优化利器:CDN 技术详解与实战