Linux 网络通信的整体流程

1次阅读

一次TCP通信的函数链路是socket()→bind()/connect()→listen()+accept()→send()/recv(),每个调用对应内核状态操作,如socket()初始化TCB、connect()触发三次握手、send()仅表示数据入内核缓冲区。

Linux 网络通信的整体流程

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 -nip route 输出的路由表,匹配目标 IP 后决定走哪个接口、下一跳是谁

三次握手失败时,你会看到什么错误?

客户端 connect() 超时或失败,常见现象不是“连不上”,而是具体错误码暴露了哪一环断了:

  • Connection refused (ECONNREFUSED):服务器没在监听(端口bind()+listen()),或防火墙 DROP 了 SYN 包
  • Operation timed out (ETIMEDOUT):SYN 发出去了,但没收到 SYN+ACK —— 可能服务器宕机、路由不通、中间设备丢包,或服务器 SYN 队列满(netstat -s | grep "SYNs to LISTEN sockets dropped"
  • No route to host (EHOSTUNREACH):本地路由表找不到目标网络,比如目标 IP 是私有地址但本机不在同一子网,且没配置网关

/etc/network/interfaces 和协议的关系很浅

这个文件只管「网卡配什么 IP」,属于网络层初始化配置;它不参与 TCP 连接建立、不控制 socket 行为、也不影响三次握手流程。改完它需要 ifdown/ifup 或重启网络服务,但已建立的 TCP 连接不受影响——因为连接状态保存在内核的 socket 结构里,和接口配置是解耦的。

真正影响通信的是运行时内核参数,比如:net.ipv4.tcp_tw_reuse = 1 允许 TIME_WaiT 套接字重用,避免高并发短连接耗尽端口;net.core.somaxconn 控制 listen() 的最大全连接队列长度。这些比静态配置文件更直接决定通信能否撑住流量。

text=ZqhQzanResources