TCP是面向连接的、可靠的、基于字节流的传输层通信协议:

- 面向连接:TCP连接必须是一对一的,这与无连接的UDP不同,后者可以支持一对多的通信。
- 可靠的:无论网络路径如何变化,TCP都能通过各种机制确保一个报文最终能够到达接收端。
- 字节流:消息没有固定边界,因此无论多大都能传输。消息是有序的,如果前面的字节未收到,即使先收到了后面的字节,也不能提交给应用层处理,同时重复的报文会被自动丢弃。
TCP头部格式

- 序列号:在建立连接时随机生成初始值,通过SYN包告知对方。每发送一次数据,该值就累加相应的数据字节数。它主要解决网络包乱序问题。
- 确认应答号:指下一次期望收到的数据的序列号。发送端收到此确认后,可以认为此序号之前的数据已被正常接收。它用来解决丢包问题。
- 控制位:
- ACK:为1时,表示确认应答号字段有效。TCP规定除了初始建立连接的SYN包外,其他报文都必须将该位置为1。
- RST:为1时,表示TCP连接出现异常,必须强制断开连接。
- SYN:为1时,表示希望建立连接,并在其序列号字段设定初始值。
- FIN:为1时,表示今后不再发送数据,希望断开连接。通信结束时,双方会交换FIN标志位为1的TCP段。
网络模型
OSI参考模型
OSI参考模型是国际标准化组织提出的七层网络互连概念模型,旨在为不同厂商的设备互联提供统一标准。

TCP/IP五层模型
实际应用中更为广泛的是TCP/IP五层(或四层)模型,它与OSI模型的对应关系如下:

TCP状态机

- CLOSED:初始状态。
- LISTEN:服务器端的某个Socket处于监听状态,可以接受连接。
- SYN_RCVD:表示接收到了SYN报文。
- SYN_SENT:表示客户端已发送SYN报文。
- ESTABLISHED:表示连接已经建立。
- TIME_WAIT:表示收到了对方的FIN报文,并发送出了ACK报文,等待2MSL后即可回到CLOSED状态。
- CLOSING:表示双方几乎同时发起关闭,都发送了FIN报文但还未收到对方的ACK。
- CLOSE_WAIT:表示被动关闭方正在等待应用层处理完数据后发送FIN报文。
在Linux系统中,我们可以通过 netstat -napt 命令查看当前的TCP连接状态。

深入理解TIME_WAIT状态
-
为什么需要TIME_WAIT状态?
主动发起关闭的一方才会进入TIME-WAIT状态。主要基于两个原因:
- 防止具有相同“四元组”(源IP、源端口、目的IP、目的端口)的旧数据包被新连接错误接收。
- 保证被动关闭的一方能够正确关闭。即确保最后一个ACK报文能被对方收到,否则对方会重发FIN。
-
TIME_WAIT过多有什么危害?
- 内存和端口资源占用:每个TIME_WAIT连接都会占用系统资源。端口资源有限(默认范围32768~61000,可通过
net.ipv4.ip_local_port_range 参数调整),一旦被占满,客户端将无法创建新的连接。
- 服务器资源压力:虽然服务端只监听一个端口,但大量TIME_WAIT连接会消耗处理线程和系统资源,可能导致无法处理新连接。
-
如何优化TIME_WAIT?
- 开启
net.ipv4.tcp_tw_reuse(需同时开启 tcp_timestamps)以复用TIME_WAIT状态的连接(仅适用于客户端)。
- 调整
net.ipv4.tcp_max_tw_buckets,限制系统中TIME_WAIT连接的最大数量。
- 在程序中设置
SO_LINGER 选项,强制使用RST方式关闭连接(不推荐常规使用)。
-
为什么等待时间是2MSL?
MSL(Maximum Segment Lifetime)是报文的最大生存时间。等待2MSL是为了确保最后一个ACK报文有足够的时间到达对端,同时让本次连接中产生的所有报文都在网络中消亡,避免影响后续的新连接。在Linux内核中,该值固定为60秒。
#define TCP_TIMEWAIT_LEN (60*HZ) /* how long to wait to destroy TIME-WAIT state, about 60 seconds */
TCP连接建立:三次握手

TCP通过“三次握手”建立可靠连接。假设初始时双方都为CLOSED状态,服务端首先监听端口进入LISTEN状态。

-
第一次握手:
- 客户端随机生成初始序列号
client_isn,填入TCP头部的“序号”字段,并将SYN标志位置1,发送SYN报文给服务端。
- 发送后,客户端进入 SYN-SENT 状态。

-
第二次握手:
- 服务端收到SYN报文后,也随机生成自己的初始序列号
server_isn,填入“序号”字段。
- 将“确认应答号”字段置为
client_isn + 1,并将SYN和ACK标志位置1,然后发送SYN+ACK报文给客户端。
- 发送后,服务端进入 SYN-RCVD 状态。

-
第三次握手:
- 客户端收到SYN+ACK报文后,将“确认应答号”字段置为
server_isn + 1,并将ACK标志位置1,发送ACK报文给服务端。此报文可以携带应用层数据。
- 发送后,客户端进入 ESTABLISHED 状态。
- 服务端收到ACK报文后,也进入 ESTABLISHED 状态,连接建立完成。

第三次握手可以携带数据吗?
可以。只有第三次握手可以携带应用层数据。如果携带数据,则下次客户端发送的报文序列号就是本次携带数据的结束序列号;如果不携带,则不消耗序列号,下次序列号仍为client_isn+1。
内核如何处理第三次握手?
- 服务端(SYN-RCVD状态):当内核收到第三次握手的ACK(可能包含数据),会先将socket状态置为ESTABLISHED,然后立即处理报文中的数据。

- 客户端(SYN-SENT状态):客户端在发送第三次ACK时,在某些特定场景(如
sk_write_pending、延迟接受TCP_DEFER_ACCEPT、交互式数据流pingpong模式)下,可能会延迟发送纯ACK,而是等待一个极短的时间,期望将ACK与数据一同发送以提升效率。

为什么是三次握手?不是两次或四次?
三次握手是建立可靠连接的最小次数,主要原因有三:
- 避免历史连接:防止因网络延迟导致的旧连接请求突然到达服务端,造成错误连接。

- 同步初始序列号:通信双方需要可靠地交换彼此的初始序列号(ISN),这是实现可靠传输、顺序接收、去重的基础。两次握手无法保证双方序列号都被确认,而四次握手可以优化为三步。

- 避免资源浪费:如果只有两次握手,服务端在收到SYN后就必须建立连接。当客户端的SYN因网络阻塞而重传时,服务端会为每一个SYN都创建一个连接,造成资源浪费。

TCP连接断开:四次挥手

连接断开由一方(假设为客户端)主动发起,过程如下:
- 第一次挥手:客户端发送FIN报文(FIN=1),进入 FIN_WAIT_1 状态。
- 第二次挥手:服务端收到FIN后,回复ACK报文,进入 CLOSE_WAIT 状态。客户端收到ACK后,进入 FIN_WAIT_2 状态。
- 第三次挥手:服务端处理完所有待发数据后,发送自己的FIN报文,进入 LAST_ACK 状态。
- 第四次挥手:客户端收到FIN后,回复ACK报文,进入 TIME_WAIT 状态。服务端收到ACK后,进入 CLOSED 状态。客户端等待2MSL时间后,也进入 CLOSED 状态。
为什么挥手需要四次?
因为TCP连接是全双工的。当一方说“我没有数据要发了”(发FIN),另一方可能还有数据要处理或发送。因此,对方的ACK(确认收到了你的关闭请求)和FIN(表示它也发完了)通常是分两步完成的,这就比建立连接时的“一问一答合并”多了一次交互。
TCP性能优化策略
三次握手优化

可调整SYN重传次数、半连接/全连接队列长度,或使用TCP Fast Open(TFO)绕过部分握手。
四次挥手优化

可调整FIN重传次数、FIN_WAIT2状态超时时间、孤儿连接上限、TIME_WAIT连接数量及复用策略。
数据传输优化

可通过调整窗口缩放、发送/接收缓冲区大小、内存限制等参数来提升吞吐量。
TCP核心机制与常见问题
TCP与UDP的区别

- 连接性:TCP面向连接,UDP无连接。
- 可靠性:TCP可靠,有序,有流量/拥塞控制;UDP尽最大努力交付。
- 传输形式:TCP是字节流,无边界;UDP是数据报,有边界。
- 开销与效率:TCP头部大,建立连接有开销;UDP头部小,实时性高。
- 应用场景:TCP适用于文件传输、网页浏览等要求可靠性的场景;UDP适用于音视频通话、直播、DNS查询等实时性或简单查询场景。
初始序列号(ISN)
- 为何随机生成? 防止失效连接的历史报文被新连接接收造成混乱,并增强安全性,抵御伪造报文攻击。
- 如何生成? 通常基于时钟(每4ms加1)并混合一个基于连接四元组的Hash值(如MD5),使ISN难以预测。
TCP如何保证可靠性?
通过以下机制协同工作:
- 校验和:检测数据在传输过程中是否发生错误。
- 序列号与确认应答(ACK):保证数据有序、不重复、不丢失。
- 超时重传:当ACK未在规定时间内到达时,重发数据。
- 连接管理:三次握手和四次挥手确保连接有序建立和释放。
- 流量控制:通过滑动窗口机制,根据接收方处理能力动态调整发送速率。
- 拥塞控制:包含慢启动、拥塞避免、快速重传和快速恢复算法,防止网络过载。
TCP如何提升传输效率?
- 滑动窗口:允许发送方在未收到确认的情况下连续发送多个报文段。
- 快速重传:当连续收到三个重复ACK时,立即重传丢失的报文段,而非等待超时。
- 延迟应答:接收方稍作延迟再回复ACK,可以给应用程序更多时间消费数据,从而在ACK中通告更大的接收窗口。
- 捎带应答:将ACK应答与要发送的应用数据合并在一个报文中发送,减少纯ACK报文。
TCP如何处理拥塞?
TCP的拥塞控制是一个动态调整发送速率的过程,包含四个阶段:慢启动(指数增长)、拥塞避免(线性增长)、快速重传(收到3个重复ACK时触发)和快速恢复。
Socket编程核心流程

基于TCP的Socket编程遵循标准流程:服务端(socket -> bind -> listen -> accept -> read/write -> close),客户端(socket -> connect -> read/write -> close)。
关于listen的backlog参数:
在现代Linux内核(2.2以后),backlog指定的是已完成三次握手、等待应用层accept()的ESTABLISHED连接队列(Accept队列)的最大长度。其实际最大值受系统参数somaxconn的限制,即 min(backlog, somaxconn)。
accept()发生在三次握手的哪一步?
服务端的accept()成功返回(从阻塞中唤醒)发生在第三次握手完成之后,即服务端收到客户端对SYN+ACK的确认ACK报文时。
客户端调用close()后的流程:
这正是四次挥手的过程:客户端close()触发第一次挥手(FIN),服务端read()读到EOF感知到连接关闭,处理完数据后服务端也调用close()触发第三次挥手(FIN),最终双方在完成四次挥手后进入CLOSED状态。
掌握TCP协议的这些核心原理、状态转换和优化思路,对于进行高性能网络编程和复杂网络问题排查至关重要。如果你想深入探讨更多网络技术细节,或与其他开发者交流实践经验,欢迎访问云栈社区。