一次TCP通信的函数链路是socket()→bind()/connect()→listen()+accept()→send()/recv(),每个调用对应内核状态操作,如socket()初始化TCB、connect()触发三次握手、send()仅表示数据入内核缓冲区。
socket() 到 send()/recv():一次 TCP 通信的函数链路
Linux 网络通信不是“直接发数据”,而是由内核协议栈驱动的一系列状态流转。从应用层发起,关键路径是:socket() → bind()(服务端)或 connect()(客户端)→ listen() + accept()(服务端)→ send()/recv()。每个调用背后都对应内核中一个数据结构操作:比如 socket() 分配 fd 并初始化 TCB(传输控制块),connect() 实际触发三次握手,但函数阻塞直到第三次 ACK 收到才返回。
- 服务端必须显式调用
bind()绑定本地 IP 和端口,否则内核随机分配(通常不满足业务需求) -
listen()的backlog参数不是连接总数,而是「已完成三次握手、等待accept()取走」的连接队列长度;设太小会导致客户端connect()返回ECONNREFUSED -
send()成功只表示数据拷贝进内核写缓冲区,不等于对方已收到;recv()返回 0 表示对端调用了close()或shutdown(),这是正常断连信号
数据怎么从用户内存跑到网线?——封装与分段的真实过程
你调用 send() 写入 10KB 数据,它不会原样发出去。内核会按 MTU(通常 1500 字节)切片,每片加上 TCP 头(20+ 字节)、IP 头(20+ 字节)、以太网头(14 字节)和尾(4 字节 CRC)。最终发到网卡的是一个个 sk_buff 结构体组成的帧,不是你的原始 buf。
- 应用层无感知分片:TCP 自动处理分段和重组,但“黏包”问题正源于此——
recv()可能一次读到多个逻辑消息,或一个消息被拆成多次返回 - 校验和由网卡硬件计算(如启用 TSO/GSO),不是 CPU 算的;若禁用硬件卸载(
ethtool -K eth0 tx off),性能可能下降 10%~30% -
路由决策发生在 IP 层:内核查
route -n或ip route输出的路由表,匹配目标 IP 后决定走哪个接口、下一跳是谁
三次握手失败时,你会看到什么错误?
客户端 connect() 超时或失败,常见现象不是“连不上”,而是具体错误码暴露了哪一环断了: