TCP协议
TCP全称为"传输控制协议(Transmission Control Protocol)"人如其名,要对数据的传输进行一个详细的控制。
1.TCP协议段格式

下面是TCP报头各个字段的表格形式:
字段名称 | 字段大小 | 描述 |
---|---|---|
源端口 | 16位 | 发送端TCP端口号。 |
目的端口 | 16位 | 接收端TCP端口号。 |
序列号 | 32位 | 本报文段所发送数据的第一个字节的序列号。 |
确认号 | 32位 | 期望收到对方下一个报文段的第一个数据字节的序列号。若确认号设置为N,则表示到序列号N-1为止的所有数据都已正确接收。 |
数据偏移 | 4位 | TCP头部的长度,以4字节为单位,也指示了选项字段(如果有)的长度。 |
保留 | 6位 | 目前未使用,必须设置为0。 |
控制位 | 6位 | 包括URG、ACK、PSH、RST、SYN、FIN等标志,用于控制TCP连接和传输。 |
窗口大小 | 16位 | 发送方接收窗口的大小,即无需再次确认可以接收的数据量。 |
校验和 | 16位 | 用于校验整个TCP段(包括TCP头部和数据)在传输过程中是否出现错误。 |
紧急指针 | 16位 | 仅当URG标志被设置时有效,指出紧急数据的最后一个字节的序号。 |
选项 | 可变长度 | 可选字段,可以包含多种类型的选项,如MSS、窗口缩放、SACK等。 |
、、、、 | 、、、、、 |
下面是TCP报头中控制位字段的表格形式:
控制位标志 | 占位 | 描述 |
---|---|---|
URG | 1位 | 紧急标志(Urgent)。当URG=1时,表明紧急指针字段有效,指示紧急数据的位置。 |
ACK | 1位 | 确认标志(Acknowledgment)。当ACK=1时,确认号字段有效,表示期望收到对方下一个报文段的第一个数据字节的序列号。 |
PSH | 1位 | 推送标志(Push)。当PSH=1时,要求接收方尽快将数据推送给应用层,而不是等到缓冲区满时才发送。 |
RST | 1位 | 重置标志(Reset)。当RST=1时,表示TCP连接中出现严重错误,需要重置连接。我们把携带RST标识的称为复位报文段 |
SYN | 1位 | 同步序列编号标志(Synchronize)。当SYN=1时,表明这是一个连接请求或连接接受报文。我们把携带SYN标识的称为同步报文段 |
FIN | 1位 | 结束标志(Finish)。当FIN=1时,表明发送方已经发送完所有数据,并要求释放连接。我们把携带FIN标识的称为结束报文段 |
、、、、、 | 、、、 |
这些控制位标志用于TCP连接的建立、维护和终止,以及数据的可靠传输。每个标志都有其特定的作用,通过组合使用这些标志,TCP能够实现复杂的网络通信功能。
4位首部长度
这里有4个比特位,按照正常取值范围就说【0,15】,但是我们的报头至少需要20字节。所以规定TCP报头中4位首部长度的基本单位是4字节,这样取值范围就是【0,60】,所以整个报头大小范围就是【20~60】,报头中选项字段最长为40字节,计算可得,4位首部长度取值范围是【5~15】,转换成二进制就是【0101~1111】
- TCP如何将报头与有效载荷进行分离?(如何解包的问题)
1️⃣ 读取标准20字节 2️⃣ 提取首部长度 3️⃣ 根据首部长度-20,结果等于0,那就是报头读完,剩下都是数据;如果结果为n(>0),再从报文中提取n个字节,这n个字节对应就是选项的大小,剩下的就是有效载荷了
- 有效载荷如何向上交付?(如何分用的问题)
在计算机网络中,有效载荷(payload)通常指的是数据包中的实际数据部分,不包括头部信息。在TCP协议中,有效载荷是指应用层的数据。有效载荷的向上交付,即从传输层(TCP)交付到应用层的过程
下面是一个简化的表格,展示了有效载荷向上交付和分用的过程:
阶段 | 操作系统内核操作 | 应用层操作 |
---|---|---|
数据接收 | 1. 校验和验证数据段 | - |
2. 检查序列号,重组数据 | - | |
3. 将重组后的数据存入缓冲区 | - | |
数据交付 | 1. 通知应用层有新数据可用 | - |
分用 | 1. 使用端口号匹配对应的套接字 | - |
2. 将数据存入套接字的缓冲区 | - | |
3. 通知拥有套接字的应用进程 | 1. 接收到通知 | |
- | 2. 通过API读取数据 | |
- | 3. 处理接收到的数据 | |
所以说目的端口解决了对报头进行分用的问题
- 理解TCP是可靠的
TCP(传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。以下是TCP为何被认为是可靠的原因:
特性/机制 | 描述 |
---|---|
面向连接 | 在数据传输之前,TCP会建立一个连接,确保通信双方都准备好进行数据交换。 |
三次握手 | 建立连接的过程使用三次握手,确保双方的序列号同步,避免旧的连接请求被错误地接受。 |
数据分段 | TCP将应用层的数据分割成合适大小的段,便于网络传输。 |
序列号和确认应答 | 每个TCP段都有一个序列号,接收方会发送确认应答(ACK),以确保数据的有序接收。 |
数据重传 | 如果发送方没有在预定时间内收到确认应答,它会重新发送数据。 |
流量控制 | TCP使用滑动窗口机制来控制发送方的发送速率,以避免接收方处理不过来。 |
拥塞控制 | TCP通过慢启动、拥塞避免、快速重传和快速恢复等算法来避免网络拥塞。 |
错误检测 | TCP头部包含校验和字段,用于检测数据在传输过程中是否发生错误。 |
数据排序 | TCP确保接收到的数据段按照正确的顺序被重新组装。 |
连接终止 | TCP使用四次挥手过程来优雅地终止连接,确保所有数据都被正确传输。 |
以下是TCP可靠性的一些关键点:
数据完整性:通过校验和,TCP可以检测数据在传输过程中是否损坏,并在必要时重新传输。
数据有序性:序列号确保了数据按照发送顺序到达接收方。
数据传输可靠性:通过确认应答和重传机制,TCP确保所有发送的数据都被接收方正确接收。
流量控制:滑动窗口机制防止发送方发送数据过快,导致接收方来不及处理。
拥塞控制:TCP的拥塞控制算法帮助网络避免过载,从而保持数据传输的可靠性。 由于这些特性,TCP适用于需要高可靠性的应用,如Web浏览器、电子邮件、文件传输等。然而,这种可靠性也意味着TCP在某些情况下可能不如UDP(用户数据报协议)那样高效,因为UDP不提供这些可靠性保证,但它允许更快的数据传输速度。
- 为什么说UDP是不可靠的?
UDP(用户数据报协议)被认为是一种不可靠的传输层协议,原因在于它缺乏TCP(传输控制协议)中提供的一系列确保数据可靠传输的特性。下面是UDP不可靠性的几个关键点:
特性/机制 | 描述 | 不可靠性的影响 |
---|---|---|
无连接 | UDP不建立持续的网络连接。 | 发送和接收双方没有持续的状态信息,每次传输都是独立的。 |
无确认应答 | UDP不使用ACK机制来确认数据包的接收。 | 发送方无法知道数据包是否已经到达接收方,如果数据包丢失,不会有任何通知。 |
无重传机制 | UDP不会重传丢失的数据包。 | 如果数据包在传输过程中丢失,它将不会被重新发送,导致数据丢失。 |
无数据排序 | UDP不保证数据包的到达顺序。 | 数据包可能会以不同于发送顺序的方式到达,接收方可能接收到乱序的数据。 |
无流量控制 | UDP没有流量控制机制。 | 发送方可以以任何速度发送数据,不管接收方是否能够处理,可能导致接收方缓冲区溢出,数据丢失。 |
无拥塞控制 | UDP不实施拥塞控制。 | UDP不会根据网络拥塞情况调整数据传输速率,可能导致网络拥塞,增加数据丢失的可能性。 |
校验和可选 | UDP头部的校验和字段是可选的。 | 即使使用了校验和,它也只能检测错误,不会修复错误的数据包。如果没有使用校验和,甚至无法检测到错误。 |
、、、、、 | ||
以下是UDP不可靠性的一些具体表现:
-
数据丢失:由于没有重传机制,如果数据包在网络中丢失,它将不会被重新发送。
-
数据重复:由于没有确认应答和数据排序,可能会出现数据包重复到达接收方的情况。
-
数据损坏:即使使用了校验和,UDP也只能检测到错误,而不会修复错误的数据包。
-
网络拥塞:UDP不会根据网络拥塞情况调整传输速率,可能会导致网络拥塞,进一步影响数据传输的可靠性。
尽管UDP被认为不可靠,但它仍然在许多应用场景中非常有用,尤其是那些对实时性要求高,可以容忍一定数据丢失的应用,例如:
-
实时视频和音频流:如视频会议和流媒体服务,它们更关注流畅性而不是数据的完整性。
-
在线游戏:游戏通常需要低延迟的通信,即使偶尔的数据丢失也比延迟更好。
-
DNS查询:域名系统查询通常使用UDP,因为它需要快速响应,而且单个查询的数据量较小。
UDP的简单性和低开销使其在某些情况下比TCP更有效。
2.确认应答(ACK机制)

再谈ACK机制前,我们先举一个生活例子,生活中两个人说话,我怎么确定对方有没有收到我的话呢?我们是通过对方的应答,来确定对面有没有收到我说的话,只有对方应答了,我们才知道他听见了
而在客户端与服务端的通信过程中(TCP下),也是通过确定对方应答,来表明另一端收到了我的信息;只要收到了应答,就能保证我发的数据,对方一定收到了
所以双方进行通信时可能除了正常的数据段,还会包含确认数据段。

客户端向服务端发送数据,服务端收到后返回一条应答给客户端,表示服务端收到了;客户端可以一次只发送一条,但是在大部分场景下,客户端是一次发送多条数据段的,服务端也可以一次性返回多条应答
但是这些数据段到达对面的顺序不一定就是发送的顺序,比如发送4个数据段,结果只收到2个或者3个确认应答,客户端是如何知道哪个数据段发送失败了呢?
因为任何一方发送的一定含有报文,报文中就有一个属性叫做序号,TCP将每个字节的数据都进行了编号,即为序列号;
每一个ACK都带有对应的确认序列号,意思是告诉发送者,我已经收到了哪些数据;下一次你从哪里开始发
比如这里的客户端发送的数据段是1,报文中含有1000个字节的数据,如果服务端收到了,那么服务端返回给客户端的响应报头中的确认序号就填成1001,这个1001有两层含义:
-
告诉客户端,1001序列号以前的字节数据我已经收到了
-
告诉客户端,下次向我发送数据时应该从序列号1001开始
也正是因为有了连续应答,TCP允许对应答的少量缺失(比如我只返回4001,表示4001之前的我全部收到了)


如果1001报文丢失了,但其他的数据段传递到了服务端,服务端只会返回1001应答,表明1001以前的序列号都收到了
- 如何理解序列号?
所谓序列号就是该字节缓冲区数组的下标,数据从应用层拷贝到传输层发送缓冲区时,每个字节天然的有了一个编号;发送方发送的序号就是首个字节数据在发送缓冲区对应的下标,接收缓冲区响应应答的确认序号就是接收缓冲区接收到最后一个有效数据的下一个位置对应的下标

3.超时重传机制
丢包的两种情况
【情况一】发送的数据报文丢失了,此时发送端在一定时间内收不到对应的应答报文,就会进行超时重传

-
主机 A 发送数据给 B 之后,可能因为网络拥堵等原因,数据无法到达主机 B。
-
如果主机 A 在一个特定时间间隔内没有收到 B 发来的确认应答 ,就会进行重发。
【情况二】应答报文丢失,此时发送端也会因为收不到对应的响应报文,而进行超时重传。但是主机 A 未收到 B 发来的确认应答,也可能是因为 ACK 丢失了。

因此主机 B 会收到很多重复数据,那么 TCP 协议需要能够识别出那些包是重复的包,并且把重复的丢弃掉。
这时候就可以利用前面提到的序列号,就可以很容易做到去重(通过序号)的效果。
那么,超时的时间如何确定呢?
• 最理想的情况下,找到一个最小的时间,保证"确认应答一定能在这个时间内返回".
• 但是这个时间的长短,随着网络环境的不同,是有差异的.
• 如果超时时间设的太长,会影响整体的重传效率;
• 如果超时时间设的太短,有可能会频繁发送重复的包;
TCP为了保证无论在任何环境下都能比较高性能的通信,因此会动态计算这个最大超时时间
• Linux 中(BSD Unix 和 Windows 也是如此),超时以500ms 为一个单位进行控制,每次判定超时重发的超时时间都是 500ms 的整数倍.
• 如果重发一次之后,仍然得不到应答,等待2*500ms 后再进行重传.
• 如果仍然得不到应答,等待4*500ms 进行重传.依次类推,以指数形式递增.
• 累计到一定的重传次数,TCP 认为网络或者对端主机出现异常,强制关闭连接.
4.连接管理机制
在正常情况下,TCP要经过三次握手建立连接,四次挥手断开连接

服务端状态转化:
[CLOSED -> LISTEN]:服务器端调用 listen 后进入 LISTEN 状态,等待客户端连接。
[LISTEN -> SYN_RCVD]:一旦监听到连接请求(同步报文段),就将该连接放入内核等待队列中,并向客户端发送 SYN 确认报文。
[SYN_RCVD -> ESTABLISHED]:服务端一旦收到客户端的确认报文,就进入 ESTABLISHED 状态,可以进行读写数据了。
[ESTABLISHED -> CLOSE_WAIT]:当客户端主动关闭连接(调用close), 服务器会收到结束报文段, 服务器返回确认报文段并进入CLOSE_WAIT;
[CLOSE_WAIT -> LAST_ACK]:进入CLOSE_WAIT后说明服务器准备关闭连接(需要处理完之前的数据)。当服务器真正调用 close 关闭连接时, 会向客户端发送 FIN,此时服务器进入 LAST_ACK 状态,等待最后一个 ACK 到来(这个 ACK 是客户端确认收到了 FIN)。
[LAST_ACK -> CLOSED]:服务器收到了对 FIN 的 ACK,彻底关闭连接。
客户端状态转化: [CLOSED -> SYN_SENT]:客户端调用 connect,发送同步报文段。
[SYN_SENT -> ESTABLISHED]:connect 调用成功, 则进入 ESTABLISHED 状态,开始读写数据。
[ESTABLISHED -> FIN_WAIT_1]:客户端主动调用 close 时,向服务器发送结束报文段,同时进入 FIN_WAIT_1。
[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 状态。
下图是 TCP 状态转换的一个汇总:

-
较粗的虚线表示服务端的状态变化情况。
-
较粗的实线表示客户端的状态变化情况。
-
CLOSED 是一个假想的起始点,不是真实状态。
三次握手
TCP三次握手是建立连接的过程,主要目的是为了确保通信双方都有能力发送和接收数据,从而建立一个可靠的通信通道。三次握手的具体过程如下:
-
第一次握手:客户端向服务器发送一个SYN(synchronize)请求报文,表示客户端请求建立连接。这时,客户端进入"SYN_SENT"状态,等待服务器的响应。
-
第二次握手:服务器收到客户端的SYN请求后,确认接收到请求,向客户端发送一个带有SYN和ACK(acknowledge)标志的报文。这表示服务器同意建立连接,并请求对客户端进行确认。服务器进入"SYN_RCVD"状态。
-
第三次握手:客户端接收到服务器的SYN+ACK报文后,向服务器发送一个ACK报文,确认已接收到服务器的回复。此时客户端和服务器都进入"ESTABLISHED"状态,连接正式建立,接下来可以进行数据传输。
-
- 建立连接并不是百分百成功的,如果第三次应答失败了呢?

客户端把ACK发送出去以后,客户端就认为三次握手已经完成了,因为建立连接的本质就是在赌,赌最后一个ACK一定收到了。建立连接的本质不就是进行数据的传输吗?客户端认为三次握手做好了,于是就开始给服务端发数据,服务端收到数据以后,就想我还没有三次握手完毕呢,你怎么就给我发数据了,服务端就知道,一定是最后一次应答丢失了,于是服务端返回一个报文,将RST置为1,告诉客户端,三次握手失败了,你需要重新与我建立连接,也就是连接重置;
RST:reset连接重置标志位,收到该标志位的主机,要对异常连接重新释放,重新建立

在日常中也有可能遇见连接重置的情况

- 为什么选择三次握手而不是一次二次握手?
1.验证全双工---验证网络的连通性
2.建立双方通信的共识意愿
选择三次握手而非一次或二次握手的主要原因在于确保连接的可靠性和数据传输的准确性,避免资源浪费和网络拥塞。三次握手机制在网络通信中具有以下几个关键作用:
防止已失效的连接请求报文再次传送 若采用一次或二次握手,当客户端发送连接请求并在网络中遭遇延迟或丢失情况时,旧的请求报文可能在网络中滞留并被服务器错误接收,导致服务器进入连接状态而客户端并未实际参与,从而引发资源浪费。三次握手可以有效确认双方的状态是否都处于准备连接状态,以防止旧的报文误导连接建立。
双方确认通信能力和确认信息可靠性 三次握手过程中的双方响应和确认步骤能够确保双方都具备发送和接收能力,避免了仅靠一次或二次握手确认双方状态不对称的问题。例如,在三次握手中,客户端会先发送请求并等待服务器响应;服务器响应后,客户端再进行确认。这个过程确保了服务器和客户端都明确对方的在线状态,并建立了可靠的连接。
避免资源过早分配 通过三次握手,服务器能够等到确认客户端的确认消息后,才正式分配资源来建立连接,避免一次或二次握手中由于客户端故障或其他原因而造成的连接失败,这样服务器可以在未确认连接稳定之前不投入过多资源,提高资源利用率和系统效率。
三次握手是用最小的成本验证全双工通信信道是通畅的
- 四次握手可不可以?
可以,但没有必要,会降低效率;服务端就是把第二次握手的SYN和ACK合并发送,这种做法不仅节省了一个确认报文包,还提高了连接建立的效率;这种合并发送的应答叫做捎带应答

几个注意点:
-
connect 函数不参与底层的三次握手,connect 函数的作用只是发起三次握手。
-
accept返回分配新的描述符和客户端通信也不参与三次握手的过程
四次挥手
四次挥手(Four-Way Handshake)是TCP协议中用于断开连接的过程,与三次握手相对应。四次挥手的设计是为了确保双方在断开连接前,都能确认彼此的关闭请求,以确保数据完整、避免信息丢失。
四次挥手的过程如下:
1.客户端发送关闭请求(FIN) 当客户端(发起方)希望断开连接时,会发送一个带有FIN标志位的报文给服务器,表示其完成了数据发送并希望关闭连接。此时,客户端进入"FIN-WAIT-1
"状态,等待服务器的响应。
2.服务器确认客户端的关闭请求(ACK) 服务器收到客户端的FIN请求后,发送一个带ACK标志位的确认报文,表示同意关闭客户端的连接。此时,服务器的连接还没有完全关闭,它可能仍有数据要发送。客户端在收到ACK后进入"FIN-WAIT-2
"状态,等待服务器的关闭请求。
3.服务器发送自己的关闭请求(FIN) 服务器在确认数据发送完成后,向客户端发送带有FIN标志的报文,表示它的数据已发送完毕且准备关闭连接。服务器进入"LAST-ACK
"状态,等待客户端的最终确认。
4.客户端确认服务器的关闭请求(ACK) 客户端收到服务器的FIN报文后,发送ACK确认报文,确认双方已准备断开连接。此时客户端进入"TIME-WAIT
"状态,等待一段时间(通常是2MSL,即最大报文生存时间),确保服务器能收到ACK报文。若该时间内未收到服务器的重发请求,客户端则彻底关闭连接。服务器在收到客户端的最终ACK后,也关闭连接,断开过程完成。

- 设计四次挥手的原因
四次挥手的设计确保双方都能在断开前完成数据传输,避免中途信息丢失。由于TCP是全双工通信(即双方的发送和接收通道独立),所以需要每个方向都独立关闭:客户端和服务器各发送各自的FIN请求,并在确认对方的ACK后,才正式关闭通道。
- 为什么四次挥手里面不使用捎带应答?
四次挥手中不使用捎带应答的原因主要是为了保证双方的连接关闭流程的独立性 和数据传输的完整性。具体分析如下:
1.全双工连接的独立性 TCP连接是全双工的,即客户端和服务器各自的发送和接收通道是独立的。断开时,双方必须分别发送各自的关闭请求(FIN)和确认应答(ACK)。这样,即使一方希望关闭连接,另一方仍然可以继续发送数据,直到它的发送任务完成。四次挥手的设计保证了双向独立的确认过程:客户端和服务器分别确认各自的数据传输已完成,确保双方的连接关闭都是明确的,而捎带应答会破坏这种独立性。
2.确保数据传输完整性 在关闭连接前,双方都需要确保已完成的所有数据传输。捎带应答在三次握手中可以节省连接建立的确认步骤,但在连接关闭时则可能导致未传输的数据丢失。例如,如果服务器在确认客户端的FIN请求后立即发送自己的FIN而不单独发送ACK,则可能忽略客户端仍在接收的数据包。通过分步确认(四次挥手),每一方都能清楚地知道对方是否还在发送数据。
3.时间控制和可靠性保障 四次挥手中的TIME-WAIT状态用于确保服务器能收到客户端的最后ACK。如果使用捎带应答,连接关闭的顺序和时间控制会变得复杂,容易导致ACK丢失或重复FIN发送。此外,TIME-WAIT提供了一个缓冲时间,以便应对网络中的延迟情况,确保双方的关闭请求和确认响应均完整到达。
综上,四次挥手的设计确保了连接关闭时的双向独立性和数据完整性,不使用捎带应答是为了保证双方能够完全确认各自的数据发送与接收状态,避免连接关闭过程中的数据丢失或异常。
- TIME-WAIT 状态的意义
客户端的"TIME-WAIT"状态是一种安全措施,用于等待一定时间确保最后的ACK报文确实到达服务器。如果服务器未收到ACK,它会重发FIN,客户端在TIME-WAIT状态下可再发ACK,确保连接关闭的可靠性。
理解TIME_WAIT状态

现在做一个测试,首先启动server,然后启动client,然后使用ctrl+C使server终止,这时马上再运行server,结果是

这是因为,虽然server的应用程序终止了,但TCP协议层的连接并没有完全断开,因此不能再次监听同样的server端口,我们用netstat命令查看一下

-
TCP协议规定,主动关闭连接的一方要处于TIME_WAIT状态,等待两个MSL(maximum segment lifetime)的时间后才能回到CLOSED状态
-
我们使用Ctrl + C 终止了server,所以server是主动关闭连接的一方,在TIME_WAIT期间仍然不能再次监听同样的server端口
-
MSL在RFC1122中规定为两分钟,但是各操作系统的实现不同,在Ubuntu与centos7上默认配置是60s
-
可以通过
cat /proc/sys/net/ipv4/tcp_fin_timeout
查看msl的值

为什么TIME_WAIT的时间是2MSL?
使用setsockopt()就可以解决这个问题,选择optname为1,表示允许创建端口号相同但IP地址不同的多个socket描述符
setsockopt
是一个在 POSIX 兼容的操作系统中使用的系统调用,它用于设置套接字(socket)的选项。套接字是支持 TCP/IP 网络通信的端点,而 setsockopt
允许程序在套接字级别上配置各种选项,从而影响套接字的行为。

-
MSL是TCP报文的最大生存时间,因此TIME_WAIT持续在2MSL的话就能保证在两个传输方向上的尚未被接收或迟到的报文段都已经消失(否则服务器立刻重启,可能会收到来自上一个进程的迟到的数据,但是这种数据很可能是错误的)
-
同时也是在理论上保证最后一个报文可靠到达(假设最后一个 ACK 丢失,那么服务器会再重发一个FIN.这时虽然客户端的进程不在了,但是TCP 连接还在,仍然可以重发LAST_ACK);
-
解决TIME_WAIT状态引起的bind失败的方法
-
在server的TCP连接没有完全断开之前不允许重新监听,某些情况下可能是不合理的。
-
服务器需要处理非常大量的客户端的连接(每个连接的生存时间可能很短,但是每秒都有很大数量的客户端来请求)。
-
这个时候如果由服务端主动关闭连接(比如某些客户端不活跃,就需要被服务端主动清理掉),就会产生大量TIME_WAIT连接。
-
由于我们的请求数量很大,就可能导致TIME_WAIT的连接数很多,每个连接都会占用一个通信五元组(源ip,源端口,目的ip,目的端口,协议)。其中服务器的ip和端口和协议是固定的。如果新来的客户端连接的ip和端口号和TIME_WAIT占用的链接重复了,就会出现问题

修改我们之前的代码,这样就不会再出现绑定失败的错误了



理解CLOSE_WAIT状态
◉ 状态出现时机 :CLOSE_WAIT
状态出现在服务器端接收到客户端的FIN包并回应了ACK包之后,但在服务器发送自己的FIN包之前。
◉ 状态含义 :CLOSE_WAIT
状态表示服务器已经知道客户端想要关闭连接,但是服务器端还有未处理完的数据或者还需要做一些清理工作。在这个状态下,服务器端有一个任务:完成必要的清理工作,然后发送FIN包以关闭连接。
◉ 潜在问题 :如果服务器端在CLOSE_WAIT
状态停留时间过长,可能会导致资源泄露,因为虽然客户端已经关闭了连接,但服务器端仍然保持着这个连接的某些资源。如果大量的连接都处于CLOSE_WAIT
状态,可能会导致服务器资源耗尽。
◉ 解决方法 :通常,如果服务器端检测到CLOSE_WAIT
状态过多,应该检查代码中是否正确处理了连接关闭的逻辑。确保在接收到客户端的FIN包后,及时完成必要的清理工作,并正确发送FIN包以关闭连接。
我们测验一下,我们关闭服务器中的close

如果我们服务器卡顿,查一下是不是存在大量的close_wait状态,如果存在,这种情况就叫做文件描述符泄漏

当使用 netstat -apn | grep 8888
命令时,可能会看到连接处于 LAST_ACK
状态,这通常发生在以下情况
被动关闭方 : 当服务器(或被动关闭方)已经发送了 FIN
并收到了客户端(或主动关闭方)的 ACK
时,它会进入 LAST_ACK
状态。此时,服务器正在等待来自客户端的最后一个 ACK
确认,以完全关闭连接。

小结:对于服务器上出现大量的CLOSE_WAIT状态,原因就是服务器没有正确的关闭socket,导致四次挥手没有正确完成.这是一个 BUG. 只需要加上对应的 close 即可解决问题.