核心概念:TCP 连接到底是什么?
一句话定义 :
TCP 连接(Transmission Control Protocol Connection)并不是两根网线物理上连在了一起,而是通信双方(客户端和服务端)在内存中建立的一种**"虚拟的、可靠的、双向的对话通道"**。
生动比喻:打电话 vs. 寄信
-
UDP(非连接) 就像 寄信 :
你把信(数据包)扔进邮筒,不知道对方收没收到,也不知道信是不是按顺序到的,甚至可能丢件。你发你的,他收他的,中间没有确认机制。
-
TCP(面向连接) 就像 打电话:
- 建立连接:你得先拨号,对方接听,互相确认"喂,听得见吗?"(三次握手)。
- 可靠传输:你说一句,对方回一句"收到了"(ACK确认)。如果对方没回应,你会重说一遍(重传机制)。
- 有序性:即使信号不好导致话语乱序到达,对方也会帮你整理好顺序再听(序列号)。
- 断开连接:聊完了,双方互道再见挂断电话(四次挥手)。
在后端视角 :
当你写 socket.accept() 时,操作系统内核并没有真的拉一根线,而是在内核内存中为这个客户创建了一个结构体(TCB,传输控制块),记录了双方的 IP、端口、当前发送/接收了多少数据、窗口大小等状态。这个内存中的状态记录,就是 TCP 连接。
🛠️ TCP 连接的三大核心特性(后端必知)
作为后端开发者,你利用 TCP 主要是为了这三点:
- 可靠性(Reliability) :
- 场景:用户提交订单支付。
- 机制:如果数据包丢了,TCP 会自动重传;如果包坏了,会校验并重传。你不需要在应用层代码里写"如果没收到就重发",TCP 底层帮你搞定了。
- 有序性(Ordering) :
- 场景:传输一个大文件(如图片、视频)。
- 机制:网络包可能走不同路径,后发的包先到。TCP 会给每个包打上"序号",接收端会按序号重组,保证你读到的数据流和发送端完全一致。
- 流量控制与拥塞控制(Flow & Congestion Control) :
- 场景:服务端处理慢,客户端发太快。
- 机制:TCP 有个"滑动窗口"。如果服务端缓冲区满了,它会告诉客户端:"别发了,我处理不过来了"。这防止了服务器被瞬间流量打挂。
⚠️ 后端开发中的"坑"与实战状态
作为后端,你经常需要用 netstat 或 ss 命令查看连接状态,以下几个状态必须烂熟于心:
1. SYN_RECV 暴涨 -> SYN Flood 攻击
- 现象:服务器收到了大量的 SYN 包,但没收到第三次握手的 ACK。
- 原因:黑客伪造 IP 发送大量 SYN 包,服务器资源(半连接队列)被占满,正常用户无法连接。
- 对策 :开启内核参数
tcp_syncookies,或者使用防火墙/WAF 限制频率。
2. CLOSE_WAIT 过多 -> 代码 Bug!
- 现象 :服务器上存在大量
CLOSE_WAIT状态的连接。 - 原因 :这是最常见的人为错误! 意味着对方(客户端)已经调用了
close(),但你的服务端代码没有调用close()关闭 socket。 - 后果 :文件描述符(FD)泄露,最终导致服务器报错
Too many open files,无法接受新连接。 - 排查 :检查代码逻辑,确保在
catch异常块或finally块中关闭了 socket/数据库连接/HTTP 响应流。
3. TIME_WAIT 过多 -> 高并发短连接
- 现象 :作为主动关闭方(通常是客户端或短连接服务器),出现大量
TIME_WAIT。 - 原因:TCP 规定主动关闭方必须等待 2MSL(最大报文生存时间,约 60-120 秒)才能彻底销毁连接,以防止旧的重复包干扰新连接。
- 影响:占用本地端口资源。如果并发极高,可能导致端口耗尽(Ephemeral ports exhausted)。
- 对策 :
- 尽量使用长连接(Keep-Alive),复用连接。
- 调整内核参数
net.ipv4.tcp_tw_reuse = 1(允许重用 TIME_WAIT 状态的 socket 用于新的出站连接)。
介绍一下TCP中的Socket,序列号和窗口大小分别是什么?
1. Socket (套接字):连接的"门牌号"与"操作句柄"
🧐 是什么?
在很多初学者的印象里,Socket 就是代码里的一个对象(比如 Java 的 Socket 类,Python 的 socket 对象)。但在操作系统内核层面,Socket 是应用层与 TCP/IP 协议栈交互的抽象接口(API),也是内核中管理网络连接的数据结构。
🏠 生动比喻:公司的"前台接待处"
- IP 地址 = 公司大楼的地址(定位到机器)。
- 端口号 (Port) = 具体的部门房间号(定位到进程,如 80 端口是 Web 部)。
- Socket = 这个房间里的"前台接待员" + "专用电话线"。
当客户端发起连接时,它并不是直接连到"IP:Port",而是连到了该端口上监听的那个 Socket 。一旦连接建立,服务端会 accept() 生成一个新的 Socket,专门服务于这一个客户端。
🔍 核心特征(后端视角)
- 唯一标识一个连接 :
一个 TCP 连接由 四元组 唯一确定:
{源IP, 源端口, 目的IP, 目的端口}
在内核中,每一个活跃的连接都对应一个独立的 Socket 结构体。即使同一个服务端端口(如 80),也可以同时维持数万个 Socket,分别对应不同的客户端 IP/端口。 - 双缓冲区 :
每个 Socket 内部都有两个队列(缓冲区):- 接收缓冲区 (Receive Buffer) :存放从网卡收到但应用程序还没
read()的数据。 - 发送缓冲区 (Send Buffer) :存放应用程序
write()了但还没被网卡发走的数据。 - 关键点 :如果你发现程序
write阻塞了,通常是因为发送缓冲区满了 ;如果read没数据,是因为接收缓冲区空了。
- 接收缓冲区 (Receive Buffer) :存放从网卡收到但应用程序还没
- 文件描述符 (FD) :
在 Linux 中,"一切皆文件"。Socket 也是一个文件,用一个整数(FD)表示。高并发服务器(如 Nginx, Redis)的核心能力之一,就是能同时管理数十万个 Socket FD。
2. 序列号 (Sequence Number, Seq):数据的"快递单号"
🧐 是什么?
TCP 是面向字节流 的协议,它不关心你发的是"图片"还是"文本",它只把数据看作一连串的字节。序列号就是给每一个字节编的号。
📦 生动比喻:拆散的拼图
假设你要传一本 1000 页的书(数据),TCP 不能一次全扔过去,必须撕成小页(数据包/MSS)发送。
- Seq = 1:代表这个包里装的是书的第 1 页开始的内容。
- Seq = 101:代表这个包里装的是第 101 页开始的内容。
🔍 核心作用
- 解决乱序问题 (Reordering) :
网络很复杂,包可能走不同路径。- 先发了包 A (Seq=1),后发了包 B (Seq=101)。
- 结果包 B 先到,包 A 后到。
- 接收端看到 Seq=101,知道前面缺了 1-100,先把 B 存进缓冲区等着;等 A (Seq=1) 到了,再按顺序拼好交给应用层。应用层永远感觉不到乱序。
- 解决丢包重传 (Retransmission) :
- 接收端发现收到了 Seq=1 和 Seq=201,中间缺了 101-200。
- 它会回复 ACK=101(意思是:我期望收到从 101 开始的数据)。
- 发送端收到这个 ACK,就知道 101 之后的数据丢了,于是重传从 101 开始的数据包。
- 初始序列号 (ISN) :
每次建立连接(三次握手)时,双方都会随机生成一个初始序列号(ISN),而不是从 0 开始。这是为了防止历史连接的旧数据包干扰新连接(安全考虑)。
💡 后端调试技巧 :
使用
tcpdump抓包时,你会看到Seq和Ack。
Seq=1000, Len=500表示这个包携带了 500 字节数据,范围是 [1000, 1499]。下一个包的
Ack应该是 1500,表示"1499 之前的我都收到了,请发 1500 开始的"。
3. 窗口大小 (Window Size):流量的"限速阀"
🧐 是什么?
窗口大小(Window Size, Win)告诉对方:"我现在接收缓冲区还剩多少空间,你最多能一次性发这么多数据给我,别把我撑爆了。"
这就是 TCP 的流量控制 (Flow Control) 机制。
🚦 生动比喻:水槽与水龙头
- 接收端是一个水槽(接收缓冲区),容量有限。
- 发送端是水龙头。
- 窗口大小 = 水槽里还能装多少水的刻度。
- 接收端在回复 ACK 时,会带上当前的
Win=5000(假设单位是字节)。 - 发送端看到这个值,就知道:"哦,他还能收 5000 字节"。
- 发送端就只发 5000 字节,然后停下来等待新的 ACK。
- 如果接收端处理慢,缓冲区快满了,它会通告
Win=0。发送端收到后必须停止发送 ,直到接收端处理完一些数据,通告Win > 0为止。
🔍 核心分类
在后端性能调优中,我们常听到两种"窗口":
-
接收窗口 (rwnd, Receive Window):
- 由接收端决定,受限于接收端的内存缓冲区大小。
- 作用:流量控制,防止发送太快把接收端压垮。
- 场景 :你的 Java 程序处理逻辑太慢,
read()不及时,导致内核接收缓冲区满,rwnd 变为 0,客户端就会暂停发送,表现为"网速变慢"或"卡顿"。
-
拥塞窗口 (cwnd, Congestion Window):
- 由发送端根据网络状况(是否丢包、延迟高低)动态估算。
- 作用:拥塞控制,防止发送太快把网络链路堵死。
- 机制:TCP 有著名的"慢启动"算法。刚开始 cwnd 很小,每收到一个 ACK 就加倍,一旦发现丢包(认为网络堵了),就立刻减半。