计算机网络
计算机网络基础
OSI七层模型
物理层
数据链路层
网络层
传输层
会话层
表示层
应用层
TCP/IP模型
TCP协议
TCP
TCP的特点
全双工通信
TCP是全双工的(Full-Duplex),这意味着:
- 双向通信:连接建立后,通信双方可以同时发送和接收数据
- 独立的数据流:每个方向都有独立的序列号和确认号,互不干扰
- 独立的流量控制:每个方向都有独立的接收窗口,可以独立控制数据流量
- 同时操作:在接收数据的同时可以发送数据,反之亦然
示例:在HTTP/1.1的持久连接中,客户端可以在接收服务器响应数据的同时,继续向服务器发送新的请求数据。
三次握手
三次握手(Three-Way Handshake)是TCP建立连接的过程:
- 第一次握手(SYN):客户端发送SYN包(SYN=1, seq=x)到服务器,进入SYN_SENT状态
- 第二次握手(SYN+ACK):服务器收到SYN包后,发送SYN+ACK包(SYN=1, ACK=1, seq=y, ack=x+1),进入SYN_RCVD状态
- 第三次握手(ACK):客户端收到SYN+ACK包后,发送ACK包(ACK=1, seq=x+1, ack=y+1),进入ESTABLISHED状态;服务器收到ACK后也进入ESTABLISHED状态
为什么采用三次握手

TCP采用三次握手主要有以下几个原因:
防止历史连接(旧连接)的干扰
- 如果客户端发送的SYN包在网络中滞留,客户端会重传新的SYN包
- 如果只使用两次握手,当滞留的旧SYN包到达服务器时,服务器会误认为这是新的连接请求,建立连接并发送数据
- 三次握手中,客户端收到服务器的SYN+ACK后,可以通过ACK中的确认号判断这是否是历史连接,如果是历史连接则发送RST包终止连接
确保双方都能确认对方的发送和接收能力
- 第一次握手:客户端发送SYN → 服务器确认客户端的发送能力
- 第二次握手:服务器发送SYN+ACK → 客户端确认服务器的发送和接收能力
- 第三次握手:客户端发送ACK → 服务器确认客户端的接收能力
- 只有三次握手才能确保双方都确认了对方的双向通信能力
同步双方的初始序列号(ISN)
- TCP需要为每个方向的数据流分配独立的序列号
- 三次握手确保双方都确认了对方的初始序列号,为后续的数据传输做准备
防止资源浪费
- 如果只使用两次握手,服务器在收到SYN后就建立连接并分配资源
- 如果客户端的ACK丢失,服务器会一直等待,造成资源浪费
- 三次握手确保客户端真正准备好通信后才建立连接
为什么不能是两次握手?
- 两次握手无法防止历史连接的干扰
- 两次握手无法确保客户端确认服务器的接收能力
- 如果客户端的ACK丢失,服务器无法确认连接是否真正建立
为什么不能是四次握手?
- 三次握手已经足够确保双方的双向通信能力
- 四次握手会增加网络延迟,没有必要
四次挥手
四次挥手(Four-Way Handshake)是TCP关闭连接的过程:
- 第一次挥手(FIN):主动关闭方(通常是客户端)发送FIN包(FIN=1, seq=u)到被动关闭方,表示不再发送数据,进入FIN_WAIT_1状态
- 第二次挥手(ACK):被动关闭方收到FIN包后,发送ACK包(ACK=1, seq=v, ack=u+1)确认收到,进入CLOSE_WAIT状态;主动关闭方收到ACK后进入FIN_WAIT_2状态
- 第三次挥手(FIN):被动关闭方发送FIN包(FIN=1, ACK=1, seq=w, ack=u+1)到主动关闭方,表示也不再发送数据,进入LAST_ACK状态
- 第四次挥手(ACK):主动关闭方收到FIN包后,发送ACK包(ACK=1, seq=u+1, ack=w+1)确认收到,进入TIME_WAIT状态;被动关闭方收到ACK后进入CLOSED状态
为什么采用四次挥手
TCP采用四次挥手主要有以下几个原因:
TCP是全双工通信
- TCP连接建立后,通信双方都有独立的发送和接收通道
- 一方关闭连接时,只是表示”我不再发送数据了”,但可能还需要接收对方的数据
- 因此需要双方分别关闭自己的发送通道,这就是为什么需要四次挥手
被动关闭方需要时间处理剩余数据
- 当主动关闭方发送FIN后,被动关闭方可能还有数据需要发送
- 第二次挥手的ACK只是确认收到了关闭请求,但被动关闭方可能还需要:
- 发送缓冲区中剩余的数据
- 处理应用层的关闭逻辑
- 只有等被动关闭方也准备好关闭时,才会发送FIN(第三次挥手)
为什么不能是三次挥手?
- 如果被动关闭方在收到FIN后立即发送FIN+ACK(将第二次和第三次挥手合并),可能会出现问题:
- 被动关闭方可能还有数据要发送,立即关闭会导致数据丢失
- 无法保证被动关闭方已经处理完所有待发送的数据
- 分开发送ACK和FIN,给被动关闭方缓冲时间来处理剩余数据
- 如果被动关闭方在收到FIN后立即发送FIN+ACK(将第二次和第三次挥手合并),可能会出现问题:
TIME_WAIT状态的作用
- 主动关闭方在发送最后一次ACK后进入TIME_WAIT状态,等待2MSL(Maximum Segment Lifetime,最大报文段生存时间)
- 作用:
- 确保最后一个ACK能够到达被动关闭方:如果ACK丢失,被动关闭方会重传FIN,主动关闭方可以再次发送ACK
- 防止”已失效的连接请求报文段”出现在本连接中:等待足够的时间,让网络中所有该连接的报文段都消失
- 如果没有TIME_WAIT状态,可能会影响新建立的连接(如果端口被立即重用)
为什么不能是两次挥手?
- 两次挥手无法保证双方都完成了数据的发送和接收
- 无法处理被动关闭方还有数据要发送的情况
- 无法保证连接的可靠关闭
为什么不能是三次挥手?
- 三次挥手无法给被动关闭方足够的时间来处理剩余数据
- 如果被动关闭方立即发送FIN+ACK,可能会导致数据丢失
- 四次挥手的设计更符合TCP全双工通信的特性
如果CLOSE_WAIT大量出现说明什么?
CLOSE_WAIT大量出现说明被动关闭方(通常是服务器)的应用程序存在严重问题:
问题的本质
- CLOSE_WAIT是被动关闭方收到FIN包后的状态,正常情况下应该很快发送FIN包进入LAST_ACK状态
- 大量CLOSE_WAIT连接说明应用程序没有正确关闭连接,导致连接一直停留在CLOSE_WAIT状态
- 这是应用程序层面的bug,不是网络问题
常见原因
- 代码bug:忘记调用
close()或shutdown()函数关闭套接字 - 异常处理不当:程序异常退出或崩溃,没有执行清理代码
- 资源泄漏:连接对象没有被正确释放,特别是在高并发场景下
- 死锁或阻塞:应用程序在处理数据时发生死锁或长时间阻塞,无法执行关闭操作
- 连接池配置问题:连接池没有正确管理连接的生命周期
- 代码bug:忘记调用
带来的严重后果
- 文件描述符耗尽:每个CLOSE_WAIT连接都占用一个文件描述符,大量CLOSE_WAIT会导致文件描述符耗尽,无法建立新连接
- 内存泄漏:连接对象和缓冲区一直占用内存,导致内存泄漏
- 服务不可用:当文件描述符耗尽时,服务无法接受新连接,导致服务不可用
- 性能下降:系统资源被无效连接占用,影响系统整体性能
注意:CLOSE_WAIT状态本身没有超时机制,连接会一直停留在CLOSE_WAIT状态直到应用程序关闭连接。大量CLOSE_WAIT是严重问题,必须立即修复应用程序代码。
例如,手动管理事务但是没有释放链接
https://zhuanlan.zhihu.com/p/377044076
1 | |
在 Go 等语言中,如果函数返回了,局部变量 o(ORM 实例)的作用域虽然结束,但它在底层的数据库连接仍然持有事务状态。数据库驱动(Driver)会认为该连接正忙于事务,因此不会将其放回连接池,也不会释放。
如果TIME_WAIT大量出现说明什么?
TIME_WAIT服务器主动关闭了大量连接,正处于协议保护期:
TIME_WAIT的正常流程
- 主动关闭方发送最后一个ACK后进入TIME_WAIT状态
- 等待2MSL(Maximum Segment Lifetime,通常为1-4分钟)后自动进入CLOSED状态
- 这是TCP协议设计的正常关闭流程,不是错误
TIME_WAIT的作用
- 确保最后一个ACK能够到达被动关闭方
- 防止旧连接的报文段影响新连接
- 这是TCP可靠性的保证
如果TIME_WAIT过多
- 大量的短连接存在
- 特别是 HTTP 请求中,如果 connection 头部取值被设置为 close 时,基本都由「服务端」发起主动关闭连接
- 而,TCP 四次挥手关闭连接机制中,为了保证 ACK 重发和丢弃延迟数据,设置 time_wait 为 2 倍的 MSL(报文最大存活时间)
- 解决方案:
- 使用HTTP长连接(Keep-Alive)减少连接建立和关闭次数
- 调整系统参数(如
net.ipv4.tcp_tw_reuse)重用TIME_WAIT状态的端口 - 使用连接池复用连接
CLOSE_WAIT vs TIME_WAIT的区别:
- CLOSE_WAIT:被动关闭方状态,超时说明应用程序有问题,需要修复代码
- TIME_WAIT:主动关闭方状态,超时是正常现象,表示连接已安全关闭