网络协议栈发送性能优化:让发包快得像按了加速键

你有没有遇到过这种情况:局域网传大文件,别人用千兆网卡10秒搞定,你却卡在3MB/s不动?或者写了个UDP服务压测,一开多线程发就丢包严重、延迟飙升?别急着换网卡——问题很可能藏在Linux内核的网络协议里。

发包慢,不一定是网卡拖后腿

很多人一看到发包慢,第一反应是查网卡驱动、换万兆卡、关掉节能。但实际调试中发现,不少案例的瓶颈根本不在硬件层。比如某台Web服务器在高并发短连接场景下,SYN包发出延迟波动剧烈,tcpdump抓包一看:应用层调用send()后,内核要等几十微秒才真正把数据推到网卡队列——这中间就是协议栈的“消时间”。

几个立竿见影的调优点

1. 关闭GSO/TSO(对小包场景特别有用)
现代网卡支持TCP分段卸载(TSO)和通用分段卸载(GSO),本意是减负,但若上层频繁发小包(比如HTTP/2头部帧),反而造成内核软中断排队。临时关闭试试:

ethtool -K eth0 tso off gso off

2. 调整sk_buff预分配池大小
内核为每个socket准备sk_buff内存池,发包高峰时若池子太小,会触发频繁内存分配。观察/proc/net/snmp里的TcpOutSegs和TcpExtDelayedACKs,如果后者突增,可能就是缓冲区紧张。可加大:

echo 8192 > /proc/sys/net/core/wmem_max
echo "2048 65536 131072" > /proc/sys/net/ipv4/tcp_wmem

3. 绕过部分协议栈(Zero-Copy发包)
对延迟敏感的服务(如高频交易、实时音视频),可以用AF_XDP或SO_ZEROCOPY跳过skb拷贝。简单起见,先试SO_SNDBUF+MSG_NOSIGNAL组合:

int sndbuf = 1024 * 1024;
setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf));
// 发送时加标志
send(sockfd, buf, len, MSG_NOSIGNAL | MSG_DONTWAIT);

别忽略这些“软性”设置

网卡队列绑定CPU:用irqbalance或手动将eth0-Tx队列绑到专用CPU核心,避免软中断被其他进程打断;
禁用IPv6路由表查找:如果你只用IPv4,在/etc/sysctl.conf加net.ipv6.conf.all.disable_ipv6 = 1,省掉不必要的协议判断;
调整qdisc:默认pfifo_fast在突发流量下易丢包,换成fq_codel更稳:

tc qdisc replace dev eth0 root fq_codel

改完别忘了跑个iperf3对比:同一台机器,同样发包大小和速率,优化前后延迟抖动能差3倍以上。发包不是越快越好,而是“稳准快”——包不丢、延不抖、吞吐拉满,这才是协议栈该有的样子。