第四章:TCP协议栈配置——参数调优、窗口大小、拥塞控制与Keepalive

各位好,欢迎来到第四章。这一章我们聊聊TCP协议栈的深度配置。说实话,TCP这块儿是网络协议栈里最“玄学”的部分。为什么?因为参数太多,牵一发动全身。我见过不少工程师,把TCP参数改得面目全非,结果性能反而更差。嗯,咱们今天就来捋一捋,到底哪些参数值得调,怎么调。

4.1 TCP参数调优:别乱动,先理解

VxWorks的TCP协议栈,默认参数其实挺保守的。为什么?因为嵌入式系统千奇百怪,有的跑在卫星上,有的跑在工业机器人里。保守一点,至少不会出大乱子。但如果你要做高性能网络应用,比如视频传输、实时数据采集,那默认参数就不够看了。

我个人习惯,调优之前先问三个问题:

  • 带宽多大? 100Mbps还是1Gbps?
  • 延迟多高? 局域网1ms以内,还是跨洋200ms?
  • 丢包率多少? 有线网络几乎不丢包,无线环境可能丢个1%-5%。

这三个问题决定了你该调什么参数。说白了,TCP调优就是“对症下药”。

核心参数一览:

参数名默认值说明
tcp_sndbuf16KB发送缓冲区大小
tcp_rcvbuf16KB接收缓冲区大小
tcp_mss1460最大报文段大小
tcp_wmem4096-16384-131072发送窗口自动调节范围
tcp_rmem4096-87380-6291456接收窗口自动调节范围

我在项目中遇到过,有人把发送缓冲区改成1MB,以为能提升吞吐量。结果呢?内存占用飙升,系统响应变慢。为什么?因为缓冲区太大,数据积压,反而增加了延迟。调优不是越大越好,是“刚刚好”最好。

4.2 TCP窗口大小:带宽延迟积的奥秘

TCP窗口大小,说白了就是“一次能发多少数据,不用等确认”。这个值太小,带宽利用率低;太大,丢包重传代价高。

有个经典公式:窗口大小 = 带宽 × 延迟。这叫“带宽延迟积”(BDP)。举个例子:

  • 1Gbps网络,延迟10ms,BDP = 1Gbps × 0.01s = 10Mbit ≈ 1.25MB
  • 所以窗口至少要1.25MB,才能跑满带宽。

你想想看,如果默认窗口只有16KB,那1Gbps的网络只能用到1%的带宽。这不是浪费吗?

我的调优建议:

在VxWorks中,通过tcp_rcvbuftcp_sndbuf设置窗口大小。我一般这样算:

/* 假设带宽100Mbps,延迟50ms */
/* BDP = 100 * 10^6 * 0.05 / 8 = 625000 bytes ≈ 610KB */
/* 我习惯再加20%余量 */
#define TCP_WINDOW_SIZE (610 * 1024 * 1.2)  /* 约732KB */

/* 在代码中设置 */
int optval = TCP_WINDOW_SIZE;
setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &optval, sizeof(optval));
setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &optval, sizeof(optval));

注意一点:setsockopt设置的值,内核可能会翻倍。因为内核内部有对齐机制。我曾经踩过这个坑,设了1MB,结果实际用了2MB。嗯,调试时记得用getsockopt确认一下实际值。

4.3 TCP拥塞控制:选对算法很重要

拥塞控制,说白了就是“网络堵了怎么办”。VxWorks默认用的是Reno算法,这是最经典的。但经典不代表最好。Reno在丢包时会把窗口减半,这对高延迟网络很不友好。

我个人的经验:

  • 局域网环境: Reno或NewReno就够用,丢包少,算法简单。
  • 高延迟网络(卫星、跨洋): 用CUBIC或BBR。CUBIC在丢包后恢复快,BBR更激进,能充分利用带宽。
  • 无线网络: 用Westwood或Vegas。它们能区分“拥塞丢包”和“无线丢包”,避免误判。

避坑指南:

我曾经在一个无线传感器网络项目里,默认用Reno算法。结果呢?无线环境偶尔丢包,Reno以为是拥塞,窗口减半,吞吐量直接掉到原来的1/3。后来换成Westwood,问题解决了。所以,选算法前先搞清楚你的网络环境。

在VxWorks中切换拥塞算法,通常需要修改内核配置:

/* 在config.h或prjParams.h中 */
#define INCLUDE_TCP_CONGESTION_CUBIC
/* 或者 */
#define INCLUDE_TCP_CONGESTION_BBR

/* 运行时也可以动态切换 */
int algo = TCP_CONG_CUBIC;
setsockopt(sock, IPPROTO_TCP, TCP_CONGESTION, &algo, sizeof(algo));

注意:不是所有VxWorks版本都支持所有算法。我建议先查一下你的版本支持哪些。如果不支持,可以自己移植,但工作量不小。

4.4 TCP Keepalive配置:别让连接“假死”

TCP Keepalive,说白了就是“心跳包”。用来检测对端是否还活着。默认情况下,VxWorks的Keepalive是关闭的。为什么?因为嵌入式系统资源宝贵,不想浪费带宽发心跳。

但如果你做的是长连接应用,比如远程监控、服务器通信,那Keepalive就很重要了。否则,对端挂了,你这边还傻傻地等着,连接就“假死”了。

Keepalive有三个关键参数:

参数默认值说明
tcp_keepidle7200秒(2小时)空闲多久后开始发心跳
tcp_keepintvl75秒心跳间隔
tcp_keepcnt8次连续失败多少次后断开

你想想看,默认2小时才发第一个心跳,这太长了。我一般会改成:

/* 开启Keepalive */
int keepalive = 1;
setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &keepalive, sizeof(keepalive));

/* 设置参数 */
int idle = 60;      /* 60秒空闲后开始检测 */
int interval = 10;  /* 每10秒发一次心跳 */
int count = 3;      /* 连续3次无响应就断开 */

setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(idle));
setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, &interval, sizeof(interval));
setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, &count, sizeof(count));

我的经验:

在工业控制项目中,我习惯把Keepalive间隔设成10-30秒。太频繁浪费带宽,太慢又检测不及时。另外,记得在应用层也做一层心跳,双重保险。我曾经遇到过,TCP Keepalive检测到对端挂了,但应用层还在等数据,结果卡死了。所以,应用层心跳和TCP Keepalive配合使用,效果最好。

4.5 实战总结:一个完整的调优案例

最后,我分享一个实际案例。去年做的一个视频传输项目,要求:

  • 带宽:100Mbps
  • 延迟:30ms(跨省专线)
  • 丢包率:<0.1%
  • 连接数:50路并发

我的调优步骤:

  1. 计算BDP: 100Mbps × 0.03s = 3Mbit ≈ 375KB。窗口设成512KB(留余量)。
  2. 选拥塞算法: 专线丢包少,用CUBIC,恢复快。
  3. Keepalive: 空闲60秒开始检测,间隔15秒,失败3次断开。
  4. 其他参数: 关闭Nagle算法(TCP_NODELAY),减少小包延迟。

调优后,吞吐量从原来的30Mbps提升到95Mbps,延迟稳定在35ms以内。效果很明显。

最后提醒一句:

TCP调优没有银弹。每个项目都要根据实际情况测试。我习惯先在测试环境跑一周,观察吞吐量、延迟、丢包率,再微调参数。别指望一次调好,迭代才是王道。

好了,第四章就到这里。下一章我们聊聊UDP协议栈的配置,那又是另一番天地了。