xiaolin coding 图解网络笔记——TCP篇
1. TCP 头格式有哪些?
序列号:在建立连接时由计算机生成的随机数作为其初始值,通过 SYN 包传给接收端主机,每发送一次数据,就【累加】一次该【数据字节数】的大小。用来解决网络包乱序问题。
确认应答号:指下一次【期望】收到的数据的序列号,发送端收到这个确认应答以后可以认为在这个序号以前的数据都已经被正常接收。用来解决丢包的问题。
控制位:
ACK
:该位为1
时,【确认应答】的字段变为有效,TCP 规定除了最初建立连接时的SYN
包之外该位必须设置为1
。RST
:为1
时,表示 TCP 连接中出现异常必须强制断开连接。SYN
:为1
时,表示希望建立连接,并在其【序列号】的字段进行序列号初始值的设定。FIN
:为1
时,表示今后不会再有数据发送,希望断开连接。当通信结束希望断开连接时,通信双方的主机之间就可以相互交换FIN
位为 1 的 TCP 段。
2. 为什么需要 TCP 协议?TCP 工作在哪一层?
IP
层【不可靠】,它不保证网络包的按序交付、不保证网络包中的数据的完整性,如果需要保障数据网络包的可靠性,就需要传输层的 TCP
协议来负责。
因为 TCP 是一个工作在传输层的可靠数据传输的服务,它能确保接收端接收的网络包是无损坏、无间隔、非冗余和按序的。
3. 什么是 TCP?
TCP 是面向连接的、可靠地、基于字节流的传输层通信协议。
- 面向连接:一对一才能连接,不像 UDP 协议可以一对多发送信息。
- 可靠地:无论网络链路发生怎样的变化,TCP 都可以保证一个报文一定能够到达接收端。(可靠性足够高,但极端情况仍会传输失败)
- 字节流:通过 TCP 协议传输时,消息可能会被【分组】成多个的 TCP 报文,并且 TCP 报文是【有序的】。(面向流的服务,而不是面向消息的服务,意味着数据是连续流动的而不是离散的数据包形式,接收端接收到分割的数据包——TCP segments,会进行重新组装以恢复原始的字节流)。
4. 什么是建立一个 TCP 连接?
建立一个 TCP 连接是需要客户端与服务端达成上述三个信息的共识。
- Socket:由 IP 地址和端口号组成
- 序列号: 用来解决乱序问题
- 窗口大小:用来做流量控制
5. 如何唯一确定一个 TCP 连接呢?
TCP 四元组可以唯一确定一个 TCP 连接:
【源地址,源端口,目的地址,目的端口】
地址字段(32位)是在 IP 头部中,作用是通过 IP 协议发送报文给对方主机。
端口字段(16位)是在 TCP 头部中,作用是告诉 TCP 协议应该把报文发给哪个进程。
最大 TCP 连接数 = 客户端的 IP 数 X 客户端的端口数(理论上,实际上一个客户端通常只有一个网络分配的 IP 地址,某些情况可以用虚拟网络或负载均衡技术得到多个 IP 地址),服务端通常是固定在某个本地端口上监听。
服务端最大并发 TCP 连接数远不能达到理论上限,会受到以下因素影响:
- 文件描述符限制:每个 TCP 连接都是一个文件,如果文件描述符被占满了,会发生 Too many open files。Linux 限制 系统级、用户级、进程级的可打开的文件描述符数量。
- 内存限制:每个 TCP 连接都要占用一定内存,操作系统的内存有限,被占满后会发生 OOM。
6. UDF 和 TCP 的区别,分别的应用场景?
UDP 不提供复杂的控制机制,利用 IP 提供面向【无连接】的通信服务,其头部只有 8
个字节(64位)。
- 目标和源端口:告诉 UDP 协议应该把报文发给哪个进程。
- 包长度:保存了 UDP 首部的长度和数据的长度之和。
- 校验和:提供可靠的 UDP 首部和数据而设计,防止收到在网络传输中受损的 UDP 包。
TCP 和 UDP 区别:
- 连接:
- TCP 是面向连接的传输层协议,传输数据前要先建立连接。
- UDP 是不需要连接,即可传输数据。
- 服务对象:
- TCP 是一对一的两点服务。
- UDP 支持 一对一、一对多、多对多的交互通信。
- 可靠性:
- TCP 是可靠交付数据的,数据可以无差错、不丢失、不重复、按序到达。
- UDP 是尽最大努力交付,不保证可靠交付数据。但是可以基于 UDP 传输协议实现一个可靠地传输协议,比如 QUIC 协议。
- 拥塞控制、流量控制:
- TCP 有拥塞控制和流量控制机制,保证数据传输的安全性。
- UDP 则没有,即使网络非常拥堵,也不会影响 UDP 的发送速率。
- 首部开销:
- TCP 首部长度较长,首部在没有使用【选项】字段时是
20
个字节。 - UDP 首部只有 8 个字节,并且固定不变,开销较小。
- 传输方式:
- TCP 是流式传输,没有边界,但保证顺序和可靠。
- UDP 是一个包一个包的发送,是有边界的,但可能会丢包和乱序。
- 分片不同:
- TCP 的数据大小如果大于 MSS 大小,则会在传输层分片,目标主机收到后也同样在传输层组装包,如果中途丢失分片,只需要传输丢失的这个分片(TCP 段)。(这里与按序传输不冲突,按序是指一个TCP段的字节流是按序的)
- UDP 的数据大小如果大于 MTU 大小,则会在 IP 层进行分片,目标主机收到后在 IP 层组装数据,。
也就是说TCP在传输层分片,UDP在IP层分片,接收方也在这个层组装。
TCP 和 UDP 应用场景:
TCP 面向连接,能保证数据的可靠性交付,常用于:
FTP
文件传输(通过 FTP 文件传输协议传输的文件)- HTTP / HTTPS
UDP 面向无连接,可以随时发送数据,其本身的处理既简单又高效,因此经常用于:
- 包总量较少的通信,如
DNS
、SNMP
等 - 视频、音频等多媒体通信;
- 广播通信
为什么 UDP 头部没有【首部长度】字段,而 TCP 头部有?
TCP 有可变长的【选项】字段,而 UDP 头部长度不变化,所以不需要记录首部长度。
TCP 和 UDP 可以使用同一个端口吗?
可以。
传输层的 【端口号】的作用,是为了区分同一个主机上不同应用程序的数据包。传输层有两个传输协议分别是 TCP 和 UDP ,在内核中这两个是完全独立的软件模块。
7. TCP 三次握手过程是怎样的?
TCP 是面向连接的协议,所以使用 TCP 前必须先建立连接,建立连接是通过三次握手来进行的。
- 一开始,客户端和服务端都处于
CLOSE
状态。先是服务端主动监听某个端口,处于LISTEN
状态。 - 客户端会随机初始化序列号(
client_isn
),将此序号置于 TCP 首部的 【序号】字段中,同时把SYN
标志位置为1
,表示SYN
报文。接着把第一个 SYN 报文发送给服务端,表示向服务端发起连接,该报文不包含应用层数据,之后客户端处于SYN-SENT
状态。 - 服务端收到客户端的
SYN
报文后,首先服务端也随机初始化自己的序号(server_isn
),将此序号填入 TCP 首部的【序号】字段中,其次把 TCP 首部的 【确认应答号】字段填入client_isn + 1
,接着把SYN
和ACK
标志位置为1
。最后把该报文发给客户端,该报文也不包含应用层数据,之后服务端处于SYN-RCVD
状态。 - 客户端收到服务端报文后,还要向服务端回应最后一个应答报文,首先该应答报文 TCP 首部
ACK
标志位为1
,其次【确认应答号】字段填入server_isn + 1
,最后把报文发送给服务端,这次报文可以携带客户到服务端的数据,之后客户端处于ESTABLISHED
状态。 - 服务端收到客户端的应答报文后,也进入
ESTABLISHED
状态。
前两次握手是不可以携带数据的,第三次握手是可以携带数据的。
8. 如何在 Linux 系统中查看 TCP 状态?
netstat -napt
9. 为什么是三次握手?不是两次、四次?
TCP连接是包括 Socket、序列号和窗口大小称为连接,三次握手的首要原因是为了防止旧的重复连接初始化造成混乱,两次握手连接的情况下,服务端没有中间状态给客户端来阻止历史连接,因为两次握手时服务端收到 SYN 报文直接进入 ESTABLISHED 状态,但是客户端并没有进入,所以会建立一个历史连接造成资源浪费。所以要解决这个现象最好就是在服务端发送数据前,也就是建立连接之前,阻止掉历史连接。
此外,三次握手能帮助双方同步初始化序列号,而三次握手理论上最少可靠连接建立,因此不需要四次握手等更多的通信次数。
10. 为什么每次建立 TCP 连接时,初始化的序列号都要求不一样呢?
- 为了防止历史报文被下一个相同四元组的连接接收(比如先建立了一个 TCP 连接,然后在客户端发送数据包的时候被网络阻塞了,然后超时重传,此时服务端断电重启了,建立的连接就消失了,于是在收到客户端的数据包的时候就会发送 RST 报文,紧接着客户端又建立了一个相同四元组的连接,在新连接完成后,上一个被网络阻塞的数据正好抵达了服务端,刚好序列号是在服务端接收窗口内,被正常接收,造成数据错乱)。
- 为了安全性,防止黑客伪造相同序列号的 TCP 报文被对方接收。
11. 初始序列号 ISN 是如何随机产生的?
起始 ISN
是基于时钟的,每 4 微秒 + 1,转一圈要 4.55 个小时。
ISN = M + F(localhost, localport, remotehost, remoteport)
M
是一个计时器,每隔 4 微秒加 1.
F
是一个 Hash 算法,根据源 IP、目的 IP、源端口、目的端口生成一个随机数值。
随机数会基于时钟计时器递增,所以基本不可能会随机成一样的初始化序列号。
12. 既然 IP 层会分片,为什么 TCP 层还需要 MSS?
MTU
:一个网络包的最大长度,以太网中一般为1500
字节;MSS
:除去 IP 和 TCP 头部之后,一个网络包所能容纳的 TCP 数据的最大长度;
如果把整个报文交给 IP 层进行分片,那么当一个 IP 分片丢失,整个 IP 报文的所有分片都得重传,因为 IP 层由传输层的 TCP 来负责超时和重传,这是非常没有效率的。
所以,为了达到最佳的传输效能,TCP 协议在建立连接的时候通常要协商双方的 MSS 值,在 TCP 层对超过 MSS 的数据进行分片后,由它形成的 IP 包长度自然也不用 IP 分片。
这样,如果一个 TCP 分片丢失后,进行重发时是以 MSS 为单位,大大增加了重传的效率。
13. 第一次握手丢失了,会发生什么?
客户端发送 SYN 报文,进入到 SYN_SENT
状态,迟迟收不到服务端的 SYN-ACK 报文,触发超时重传,而且重传的 SYN 报文的序列号都是一样的,最大重传次数由内核参数控制,默认值一般为 5,每次超时的时间是上一次的一倍,1,2,4,8,16,32,如果仍然没有收到 ACK,断开 TCP 连接。
14. 第二次握手丢失了,会发生什么?
服务端收到第一次握手后,回 SYN-ACK 报文,服务端进入 SYN_RCVD
状态,第二次握手里的 ACK 是对第一次握手的确认报文,第二次握手里的 SYN 是对服务端发起建立 TCP 连接的报文。
如果第二次握手丢失了,那么客户端会认为 SYN 报文丢失,所以客户端会触发超时重传,重传 SYN 报文,然后服务端也收不到第三次握手,服务端会触发超时重传,重传 SYN-ACK 报文。
15. 第三次握手丢失了,会发生什么?
客户端收到服务端的 SYN-ACK 报文后,会给服务端回一个 ACK 报文,也就是第三次握手,此时客户端进入 ESTABLISH
阶段,如果第三次握手丢失了,服务端会触发超时重传,直到收到第三次握手,ACK 报文是不会有重传的,当 ACK 丢失了,就由对方重传对应的报文。
16. 什么是 SYN 攻击?如何避免 SYN 攻击?
假设攻击者短时间伪造不同 IP 地址的 SYN
报文,服务端每接收到一个 SYN
报文,就进入 SYN_RCVD
状态,但服务端发送出去的 ACK + SYN
报文,无法得到未知 IP 主机的 ACK
应答,久而久之就会占满服务端的半连接队列,使得服务端不能为正常用户服务。
在 TCP 三次握手的时候,Linux 内核会维护两个队列,分别是:
- 半连接队列,也称 SYN 队列
- 全连接队列,也称 Accept 队列
正常流程:
- 服务端收到客户端的 SYN,创建一个半连接的对象,加入到 SYN 队列
- 接着发送 SYN + ACK,等待回应 ACK 报文
- 服务端接收到 ACK 报文后,从 SYN 队列取出一个半连接对象,然后创建一个新的连接对象放入到 Accept 队列
- 应用通过调用
accept()
socket 接口,从 Accept 队列取出连接对象。
半连接队列和全连接队列都有最大长度限制,超过限制时,默认情况都会丢弃报文,因此, SYN 攻击方式最直接的表现就是把半连接队列打满,这样后续的 SYN 报文就会丢弃。
避免 SYN 攻击,有以下几种方法:
- 调大
netdev_max_backlog
:当网卡接收数据包的速度大于内核处理的速度时,会有一个队列保存这些数据包,可以调大控制该队列的参数的值。 - 增大 TCP 半连接队列,这要求同时增大这三个参数:
net.ipv4.tcp_max_syn_backlog
、listen()
函数中的backlog
、net.core.somaxconn
- 开启
net.ipv4.tcp_syncookies
,开启 syncookies 功能就可以在不使用 SYN 半连接队列的情况下建立连接。具体过程是当 SYN 队列满后,后续服务端收到 SYN 包,不会丢弃,而是根据算法计算出一个cookie
值,将 cookie 值放到第二次握手报文的【序列号】里,然后服务端回二次握手给客户端。服务端接收到客户端的应答报文时,服务端会检查这个 ACK 包的合法性。如果合法,将该连接对象放入到 Accept 队列,最后应用程序通过调用accept()
接口,从 Accpet 队列取出连接。net.ipv4.tcp_syncookies
参数主要有以下三个值:0 值,表示关闭该功能;1 值,表示仅当 SYN 半连接队列放不下时再启用;2 值,表示无条件开启功能。实际上就是,如果服务器资源充足,那么就是按本身的三次握手方式来做,如果在高负载情况下,服务器会利用 SYN Cookies,使得允许在半连接队列满时,仍然能够处理新的 SYN 报文,而不是简单丢弃。$ echo 1 > /proc/sys/net/ipv4/tcp_syncookies
。 - 减少 SYN + ACK 重传次数,当服务器受到 SYN 攻击时,就会有大量处于 SYN_REVC 状态的 TCP 连接,服务器会重传 SYN + ACK,那么针对 SYN 攻击的场景,可以减少 SYN-ACK 的重传次数,以加快处于 SYN_REVC 状态的 TCP 连接断开。
$ echo 2 > /proc/sys/net/ipv4/tcp_synack_retries
。
17. TCP 四次挥手过程是怎样的?
- 客户端打算关闭连接,此时会发送一个 TCP 首部
FIN
标志位为1
的报文,之后客户端进入FIN_WAIT_1
状态。 - 服务端收到该报文后,就向客户端发送
ACK
应答报文,接着服务端进入CLOSE_WAIT
状态。 - 客户端收到服务端的
ACK
应答报文后,之后进入FIN_WAIT_2
状态。 - 等待服务端处理完数据后,也向客户端发送
FIN
报文,之后服务端进入LAST_ACK
状态。 - 客户端收到服务端的
FIN
报文后,回一个ACK
应答报文,之后进入了TIME_WAIT
状态。 - 服务端收到了
ACK
应答报文后,就进入了CLOSE
状态,至此服务端已经完成连接的关闭。 - 客户端在经过
2MSL
一段时间后,自动进入CLOSE
状态,至此客户端也完成连接的关闭。 - 注意,主动关闭连接的,才有
TIME_WAIT
状态
18. 为什么挥手需要四次?
服务端通常需要等待完成数据的发送和处理,所以服务端的 ACK
和 FIN
一般都会分开发送,因此是需要四次挥手。在被动关闭方在 TCP 回收过程中,【没有数据要发送】并且 【开启了 TCP 延迟确认机制】,那么第二和第三次挥手就会合并传输,这样就出现了三次挥手,FIN
,FIN-ACK
,ACK
。
19. 第一次挥手丢失了,会发生什么?
客户端调用 close 函数后,就会向服务端发送 FIN 报文,如果第一次挥手丢失了,那么客户端迟迟收不到被动方的 ACK 的话,就会触发超时重传机制。
20. 第二次挥手丢失了,会发生什么?
服务端收到客户端的第一次挥手后,就会先回一个 ACK 确认报文,此时服务端的连接进入到 CLOSE_WAIT
状态。如果服务端的第二次挥手丢失了,由于 ACK 报文是不会重传的,因此仍是客户端触发超时重传机制。
这里提示,如果主动关闭方使用 close 函数关闭连接,无法再发送和接收数据,所以 FIN_WAIT2
状态不能持续太久,如果 60秒(默认值)还没收到第三次挥手的 FIN 报文,那么主动关闭方的连接会直接关闭。
如果主动关闭方使用 shutdown 函数关闭连接,指定了只关闭发送方向,而接收方向没有关闭,那么如果一直没收到第三次挥手,那么主动方的连接会一直处于FIN_WAIT2
状态。
21. 第三次挥手丢失了,会发生什么?
当服务端收到客户端的 FIN 后,内核自动发送 ACK,同时连接处于 CLOSE_WAIT
状态,表示等待应用进程调用 close 函数关闭连接,当进程调用 close 函数后,内核就会发出 FIN 报文,同时连接进入 LAST_ACK 状态,等待客户端返回 ACK 来确认连接关闭。
如果迟迟收不到 ACK,服务端就会重发 FIN 报文。
22. 第四次挥手丢失了,会发生什么?
如果客户端收到服务端的第三次挥手的 FIN 报文,会回 ACK 报文,也就是第四次挥手,此时客户端进入 TIME_WAIT
状态,在 Linux 系统, TIME_WAIT 状态会持续 2MSL 后才会进入关闭状态。服务端如果没有收到 ACK 报文,那么会超时重传 FIN 报文。
23. 为什么 TIME_WAIT 等待的时间是 2MSL?
MSL
是 Maximum Segment Lifetime,报文最大生存时间,2MSL时长其实是相当于至少允许报文丢失一次,时间是从客户端接收到 FIN 后发送 ACK 开始计时的,如果又接收到服务端重发的 FIN 报文,那么2MSL 时间将重新计时。
24. 为什么需要 TIME_WAIT 状态?
主动发起关闭连接的一方,才会有 TIME-WAIT
状态,需要这个状态主要是两个原因:
- 防止历史连接中的数据,被后面相同四元组的连接错误地接收
序列号是 TCP 一个头部字段,是 TCP 为每个传输方向上的每个字节都赋予的一个编号,以便传输成功后确认、丢失后重传以及在接收端保证不会乱序。序列号是一个 32 位的无符号数,因此在到达 4G 之后再循环回到 0。
初始化序列号是在 TCP 建立连接的时候,客户端和服务端都会生成的一个初始序列号,序列号和初始化序列号并不是无限递增的,会发生回绕为初始值的情况,这意味着无法根据序列号来判断新老数据。
所以如果 TIME_WAIT 没有等待时间或者等待时间过短,那么有可能出现:服务端在关闭连接之前发送的 SEQ = 301
报文被网络延迟了,接着,服务端以相同的四元组重新打开了新连接,前面被延迟的 SEQ = 301
这时抵达了客户端,而且该数据报文的序列号刚好在客户端接收窗口内,因此造成了数据错乱。TIME_WAIT 状态的时间足以让两个方向上的数据包都被丢弃,使得原来连接的数据包在网络中都自然消失,再出现的数据包一定都是新建立连接所产生的。
- 保证【被动关闭连接】的一方,能被正确的关闭
TIME-WAIT 等待足够的时间以确保最后的 ACK 能让被动关闭方接收,从而帮助其正常关闭,因为如果客户端没有 TIME_WAIT,而是在发完最后一次 ACK 就进入 CLOSE 状态,如果 ACK 报文丢失了,服务器重传 FIN,这是客户端已经进入关闭了,就会回 RST 报文,服务端收到并将其解释为一个错误,这对于可靠的协议不是一个好的终止方式。
25. TIME_WAIT 过多有什么危害?
过多的 TIME-WAIT 状态主要有两种危害:第一是占用系统资源,比如文件描述符、内存资源、CPU 资源、线程资源等;第二是占用端口资源,端口资源也是有限的,一般可以开启的端口为32768-61000
,也可以通过net.ipv4.ip_local_port_range
参数指定范围。
如果客户端(主动发起关闭连接方)的 TIME_WAIT 状态过多,沾满了所有端口资源,那么就受到了端口资源限制,无法再跟【目的 IP + 目的 PORT】都一样的服务端建立连接了,但只要连接的是不同服务器,端口是可以重新使用的,因为内核定位连接是根据四元组信息定位。
如果服务端(主动发起关闭连接方)的 TIME_WAIT 状态过多,由于只监听一个端口,不会导致端口受限,但是 TCP 连接过多会占用系统资源,比如文件描述符、内存资源、CPU 资源、线程资源等。
26. 如何优化 TIME_WAIT?
- 打开
net.ipv4.tcp_tw_reuse
和tcp_timestamps
,这样的 Linux 内核参数开启后,则可以复用处于 TIME_WAIT 的 socket 为新的连接所用。 net.ipv4.tcp_max_tw_buckets
,默认值为 18000,当系统中处于 TIME_WAIT 的连接一旦超过这个值,系统就会将后面的 TIME_WAIT 连接状态重置。- 程序中使用
SO_LINGER
,可以通过设置 socket 选项,来设置调用 close 关闭连接行为,相当于跳过TIME_WAIT
状态。
TIME_WAIT 是有助于连接的,上面的方法都试图越过 TIME_WAIT,是不太好的,如果服务端要避免过多的 TIME_WAIT,就不要主动断开连接,把 TIME_WAIT 让客户端承受。
27. 服务器出现大量 TIME_WAIT 状态的原因有哪些?
服务端主动断开连接:
- HTTP 没有使用长连接
从 HTTP/1.1 开始,默认开启了 Keep-Alive,也就是 HTTP 长连接机制,当客户端和服务端任意一方的 HTTP header 中有 Connection:close
信息,那么就无法使用 HTTP 长连接的机制,在关闭长连接机制后,每次请求都需要经历,建立 TCP -> 请求资源 -> 响应资源 -> 释放连接,也就是 HTTP 短连接。根据大多数 Web 服务的实现,不管哪一方禁用了长连接,都是由服务器主动关闭连接。因此,当服务端出现大量的 TIME_WAIT 状态连接的时候,可以排查下是否客户端和服务端都开启了 HTTP Keep-Alive。
- HTTP 长连接超时
为了避免资源浪费的情况,web 服务软件一般都会提供一个参数,用来指定 HTTP 长连接的超时时间,比如 nginx 提供的 keepalive_timeout 参数。假设设置了 HTTP 长连接的超时时间是 60 秒,nginx 就会启动一个【定时器】,如果客户端在完成一个 HTTP 请求后,在 60 秒内都没有再发起新的请求,定时器的时间一到,nginx 就会触发回调函数来关闭该连接,此时服务端上就会出现 TIME_WAIT 状态的连接。所以可以往网络问题的方向排查,比如是否是因为网络问题,导致客户端发送的数据一直没有被服务端接收到,以至于 HTTP 长连接超时。
- HTTP 长连接的请求数量达到上限
Web 服务端通常会有个参数,来定义一条 HTTP 长连接上最大能处理的请求数量,当超过最大限制时,就会主动关闭,比如 nginx 的 keepalive_requests,默认值是 100,对于一些 QPS(每秒请求数)比较高的场景,比如超过 10000 QPS,这个时候 nginx 就会很频繁的关闭连接,此时服务端会出现大量的 TIME_WAIT 状态。
28. 服务器出现大量 CLOSE_WAIT 状态的原因有哪些?
CLOSE_WAIT 状态是 【被动关闭方】才会有的状态,而且如果【被动关闭方】没有调用 close 函数关闭连接,那么就无法发出 FIN 报文,从而无法使得 CLOSE_WAIT 状态的连接转变为 LAST_ACK 状态。
所以,当服务端出现大量 CLOSE_WAIT 状态的连接的时候,说明服务端的程序没有调用 close 函数关闭连接。
一个普通的 TCP 服务端的流程:
- 创建服务端 socket,bind 绑定端口,listen 监听端口
- 将服务端 socket 注册到 epoll
- epoll_wait 等待连接到来,连接到来时,调用 accept 获取已连接的 socket
- 将已连接的 socket 注册到 epoll
- epoll_wait 等待事件发生
- 对方连接关闭时,我方调用 close
导致服务端没有调用 close 函数的原因可能有:
- 第 2 步没有做,没有将服务端 socket 注册到 epoll,这样有新连接到来时,服务端没办法感知这个事件,也就无法获取到已连接的 socket,这种原因发生概率较小,属于明显的代码逻辑 bug
- 第 3 步没有做,有新连接到来时没有调用 accept 获取该连接的 socket,导致当有大量的客户端主动断开了连接,而服务端没机会对这些 socket 调用 close 函数。发生这种情况可能是因为服务端在执行 accept 函数之前,代码卡在某一个逻辑或者提前抛出了异常。
- 第 4 步没有做,没有将已连接的 socket 注册到 epoll,导致后续收到 FIN 报文的时候服务端没办法感知这个事件
- 第 6 步没有做,当发现客户端关闭连接后,可能是代码漏处理,或者是在执行 close 之前,代码卡在某一个逻辑,比如发生死锁。
当服务端出现大量 CLOSE_WAIT 状态的连接的时候,通常都是代码的问题,这时候我们需要针对具体的代码一步一步的排查定位。
29. 如果已经建立了连接,但是客户端突然出现了故障怎么办?
服务端无法感知到客户端宕机,所以服务端的 TCP 连接将一直处于 ESTABLISH
状态,占用着系统资源。为了避免这种情况,TCP 搞了个保活机制,在这个时间段内,如果没有任何连接相关的活动,那么 TCP 保活机制会每隔一个时间间隔,发送一个探测报文,如果连续几个探测报文都没有得到响应,则认为当前的 TCP 连接已经死亡。
在 Linux 内核可以有对应的参数可以设置保活时间、保活探测的次数、保活探测的时间间隔,以下都为默认值:
net.ipv4.tcp_keepalive_time=7200
net.ipv4.tcp_keepalive_intvl=75
net.ipv4.tcp_keepalive_probes=9
用用程序如果想使用 TCP 保活机制需要通过 socket 接口设置 SO_KEEPALIVE
选项才能够生效。
TCP 保活这个机制检测时间比较长,因此可以自己在应用层实现一个心跳机制,比如 web 服务软件提供的 keepalive_timeout
参数,用来指定 HTTP 长连接的超时时间。
30. 如果已经建立了连接,但是服务端的进程崩溃会发生什么?
TCP 的连接信息是由内核维护的,所以当服务端的进程崩溃后,内核需要回收该进程的所有 TCP 连接资源,于是内核会发送第一次挥手 FIN 报文,后续的挥手过程也都是在内核完成,并不需要进程的参与,所以即使服务端的进程退出了,还是能与客户端完成 TCP 四次挥手的过程。
比如,使用 kill -9 来模拟进程崩溃的情况,在 kill 掉进程后,服务端会发送 FIN 报文,与客户端进行四次挥手。
31. 针对 TCP 应该如何 Socket 编程?
- 服务端和客户端初始化
socket
,得到文件描述符; - 服务端调用
bind
,将 socket 绑定在指定的 IP 地址和端口; - 服务端调用
listen
,进行监听; - 服务端调用
accept
,等待客户端连接; - 客户端调用
connect
,向服务端的地址和端口发起连接请求; - 服务端
accept
返回用于传输的socket
的文件描述符; - 客户端调用
write
写入数据;服务端调用read
读取数据; - 客户端断开连接时,会调用
close
,那么服务端read
读取数据,就会读取到了EOF
,待处理完数据后,服务端调用close
,表示连接关闭。
这里需要注意的是,服务端调用 accept
时,连接成功了会返回一个已完成连接的 socket,后续用来传输数据。
所以,监听的 socket 和真正用来传送数据的 socket,是【两个】socket,一个叫做监听 socket,一个叫做已完成连接 socket。
32. listen 时候参数 backlog 的意义?
Linux 内核会维护两个队列:
- 半连接队列(SYN 队列):接收到一个 SYN 建立连接请求,处于 SYN_RCVD 状态;
- 全连接队列(Accept 队列):已完成 TCP 三次握手过程,处于 ESTABLISHED 状态;
int listen (int socketfd, int backlog)
,参数一 socketfd 为 socketfd 文件描述符,backlog 是 accept 队列,但是上限值是内核参数 somaxconn 的大小,也就说 accept 队列长度 = min(backlog, somaxconn)。
33. accept 发生在三次握手的哪一步?
客户端 connect 成功返回是在第二次握手成功以后,服务端 accept 成功返回是在第三次握手成功以后。
34. 客户端调用 close 了,连接断开的流程是什么?
- 客户端调用
close
,此时会发送 FIN,进入 FIN_WAIT_1 状态 - 服务端收到 FIN 报文,TCP 协议栈会为 FIN 包插入一个文件结束符
EOF
到接收缓冲区,应用程序可以通过read
调用来感知这个 FIN 包。这个EOF
会被放在已排队等候的其他已接收的数据之后,这就意味着服务端需要处理这种异常情况,因为 EOF 表示在该连接上再无额外数据到达。服务端进入 CLOSE_WAIT 状态。 - 接着,当处理完数据后,自然会读到
EOF
,于是也调用close
关闭它的套接字,这会使得服务端发出一个 FIN 包,之后处于 LAST_ACK 状态; - 客户端接收到服务端的 FIN 包,并发送 ACK 确认包给服务端,此时客户端将进入 TIME_WAIT 状态;
- 服务端收到 ACK 确认包后,就进入了最后的 CLOSE 状态;
- 客户端经过
2MSL
时间之后,也进入 CLOSE 状态。
35. 没有 accept,能建立 TCP 连接吗?
可以,accept 系统调用并不参与 TCP 三次握手过程,它只是负责从 TCP 全连接队列中取出一个已经建立连接的 socket,用户层通过 accept 系统调用拿到了已经建立连接的 socket,就可以对该 socket 进行读写操作了。相当于accept 系统是用于握手完成以后从全连接队列取出已建立的连接的,不影响连接的握手过程。
36. 没有 listen,能建立 TCP 连接吗?
客户端是可以自己连自己的形成连接(TCP 自连接),也可以两个客户端同时向对方发出请求建立连接(TCP 同时打开),这两个情况都没有服务器参与,也就是没有 listen。
相关文章:
xiaolin coding 图解网络笔记——TCP篇
1. TCP 头格式有哪些? 序列号:在建立连接时由计算机生成的随机数作为其初始值,通过 SYN 包传给接收端主机,每发送一次数据,就【累加】一次该【数据字节数】的大小。用来解决网络包乱序问题。 确认应答号:指…...
基于大数据python 豆果美食推荐数据可视化系统(源码+LW+部署讲解+数据库+ppt)
!!!!!!!!! 很对人不知道选题怎么选 不清楚自己适合做哪块内容 都可以免费来问我 避免后期給自己答辩找麻烦 增加难度(部分学校只有一次答辩机会 没弄好就延迟…...
ElasticSearch通过es-head插件安装可视化及相关问题
1.es-head下载地址 GitHub - mobz/elasticsearch-head: A web front end for an elastic search cluster 2.启动 建议使用vscode启动,并安装好node.js环境 npm installnpm run start 通过http://localhost:9100就可以看到本地添加的es库 3.相关问题 3.1跨域问…...
JVM系列之OOM观测准备
OOM, 全称 “Out Of Memory”,即内存用完的意思。JVM 因为没有足够的内存来为对象分配空间并且垃圾回收器也已经没有空间可回收时(可分配内存大于需要分配的内存), 就会抛出 java.lang.OutOfMemoryError。在实际的生产应用中,一旦…...
基于Java Springboot Vue3图书管理系统
一、作品包含 源码数据库设计文档万字全套环境和工具资源部署教程 二、项目技术 前端技术:Html、Css、Js、Vue3、Element-ui 数据库:MySQL 后端技术:Java、Spring Boot、MyBatis 三、运行环境 开发工具:IDEA 数据库&#x…...
解析生成对抗网络(GAN):原理与应用
目录 一、引言 二、生成对抗网络原理 (一)基本架构 (二)训练过程 三、生成对抗网络的应用 (一)图像生成 无条件图像生成: (二)数据增强 (三ÿ…...
torch.maximum函数介绍
torch.maximum 函数介绍 定义:torch.maximum(input, other) 返回两个张量的逐元素最大值。 输入参数: input: 张量,表示第一个输入。other: 张量或标量,表示第二个输入。若为张量,其形状需要能与 input 广播。输出&a…...
Git | 理解团队合作中Git分支的合并操作
合并操作 团队合作中Git分支的合并操作分支合并过程1.创建feature/A分支的过程2. 创建分支feature/A-COPY3.合并分支查看代码是否改变 团队合作中Git分支的合并操作 需求 假设团队项目中的主分支是main,团队成员A基于主分支main创建了feature/A,而我又在团队成员A创…...
源代码定制编译:构建理想的库 以curl为例
文章目录 源代码curl开发环境下载地址制定理想的库初级进阶如何知道选项名称交叉编译交叉编译工具链配置编译环境设置目标架构库和头文件路径编译代码 源代码 我们在日常中会接触到比较多第三方库,比如 网络库相关: libevent、mongoose、curl图形界面&…...
服务熔断-熔断器设计
文章目录 服务为什么需要熔断熔断器设计思想熔断器代码实现 服务为什么需要熔断 对于服务端采用的保护机制为服务限流。 对于服务调用端是否存在保护机制? 假如要发布一个服务 B,而服务 B 又依赖服务 C,当一个服务 A 来调用服务 B 时&#x…...
生产环境中:Flume 与 Prometheus 集成
在生产环境中,将 Apache Flume 与 Prometheus 集成的过程,需要借助 JMX Exporter 或 HTTP Exporter 来将 Flume 的监控数据转换为 Prometheus 格式。以下是详细的实现方法,连同原理和原因进行逐步解释,让刚接触的初学者也能完成集…...
深度解析MySQL的刷脏机制
前言 今天天气挺好,看大家对MySQL系列这么感兴趣,继续来聊聊MySQL,在MySQL的InnoDB中Buffer Pool和LSN关系紧密相连,尤其在脏页管理和刷新过程中起着至关重要的作用。理解LSN如何同Buffer Pool协同工作,有助于深入掌握 MySQL 的写入优化机制及数据一致性保证。 Buffer P…...
Java NIO 全面详解:初学者入门指南
除了前一篇文章讲的传统的 java.io 模块,Java 还提供了更现代化、更高效的非阻塞 IO 模块,即 java.nio(New IO)。java.nio 引入了面向缓冲区(Buffer)的数据处理方式,以及多路复用器(…...
优化 Conda 下载速度:详细的代理配置和网络管理策略
优化 Conda 下载速度:详细的代理配置和网络管理策略 为了彻底解决使用 Conda 下载 PyTorch 时遇到的速度问题,并确保下载过程稳定可靠,这需要一个详细、综合的技术方案。让我们更深入地分析问题原因,然后详尽地解释采取的解决策略…...
蓝牙MCU单片机8k高回报率无线应用
随着高端无线产品性能大幅提升,相比常规蓝牙133Hz回报率,8kHz回报率作为市场最高标准,每秒上传8000个数据包。以鼠标为例,位置每秒更新8000次,刷新率较常规蓝牙提升了60倍,超低延迟、极速响应,已…...
Java抛出自定义运行运行
1.重新生成异常的.java文件 Empty:空 Exception:异常 加起来就是 空指针异常的文件 2.打上extends 运行的异常(异常的类型) 3.点击ctrlo,选着这两个快捷重写 4.在需要抛出异常的地方写上:th…...
JVM 性能调优 -- JVM常用调优工具【jps、jstack、jmap、jstats 命令】
前言: 前面我们分析怎么去预估系统资源,怎么去设置 JVM 参数以及怎么去看 GC 日志,本篇我们分享一些常用的 JVM 调优工具,我们在进行 JVM 调优的时候,通常需要借助一些工具来对系统的进行相关分析,从而确定…...
python+django自动化部署日志采用WebSocket前端实时展示
一、开发环境搭建和配置 # channels是一个用于在Django中实现WebSocket、HTTP/2和其他异步协议的库。 pip install channels#channels-redis是一个用于在Django Channels中使用Redis作为后台存储的库。它可以用于处理#WebSocket连接的持久化和消息传递。 pip install channels…...
【Flink-scala】DataStream编程模型之窗口计算-触发器-驱逐器
DataStream API编程模型 1.【Flink-Scala】DataStream编程模型之数据源、数据转换、数据输出 2.【Flink-scala】DataStream编程模型之 窗口的划分-时间概念-窗口计算程序 文章目录 DataStream API编程模型前言1.触发器1.1 代码示例 2.驱逐器2.1 代码示例 总结 前言 本小节我想…...
毕昇入门学习
schemas.py 概述 这段代码主要定义了一系列基于 Pydantic 的数据模型(BaseModel),用于数据验证和序列化,通常用于构建 API(如使用 FastAPI)。这些模型涵盖了用户认证、聊天消息、知识库管理、模型配置等多…...
实时数据开发|Flink实现数据输出--DataSinks操作
哇哦,又是快乐周五!今天主管又又又请我们喝奶茶了,是乐乐茶的草莓新品。甜甜的草莓配上糯叽叽的麻薯,喝完好满足。这应该不是什么加班信号吧哈哈哈,不加不加周五要回家。 前几天被不同的bug缠身,今天终于正…...
详解网络代理模式:规则、全局与直连的应用与配置
“详解网络代理模式:规则、全局与直连的应用与配置” 当然,为了提供更深入的理解,让我们对每种代理模式进行更详尽的探讨,包括它们的内部工作机制、具体使用场景以及在实际应用中的优势和局限。 规则模式(Rule-based…...
Nacos部署和使用(服务注册与发现、配置中心)
1. docker部署nacos 参考: docker安装nacos-CSDN博客 2.注册中心原理 在微服务远程调用的过程中,包括两个角色: 服务提供者:提供接口供其它微服务访问,比如 A-service服务消费者:调用其它微服务提供的…...
医学机器学习:数据预处理、超参数调优与模型比较的实用分析
摘要 本文介绍了医学中的机器学习,重点阐述了数据预处理、超参数调优和模型比较的技术。在数据预处理方面,包括数据收集与整理、处理缺失值、特征工程等内容,以确保数据质量和可用性。超参数调优对模型性能至关重要,介绍了多种调…...
【大数据学习 | Spark-SQL】关于RDD、DataFrame、Dataset对象
1. 概念: RDD: 弹性分布式数据集; DataFrame: DataFrame是一种以RDD为基础的分布式数据集,类似于传统数据库中的二维表格。带有schema元信息,即DataFrame所表示的二维表数据集的每一列都带有名称和类型…...
流媒体中ES流、PS流 、TS流怎么理解
在流媒体的领域中,ES流、PS流和TS流是视频和音频数据的不同封装格式。它们通常用于传输、存储和播放多媒体内容。让我们分别了解一下它们的定义和用途。 1. ES流(Elementary Stream) ES流(基本流)是最基本的视频或音…...
阿里云ECS服务器磁盘空间不足的几个文件
查看磁盘空间命令: df -h /mnt 清零 echo >nohup.out 磁盘空间不足的文件列表: 一、nohup.out:来自"nohup java -jar service.jar &"命令产生的文件,位置在服务jar所在目录 二、access.log:位于…...
pip 安装指定镜像源
pip 安装指定镜像源 使用 pip 安装时,可以通过指定镜像源来加速安装速度,尤其在网络状况不佳或需要访问国内镜像源的情况下。 常见的国内镜像源 清华大学: https://pypi.tuna.tsinghua.edu.cn/simple 阿里云: https://mirrors.aliyun.com/pypi/simple …...
java全栈day10--后端Web基础(基础知识)
引言:只要能通过浏览器访问的网站全是B/S架构,其中最常用的服务器就是Tomcat 在浏览器与服务器交互的时候采用的协议是HTTP协议 一、Tomcat服务器 1.1介绍 官网地址:Apache Tomcat - Welcome! 1.2基本使用(网上有安装教程,建议…...
GPT(Generative Pre-trained Transformer) 和 Transformer的比较
GPT(Generative Pre-trained Transformer) 和 Transformer 的比较 flyfish 1. Transformer 是一种模型架构 Transformer 是一种通用的神经网络架构,由 Vaswani 等人在论文 “Attention Is All You Need”(2017)中提…...
大数据营销
大数据营销是一个热门的大数据应用。对于多数企业而言,大数据营销的主要价值源于以下几个方面。 市场预测与决策分析支持 数据对市场预测及决策分析的支持,早就在数据分析与数据挖掘盛行的年代被提出过。沃尔玛著名的“啤酒与尿布”案例就是那个时候的杰…...
数据字典的实现与应用 —— 提高系统灵活性与维护效率的关键
目录 前言1. 数据字典的基本概念1.1 什么是数据字典1.2 数据字典的主要特点 2. 数据字典的优势2.1 提高代码复用性2.2 提升系统的灵活性2.3 方便非技术人员管理2.4 减少错误概率 3. 数据字典在若依中的实现3.1 若依框架简介3.2 数据字典的结构设计 4. 若依框架中数据字典的配置…...
Scrapy管道设置和数据保存
1.1 介绍部分: 文字提到常用的Web框架有Django和Flask,接下来将学习一个全球范围内流行的爬虫框架Scrapy。 1.2 内容部分: Scrapy的概念、作用和工作流程 Scrapy的入门使用 Scrapy构造并发送请求 Scrapy模拟登陆 Scrapy管道的使用 Scrapy中…...
Jenkins的使用
文章目录 一、Jenkins是什么\有什么用\与GitLab的对比二、Jenkins的安装与配置Jenkins的安装方式在Linux上安装Jenkins:在Windows上安装Jenkins:配置Jenkins: (可选)配置启动用户为root(一定要是root吗??…...
计算机基础 原码反码补码问题
整数的二进制的表示形式:其实有三种 原码:直接根据数值写出的二进制序列就是原码 反码:原码的符号位不变,其他位按位取反就是反码 补码:反码1,就是补码 负数:-1 以补码形式存放在内存 写出 -1…...
ORB-SLAM2 ----- LocalMapping::SearchInNeighbors()
文章目录 一、函数意义二、函数讲解三、函数代码四、本函数使用的匹配方法ORBmatcher::Fuse()1. 函数讲解2. 函数代码 四、总结 一、函数意义 本函数是用于地图点融合的函数,前面的函数生成了新的地图点,但这些地图点可能在前面的关键帧中已经生成过了&a…...
游戏引擎学习第27天
仓库:https://gitee.com/mrxiao_com/2d_game 欢迎 项目的开始是从零开始构建一款完整的游戏,完全不依赖任何库或引擎。这样做有两个主要原因:首先,因为这非常有趣;其次,因为它非常具有教育意义。了解游戏开发的低层次…...
【超全总结】深度学习分割模型的损失函数类别及应用场景
《------往期经典推荐------》 一、AI应用软件开发实战专栏【链接】 项目名称项目名称1.【人脸识别与管理系统开发】2.【车牌识别与自动收费管理系统开发】3.【手势识别系统开发】4.【人脸面部活体检测系统开发】5.【图片风格快速迁移软件开发】6.【人脸表表情识别系统】7.【…...
基于HTML和CSS的校园网页设计与实现
摘要 随着计算机、互联网与通信技术的进步,Internet在人们的学习、工作和生活中的地位也变得越来越高,校园网站已经成为学校与学生,学生与学生之间交流沟通的重要平台,对同学了解学校内发生的各种事情起到了重要的作用。学校网站…...
深度学习基础02_损失函数BP算法(上)
目录 一、损失函数 1、线性回归损失函数 1.MAE损失 2.MSE损失 3.SmoothL1Loss 2、多分类损失函数--CrossEntropyLoss 3、二分类损失函数--BCELoss 4、总结 二、BP算法 1、前向传播 1.输入层(Input Layer)到隐藏层(Hidden Layer) 2.隐藏层(Hidden Layer)到输出层(Ou…...
Flutter:列表分页,上拉加载下拉刷新,在GetBuilder模板使用方式
GetBuilder模板使用方式参考上一节 本篇主要代码记录如何使用上拉加载下拉刷新, 接口请求和商品组件的代码不包括在内 pubspec.yaml装包 cupertino_icons: ^1.0.8# 分页 上拉加载,下拉刷新pull_to_refresh_flutter3: 2.0.2商品列表:controlle…...
使用eclipse构建SpringBoot项目
我这里用eclipse2018版本做演示,大家有需要的可以下载Eclipse Downloads | The Eclipse Foundation 1.打开eclipse,选择存放代码的位置 2.选择 file >> new >> project >> 选择springboot文件下的 spring starter project 2.这里选择N…...
Linux系统存储挂载与管理:从基础到高级
标题:Linux系统存储挂载与管理:从基础到高级 摘要 在Linux系统中,合理的存储管理和分配对于系统的性能、稳定性和资源利用至关重要。本文将详细介绍存储挂载的基本概念、如何进行存储分配和管理,并解释系统盘的作用。通过这些内…...
Flutter 权限申请
这篇文章是基于permission_handler 10.2.0版本写的 前言 在App开发过程中我们经常要用到各种权限,我是用的是permission_handler包来实现权限控制的。 pub地址:https://pub.dev/packages/permission_handler permission_handler 权限列表 变量 Androi…...
Linux之信号的产生,保存,捕捉
Linux之信号的产生,保存,捕捉处理 一.信号的概念1.1概念1.2分类 二.信号的产生2.1通过键盘产生的信号2.2系统调用接口产生的信号2.3硬件异常产生的信号2.4软件条件产生的信号 三.信号的保存四.信号的捕捉五.信号的其他杂碎知识5.1可重入函数5.2volatile关…...
基于AutoEncode自编码器的端到端无线通信系统matlab误码率仿真
目录 1.算法仿真效果 2.算法涉及理论知识概要 3.MATLAB核心程序 4.完整算法代码文件获得 1.算法仿真效果 matlab2022a仿真结果如下(完整代码运行后无水印): 仿真操作步骤可参考程序配套的操作视频。 2.算法涉及理论知识概要 自编码器是…...
泛化调用 :在没有接口的情况下进行RPC调用
什么是泛化调用? 在RPC调用的过程中,调用端向服务端发起请求,首先要通过动态代理,动态代理可以屏蔽RPC处理流程,使得发起远程调用就像调用本地一样。 RPC调用本质:调用端向服务端发送一条请求消息&#x…...
2025年人工智能,自动化与机械工程国际学术会议(AIAME2025)
早鸟通道开启: 2025年人工智能,自动化与机械工程国际学术会议(AIAME2025) 2025 International Conference on Artificial Intelligence, Automation, and Mechanical Engineering 【重要日期】 早鸟征稿截止日期:…...
docker compose 快速搭建Nacos单节点测试环境(mysql 版)
〓 参考: https://nacos.io/docs/latest/quickstart/quick-start-docker/?sourcewuyi https://github.com/nacos-group/nacos-docker https://nacos.io/docs/latest/manual/admin/deployment/deployment-standalone/?sourcewuyi https://nacos.io/docs/latest/man…...
数字3D虚拟展厅成熟运用于旅游业
在数字空间展览会与VR3D虚拟企业展厅设计的兴起中,我们迎来了互联网、物联网与3D技术融合的大时代。这些企业虚拟展厅主要依托互联网作为传播媒介,利用图片、文字和Flash动画等形式,生动展现企业的核心产品。作为一种新型的网络信息技术展厅&…...