TCP(Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层协议,用于在网络上可靠地传输数据。TCP是互联网协议套件(TCP/IP)中的一个主要协议,它在IP(Internet Protocol)的基础之上提供了可靠的数据传输服务。
TCP协议具有以下特点和功能:
-
面向连接:在通信双方进行数据传输之前,需要建立TCP连接,包括三次握手和四次挥手过程。这种面向连接的机制可以确保数据的可靠传输和顺序交付。
-
可靠传输:TCP通过序列号、确认应答和超时重传等机制来确保数据的可靠传输。如果发生丢包或数据包错误,TCP可以重新发送数据,直到对方确认接收正常。
-
流量控制:TCP通过滑动窗口机制来控制数据的传输速率,避免发送方发送数据速度过快导致接收方无法处理。接收方通过发送窗口大小来告知发送方自身的接收能力。
-
拥塞控制:TCP通过拥塞窗口和拥塞避免算法来避免网络拥塞,从而提高网络的稳定性和性能。
-
面向字节流:TCP将应用程序交给它发送的数据视为字节流,而不是分割成数据块。TCP会将这个字节流分割成合适大小的数据包,传输到接收方后再重组还原为原始的字节流。
总之,TCP是一种可靠的传输协议,适用于需要数据可靠传输和确保数据完整性的应用场景,如网页访问、文件传输、电子邮件等。它是互联网中最常用的传输层协议之一,为网络通信提供重要的支持。
一、TCP协议段格式
TCP协议段的格式如下:
-
源端口号(16位):标示发送该报文段的进程(或应用)的端口号。
-
目的端口号(16位):标示接收该报文段的进程(或应用)的端口号。
-
序号(32位):指定发送数据的最后一个字节的编号,确保数据的有序传输。
-
确认号(32位):指定期望收到的下一个字节的编号,用于实现可靠的数据传输。
-
首部长度(4位):以32位字(即4字节)为单位计算出的TCP首部的长度。由于TCP选项字段的存在,TCP首部的长度是可变的,但通常TCP首部的典型长度为20字节。(范围[5, 15] -> [20, 60])
-
保留(6位):保留的6个位用于后续协议的扩展。
-
控制位(6位):包含多个控制标志,用于管理TCP连接的建立和断开等过程。
- URG:紧急指针(urgent pointer)有效。
- ACK:确认号有效。
- PSH:接收方应尽快将报文段交给应用层。
- RST:重置连接。
- SYN:同步序列号,用于建立连接。
- FIN:结束连接。
-
窗口大小(16位):用于指定发送方可以接收的字节数,实际就是发送方接收缓冲区剩余空间的大小。
-
校验和(16位):用于检测TCP报文段(首部+数据)在传输过程中是否发生了错误。
-
紧急指针(16位):当URG标志位被设置时,紧急指针指向紧急数据的最后一个字节的序号。
-
选项(可变长度):用于传输其他信息,如最大段长、最大窗口大小等。
TCP报文段由首部和数据两部分组成。首部固定部分长度为20字节,加上选项字段后,最大长度可达60字节(15*4)。数据部分则包含要传输的应用层数据。
TCP协议段格式的设计充分考虑了数据传输的可靠性、有序性和流量控制等需求。通过序列号、确认号、窗口大小等机制,TCP能够确保数据在网络中的可靠传输。同时,标志位和选项字段的存在,使得TCP能够灵活地处理各种网络情况,满足不同的应用需求。
注意:不同的协议层对数据包有不同的称谓,在传输层叫做数据段(segment),在网络层叫做数据报 (datagram),在链路层叫做数据帧(frame)
1.1 解包与分用
解包:将报头与有效载荷进行分离
TCP从下层获取到一个TCP报文后,虽然TCP不知道报头的具体长度,但报文的前20个字节是TCP的基本报头,并且这20字节当中涵盖了4位的首部长度。
TCP解包的过程如下:
- TCP获取到一个报文后,首先读取报文的前20个字节(固定大小的基本报头)
- 并从中提取出4位的首部长度(范围[5, 15] -> [20, 60]),此时便获得了TCP报头的大小(基本报头+选项)
- 拿到报头长度之后,就可以直接与有效载荷进行分离了
分用:将有效载荷交付给上层协议
-
应用层的每一个网络进程都必须绑定一个端口号。
-
提取出TCP报头中的目的端口号,找到对应的应用层进程,进而将有效载荷交给对应的进程进行处理,分用完毕
1.2 TCP协议的6个标志位
TCP协议头部中有6个标志位,它们分别是:
-
URG(URGent):紧急指针有效位,表示TCP报文中的紧急指针字段有效,用于指示紧急数据,通常配合紧急指针字段一起使用。
-
ACK(ACKnowledgment):确认号标志位,表示该字段的值是有效的,用于确认收到的数据包序号。凡是应答都要带ACK。
-
PSH(PuSH):推送标志位,表示接收端应该立即将缓冲区中接收到的数据送交上层应用,而不需要等到缓冲区满或者等待一个时间片。
-
RST(ReSeT):重置连接标志位,用于中断连接、重新建立连接或者处理异常情况。
-
SYN(SYNchronization):同步序号标志位,用于建立连接时发起连接请求。
-
FIN(FINish):结束标志位,用于释放连接时通知对方,表示已经发送完所有数据,即将关闭连接。
这些标志位在TCP头部中用于控制连接的建立、数据传输和连接的释放等各个阶段,以确保数据在传输过程中的可靠性和有序性。
可以说标志位就是TCP报文的类型,包含不同标志位的报文段所对应的功能不同。同一个报文可以包含多个标志位,具有多重功能和身份。
TCP协议的紧急数据(带外数据)
-
紧急数据又被称为带外数据,是插在正常数据流中进行传输的,长度只有1字节。紧急数据会被高优先级处理。
-
TCP协议中的紧急指针(Urgent Pointer)是一个16位的字段,用于指示紧急数据的位置。当 TCP 报文段中的 URG 标志被设置时,紧急指针字段会被用来指示当前数据中紧急数据的末尾位置。紧急指针的值表示紧急数据相对于序列号的偏移量。
-
当发送方发送包含紧急数据的 TCP 报文时,在 TCP 头部中设置 URG 标志位,并且指定紧急指针的值,以通知接收方需要尽快处理这部分数据。接收方在收到带有紧急指针的 TCP 报文时,会根据紧急指针指示的位置立即处理紧急数据,而不会等待正常数据的处理。
-
如何发送带外数据?send和recv函数的最后一个参数传MSG_OOB参数可以发送和读取带外数据。对应的TCP报头中URG标记位被置1,且紧急指针指向紧急数据的末尾位置。
参考文章:
tcp中的带外数据 - lypbendlf - 博客园 (cnblogs.com)
1.3 其他问题
TCP报头问什么没有有效载荷的长度?
-
TCP报头中没有具体的字段来表示载荷(payload)的大小。TCP协议本身关注的是可靠传输的机制,对于应用层的具体数据并不关心。
-
如果需要知道TCP报文段中的载荷大小,应用程序通常需要自己进行协议解析,根据自己数据的格式和协议规定来解析报文段,确定载荷的大小。比如,在HTTP协议中,可以通过解析HTTP头部中的Content-Length字段来获取数据的大小。
TCP网络通信中的“一切皆文件”
具体来说,TCP网络通信中的“一切皆文件”可以从以下几个方面来理解:
- 套接字(Socket)作为通信端点:套接字是网络通信中的一个关键抽象,它充当了底层协议与上层应用间通信的端点。开发者可以像操作文件一样打开和关闭套接字,通过套接字发送和接收数据。虽然套接字在内部实现上与文件有很大不同,但这种抽象使得网络通信在编程模型上与文件操作相似。
- 数据流的概念:在TCP网络通信中,数据以流的形式传输,数据被视为连续的字节序列。这种数据流的概念与文件操作中的读取和写入文件内容非常相似。开发者可以像从文件中读取数据一样从套接字中接收数据,也可以像写入文件一样通过套接字发送数据。
- 套接字的缓冲区:套接字缓冲区是套接字(socket)在发送和接收数据时使用的临时存储区域。套接字缓冲区分为发送缓冲区和接收缓冲区,这与文件的读写缓冲区非常类似。
提示:套接字缓冲区的存在可以提高网络通信性能。通过使用缓冲区,可以将应用程序和网络通信进行解耦,使得应用程序可以继续执行而不需要等待网络通信的完成。除了这些套接字缓冲区还参与流量控制、超时重传、按序接收数据等功能。例如:发送缓冲区还可以充当流量控制的作用,当对端接收缓冲区即将占满或已经占满,TCP协议可以控制减少发送流量,或者干脆停止发送。
TCP协议的缓冲区
-
tcp是全双工通信,因为每个链接(每个套接字)都有对应的发送和接收缓冲区,收发可以并发执行(但是多线程单独收或发必须串行执行)
-
read, write, send, recv等系统调用只负责应用层和传输层缓冲区之间数据的拷贝,而传输层缓冲区和网络之间数据的收发(发多少,何时发)则完全是由tcp协议负责,所以TCP协议称为传输控制协议。
二、确认应答机制
TCP(Transmission Control Protocol)协议中的确认应答机制是确保数据可靠传输的核心机制之一。这一机制通过一系列步骤和规则,确保发送方发送的数据能够被接收方正确接收,并在必要时进行重传,以保证数据的完整性和顺序性。以下是对TCP协议中确认应答机制的详细解析:
2.1 基本流程
- 发送数据:发送方(如客户端)将数据分割成合适大小的报文段,并附加TCP头部信息(包括序列号、确认号、标志位等),然后发送给接收方(如服务器)。
- 接收数据:接收方收到报文段后,会检查TCP头部信息,特别是序列号,以确认数据的顺序和完整性。
- 发送确认报文:接收方在成功接收数据后,会向发送方发送一个确认报文(ACK报文),该报文中包含确认号(即期望接收到的下一个数据的序列号)。这个确认号告诉发送方,接收方已经成功接收了哪些数据。
- 等待确认:发送方在发送数据后,会等待接收方的确认报文。如果在一定时间内(如超时时间)未收到确认报文,发送方会认为数据可能丢失或损坏,并触发重传机制。
为什么要有确认应答机制呢?
- 简单来讲,发送数据后收到应答,就能够保证发送数据的可靠性。因此不管是客户端还是服务端都要对接收到的数据进行应答。这样就能够保证两个方向上数据发送的可靠性。
- 至于应答是否被收到,我们无需关心,也就是不需要再为应答而应答。如果应答丢失大不了重传呗,收到重复的数据去重即可。
2.2 关键要素
- 序列号:TCP为每个字节的数据进行编号,这个编号称为序列号。TCP报头中的序列号就是对应发送数据块的最后一个字节的编号。序列号用于标识发送方发送的数据,接收方通过序列号来确认收到的数据是否完整和有序。
- 确认号:接收方在发送确认报文时,会包含一个确认号,该确认号表示接收方期望接收到的下一个数据的序列号,同时也表示确认号之前的数据已经被成功接收。
- 超时重传机制:如果发送方在一定时间内未收到接收方的确认报文,它会认为数据可能丢失或损坏,并触发超时重传机制。发送方会重新发送那些未被确认的数据,直到收到接收方的确认报文为止。
- 流量控制:TCP还通过窗口大小等机制来实现流量控制,防止发送方发送速度过快导致接收方无法处理。接收方会在确认报文中包含自己的窗口大小信息,告诉发送方自己当前能够接收的数据量。
注意:
- 初始序列号(ISN):通常初始序列号并不是1而是一个随机数字。随机选择初始序列号可以增加TCP连接的安全性,因为具有随机性的初始序列号可以降低被攻击者猜测序列号的可能性。
- 有了序号为什么还要有确认序号?共用一个字段空间不行吗?TCP报文大多数情况具有多重身份,既是发送也是应答(捎带应答),如果序号和确认序号共用一个字段空间显然不能满足两个方向上的需求。
- TCP协议中确认序号并不要求必须与序号严格匹配,而是用来表示期望接收到的下一个序号。确认序号告诉对方接收到了哪个序号之前的数据,并为对方提供一个基准点,以便正确地接收后续的数据。上图中,如果并未收到序号2000的报文(丢失)而收到了序号3000的报文,那么应答报文中的确认序号应该是1001(而不是3001)。这样就能够保证确认序号之前的数据已经被成功接收,同时也允许应答有少量的丢失。
2.3 确认应答机制的作用
- 确保数据的可靠传输:通过确认应答机制,TCP能够确保发送方发送的数据能够被接收方正确接收,并在必要时进行重传,从而保证数据的完整性和顺序性。
- 提高网络传输效率:通过流量控制等机制,TCP能够避免发送方发送速度过快导致接收方无法处理的情况,从而提高网络传输效率。
- 支持全双工通信:TCP连接是全双工的,即双方在同一时间既能发送数据又能接收数据。确认应答机制为这种全双工通信提供了可靠的保障。
综上所述,TCP协议中的确认应答机制是确保数据可靠传输的关键机制之一。它通过序列号、确认号、超时重传机制和流量控制等要素,实现了数据的可靠传输和网络传输效率的提高。
三、超时重传机制
3.1 情况一:发送数据丢失
- 主机A发送数据给B之后, 可能因为网络拥堵等原因, 数据无法到达主机B(丢失或超时);
- 如果主机A在一个特定时间间隔内没有收到B发来的确认应答, 就会进行重发;
3.2 情况二:确认应答丢失
- 但是, 主机A未收到B发来的确认应答, 也可能是因为ACK丢失了(丢失或超时)
- 因此主机B会收到很多重复数据. 那么TCP协议需要能够识别出哪些包是重复的包, 并且把重复的丢弃掉。
- 我们可以通过TCP报头中的序列号来实现去重的功能。
3.3 超时重传时间(RTO)
TCP具有超时重传机制,即当一个数据包没有收到确认回复时,会在一定的时间间隔后进行重传。这个时间间隔被称为超时重传时间(Retransmission TimeOut,简称RTO)。那么如何确定超时重传时间呢?
最理想的情况下, 找到一个最小的时间, 保证 确认应答一定能在这个时间内返回。但是这个时间的长短, 随着网络环境的不同,是有差异的。
- 如果超时时间设的太长, 丢包很久才会重传,会影响整体的重传效率;
- 如果超时时间设的太短, 有可能会频繁发送重复的包;
TCP为了保证无论在任何环境下都能比较高性能的通信会动态计算超时重传时间。
平滑往返时间(SRTT)
往返时延(RTT)是指数据从发送端到接收端并返回发送端所需的时间。
一种简单的方法是取RTT的平均值,例如:第一次RTT为500ms,第二次RTT为800ms,那么第三次发送时,可以将两次RTT取平均得到RTO为650ms。
为了确定超时重传时间和更准确地估计RTT,经典算法引入了「平滑往返时间」(Smoothed round trip time,SRTT):每次测量RTT后,都对SRTT进行更新计算,使其更加平滑和准确。
平滑因子α是用于计算平滑往返时间(SRTT)的一个参数,建议取值范围为0.8至0.9。具体而言,当α为0.8时,SRTT的计算公式为80%的原始值加上20%的新采样RTT值。
- 当α趋近于1时,SRTT会更接近上一次的SRTT值,对新的RTT值的影响较小。这意味着对于短暂的时延变化,SRTT会表现出较低的敏感性。
- 相反,当α趋近于0时,1-α趋近于1,SRTT会更接近新采样的RTT值,与旧的SRTT值的关系较小。这意味着对于时延的变化,SRTT会表现出更高的敏感性,能够更快速地跟随时延的变化而调整。
通过调整平滑因子α的取值,TCP可以根据网络环境的不同情况来灵活地调整SRTT的计算方式,以适应不同的时延变化。这样可以提高TCP的适应性和性能,使其能够更好地应对网络条件的变化。
基于SRTT,TCP进一步计算RTO的值,通常,RTO的值会略大于连接往返时间。
当然,累计到一定的重传次数,TCP会认为网络或者对端主机出现异常,将强制关闭连接。
四、连接管理机制
TCP(传输控制协议)的连接管理机制主要包括两个核心过程:三次握手建立连接和四次挥手断开连接。这两个过程确保了TCP连接的可靠性和数据传输的完整性。
4.1 三次握手建立连接
4.1.1 具体过程
三次握手是TCP协议中用来建立连接的过程,它通过三个步骤来确认通信双方的发送和接收能力是否正常,以及协商一些必要的参数(如初始化序列号等)。具体过程如下:
- 客户端发送SYN报文:
- 客户端首先向服务器发送一个带有SYN(同步序列编号)标志位的TCP报文段。这个报文段不包含应用层的数据,只包含TCP头部信息,其中会包含客户端的初始序列号(ISN)。
- 服务器发送SYN-ACK报文:
- 服务器收到客户端的SYN报文后,会回复一个带有SYN和ACK(确认应答)标志位的TCP报文段,即SYN-ACK报文。这个报文段不仅确认了客户端的SYN报文(ACK=客户端的ISN+1),还包含了服务器自己的初始序列号(ISN)。
- 客户端发送ACK报文:
- 客户端收到服务器的SYN-ACK报文后,会发送一个带有ACK标志位的TCP报文段作为应答。这个报文段确认了服务器的SYN报文(ACK=服务器的ISN+1),至此三次握手完成,TCP连接成功建立。
三次握手的意义在于:
- 在正式通信之前双方都至少进行了一次收发,确认通信双方的发送和接收能力是否正常。
- 协商一些重要的参数,如初始化序列号、窗口大小等。
- 三次握手可以确保一般情况下握手失败的连接成本是嫁接在client端的:要知道链接是要有维护成本的(如创建和维护链接数据结构),而最后一次应答的可靠性是无法保证的,在三次握手中最后一次应答由客户端发起,随后客户端建立链接(不可靠,可能失败),而服务端只有在接收到最后一次应答后才会建立链接(可靠)。相比服务端,客户端的工作压力小,可以承受握手失败的连接成本。
注意:
- 客户端调用connect函数只是要求系统构建一个SYN报文段并发送给服务端,只负责发起三次握手,之后便处于阻塞状态等待三次握手完成。至于三次握手的细节作为系统调用接口的connect函数并不过多参与。握手的过程是双方操作系统自主完成的。
- 服务端调用accept函数获取链接,本身并不参与三次握手,只会在三次握手成功后将已经建立好的链接返回。如果底层一直没有建立好链接,accept会一直阻塞。
4.1.2 状态转换
服务端:
- [CLOSED -> LISTEN] 服务器端调用listen后进入LISTEN状态,等待客户端连接
- [LISTEN -> SYN_RCVD] 一旦监听到连接请求(SYN),就将该连接放入内核等待队列(半连接队列)中,并向客户端发送SYN确认报文
- [SYN_RCVD -> ESTABLISHED] 服务端一旦收到客户端的确认报文,就进入ESTABLISHED状态,可以进行读写数据了
客户端:
- [CLOSED -> SYN_SENT] 客户端调用connect,发送同步报文段(SYN)
- [SYN_SENT -> ESTABLISHED] 接收到服务端发送的SYN+ACK报文段,则进入ESTABLISHED状态,connect调用成功返回,开始读写数据
4.1.3 相关测试
测试例程是之前【socket编程】TCP网络通信模型-CSDN博客中的单执行流版本;
服务端、客户端分别位于两台不同机器上启动,测试效果更好;
测试一:服务器完成socket, bind, listen工作后不进行accept,启动服务端、客户端观察链接情况:
现象:虽然服务器并未调用accept接收新链接,但是无论在客户端还是服务端链接都已经被成功建立了。
结论:连接建立成功与否和上层有没有accept没有关系,三次握手是双方操作系统自动完成的
测试二:将listen函数的第二个参数backlog设为1,启动服务端、客户端观察链接情况:
现象:
- 客户端认为自己的3次链接都建立成功了,状态ESTABLISHED
- 而在服务端前2次链接建立成功,而最后一次链接并未建立成功,状态是SYN_RECV
4.1.4 全连接和半连接
在TCP协议中,全连接(full connection)和半连接(half connection)通常指的是TCP三次握手建立连接的过程中的不同阶段。
全连接和全连接队列
- 全连接(full connection):在TCP连接的全连接阶段,指客户端和服务器完成了三次握手过程,建立了完整的双向通信连接。在全连接状态下,客户端和服务器可以同时发送和接收数据,实现双向通信。这是TCP连接建立后的正常工作状态。
- 全连接队列也称为已完成连接队列(Established Connection Queue),用来存储已经完成三次握手的TCP全连接。当服务器接收到客户端的连接请求,完成三次握手建立连接后,就将该连接放入全连接队列中,可以进行数据交换和传输。全连接队列中的连接处于已建立的完整连接状态,可以直接被服务器进程接收和处理(通过accept系统调用获取)。
- 全链接队列的最大长度(backlog):listen函数的第二个参数backlog再+1就是全链接队列的最大长度。在上面的测试中我们将backlog设置为1,那么对应全链接队列的最大长度就是2。因此,前2次连接可以建立成功,而第3次却不行。
- 全连接队列的最大长度需要合理设置,既不能太长也不能太短,以确保服务器的性能和可靠性。全链接队列的维护是需要时间空间消耗的,如果设置太长会导致:资源浪费、访问延迟(资源不足)、系统不稳定等问题;相反,全连接队列的功能是缓存已经建立好的连接,如果设置太短会导致:连接拒绝、连接丢失、服务进程空闲等问题。
半连接和半连接队列
- 半连接(half connection):在TCP连接的半连接阶段,指在三次握手的某个阶段未完成,连接未完全建立,导致连接状态不完整无法进行数据传输。在网络通信中,半连接状态可能会导致资源浪费或连接异常,因此应该及时处理并确保连接状态完整。
- 半连接队列用来存储还未完成三次握手的TCP半连接,也称为未完成连接队列、SYN队列。如以上测试,在三次握手的过程中,当服务器接收到客户端的SYN请求后,将其放入半连接队列中等待接收客户端的确认ACK,完成连接的建立。但如果此时全连接队列已满,即使收到了客户端的确认ACK,服务端也会选择忽略,任然保持该连接的半连接状态(SYN_RECV状态)。
- 半连接不会被长时间维护,如果一段时间后上层应用任然没有接收和处理新链接,使全连接队列长时间占满,那么半链接就会被释放。
- SYN_RECV半链接存在客户端和服务端链接状态不一致的问题,客户端接收到服务端发来的SYN+ACK报文后就认为连接建立成功,而服务端却因服务器繁忙未能建立连接。此时客户端发送数据可能会收到服务端的RST报文要求重新建立链接,客户端多次尝试最终成功建立连接后才能进行正常的通信。
SYN洪水攻击:SYN洪水攻击利用TCP协议中的三次握手机制,通过向服务器发送大量伪造的SYN请求包,以开放的连接(半连接)淹没服务器,导致服务器资源耗尽,无法正常处理合法的连接请求。实际上就是将服务器的半连接队列占满,使得合法的SYN请求被丢弃或拒绝。
4.2 四次挥手断开连接
4.2.1 具体过程
四次挥手是TCP协议中用来断开连接的过程,它通过四个步骤来确保通信双方都能正常结束数据传输并释放资源。具体过程如下:
- 客户端发送FIN报文:
- 客户端想要关闭连接时,会向服务器发送一个带有FIN(结束)标志位的TCP报文段,表示客户端没有数据要发送了。
- 服务器发送ACK报文:
- 服务器收到客户端的FIN报文后,会发送一个带有ACK标志位的TCP报文段作为应答,确认收到了客户端的FIN报文。此时,客户端到服务器的连接被关闭,但服务器到客户端的连接仍然保持开启状态,以便服务器可以继续发送数据。
- 服务器发送FIN报文:
- 当服务器也想要关闭连接时,会向客户端发送一个带有FIN标志位的TCP报文段。
- 客户端发送ACK报文:
- 客户端收到服务器的FIN报文后,会发送一个带有ACK标志位的TCP报文段作为应答,确认收到了服务器的FIN报文。至此,四次挥手完成,TCP连接被完全关闭。
四次挥手的意义在于:
四次挥手确保了双方都能够确认彼此已经知道连接要关闭,避免出现数据丢失或者连接被意外关闭的情况。通过四次挥手,TCP连接的关闭过程变得有序且安全。
需要注意的是,四次挥手过程中,任何一方都可以先发起关闭连接的操作,而且中间步骤可能会因为网络延迟或丢包等原因而重复进行(超时重传)。
4.2.2 状态转换
客户端状态转化:
- [FIN_WAIT_1 -> FIN_WAIT_2] 客户端收到服务器对结束报文段的确认,则进入FIN_WAIT_2,开始等待服务器的结束报文段
- [FIN_WAIT_2 -> TIME_WAIT] 客户端收到服务器发来的结束报文段,进入TIME_WAIT, 并发出LAST_ACK
- [TIME_WAIT -> CLOSED] 客户端要等待2MSL(Max Segment Life, 报文最大生存时间)的时间,才会进入CLOSED状态
服务端状态转化:
- [ESTABLISHED -> CLOSE_WAIT] 当客户端主动关闭连接(调用close),服务器会收到结束报文段,服务器返回确认报文段并进入CLOSE_WAIT
- [CLOSE_WAIT -> LAST_ACK] 进入CLOSE_WAIT后说明服务器准备关闭连接(需要处理完之前的数据);当服务器真正调用close关闭连接时,会向客户端发送FIN,此时服务器进入LAST_ACK状态,等待最后一个ACK到来(这个ACK是客户端确认收到了FIN)
- [LAST_ACK -> CLOSED] 服务器收到了对FIN的ACK,彻底关闭连接
4.2.3 相关测试
测试三:服务端调用accept函数接收到新链接,5s后close(sockfd)关闭链接。服务端是主动断开连接的一方。
现象:服务端连接变成了FIN_WAIT2状态
接着客户端退出,也断开连接:
现象:服务端连接变成了TIME_WAIT状态,一段时间后该条连接才彻底消失(断开)
在该条链接处于TIME_WAIT状态期间,尝试重新启动服务器:
现象:发生bind error,错误原因是address already in use,意味着指定的地址和端口已经被另一个进程使用。
4.2.4 TIME_WAIT状态
- TCP协议规定,主动关闭连接的一方,在四次挥手完成后要处于TIME_ WAIT状态,等待两个MSL(maximum segment lifetime)的时间后才能回到CLOSED状态自动释放。
- MSL是一个TCP连接在网络中能够存活的最长时间,通常为2分钟,因此,一般情况下TIME_WAIT状态的等待时间为4分钟,但也有可能被系统参数配置影响(Centos7上默认配置的值是60s),最长不会超过240秒。
- 可以通过
cat /proc/sys/net/ipv4/tcp_fin_timeout
查看msl的值 - 在server/client通信过程中,如果server因为某些原因退出,是主动断开连接的一方,那么server端建立的链接就要进入TIME_WAIT状态,这表示连接没有被彻底断开。此时如果server重新启动,会出现端口号绑定失败的问题。这是因为先前绑定的ip-port仍处于TIME_WAIT状态并未被释放,而端口号无法被两个进程同时绑定。
- 因为客户端的端口号是由操作系统动态分配的,所以如果是客户端主动断开的链接,其链接状态自然也要变为TIME_WAIT,但是不会影响客户端的再次启动,因为操作系统会自动选取一个未被占用的端口号进行绑定。
为什么要有TIME_WAIT状态?
以下是一些主要原因和目的:
-
双向数据包传输完整性:在TIME_WAIT状态下,等待一段时间确保最后一个ACK确认数据包被对端接收,保证双方都已经完全关闭了连接。这可以保证双向数据包的传输完整性,防止数据包在过早关闭连接时丢失。
-
防止旧的数据包干扰新连接:TIME_WAIT状态的等待时间能够确保网络中已发送的数据包在连接关闭之后不再出现,避免旧的数据包在网络中延迟到达时被错误处理,干扰了正在建立的新连接。例如:如果连接过早关闭,那么延迟到达的数据包会被误认为连接已经关闭而导致的问题。
-
避免连接混淆:TIME_WAIT状态保留关闭连接的状态信息,确保端口不会在短时间内被重复利用,防止连接混淆或冲突。这可以避免因为端口资源被频繁占用而导致的连接问题。
-
安全性:TIME_WAIT状态能够保护连接的完整性和安全性,防止连接被非法篡改或重放攻击。确保连接关闭的正确性,提高数据传输的安全性。
解决TIME_WAIT状态引起的服务器bind error问题
因为存在TIME_WAIT状态的连接而导致服务器出现bind error,进而就会导致服务器长时间无法重启。这种情况是很不合理的。
可以通过setsockopt
函数设置监听套接字的端口重用选项(SO_REUSEADDR | SO_REUSEPORT)为1,来允许多个应用程序共享相同的端口,避免端口资源被浪费。
int opt = 1;
setsockopt(listensocket, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));