第四章:TCP协议栈配置——参数调优、窗口大小、拥塞控制与Keepalive
各位好,欢迎来到第四章。这一章我们聊聊TCP协议栈的深度配置。说实话,TCP这块儿是网络协议栈里最“玄学”的部分。为什么?因为参数太多,牵一发动全身。我见过不少工程师,把TCP参数改得面目全非,结果性能反而更差。嗯,咱们今天就来捋一捋,到底哪些参数值得调,怎么调。
4.1 TCP参数调优:别乱动,先理解
VxWorks的TCP协议栈,默认参数其实挺保守的。为什么?因为嵌入式系统千奇百怪,有的跑在卫星上,有的跑在工业机器人里。保守一点,至少不会出大乱子。但如果你要做高性能网络应用,比如视频传输、实时数据采集,那默认参数就不够看了。
我个人习惯,调优之前先问三个问题:
- 带宽多大? 100Mbps还是1Gbps?
- 延迟多高? 局域网1ms以内,还是跨洋200ms?
- 丢包率多少? 有线网络几乎不丢包,无线环境可能丢个1%-5%。
这三个问题决定了你该调什么参数。说白了,TCP调优就是“对症下药”。
核心参数一览:
| 参数名 | 默认值 | 说明 |
|---|---|---|
| tcp_sndbuf | 16KB | 发送缓冲区大小 |
| tcp_rcvbuf | 16KB | 接收缓冲区大小 |
| tcp_mss | 1460 | 最大报文段大小 |
| tcp_wmem | 4096-16384-131072 | 发送窗口自动调节范围 |
| tcp_rmem | 4096-87380-6291456 | 接收窗口自动调节范围 |
我在项目中遇到过,有人把发送缓冲区改成1MB,以为能提升吞吐量。结果呢?内存占用飙升,系统响应变慢。为什么?因为缓冲区太大,数据积压,反而增加了延迟。调优不是越大越好,是“刚刚好”最好。
4.2 TCP窗口大小:带宽延迟积的奥秘
TCP窗口大小,说白了就是“一次能发多少数据,不用等确认”。这个值太小,带宽利用率低;太大,丢包重传代价高。
有个经典公式:窗口大小 = 带宽 × 延迟。这叫“带宽延迟积”(BDP)。举个例子:
- 1Gbps网络,延迟10ms,BDP = 1Gbps × 0.01s = 10Mbit ≈ 1.25MB
- 所以窗口至少要1.25MB,才能跑满带宽。
你想想看,如果默认窗口只有16KB,那1Gbps的网络只能用到1%的带宽。这不是浪费吗?
我的调优建议:
在VxWorks中,通过tcp_rcvbuf和tcp_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_keepidle | 7200秒(2小时) | 空闲多久后开始发心跳 |
| tcp_keepintvl | 75秒 | 心跳间隔 |
| tcp_keepcnt | 8次 | 连续失败多少次后断开 |
你想想看,默认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路并发
我的调优步骤:
- 计算BDP: 100Mbps × 0.03s = 3Mbit ≈ 375KB。窗口设成512KB(留余量)。
- 选拥塞算法: 专线丢包少,用CUBIC,恢复快。
- Keepalive: 空闲60秒开始检测,间隔15秒,失败3次断开。
- 其他参数: 关闭Nagle算法(TCP_NODELAY),减少小包延迟。
调优后,吞吐量从原来的30Mbps提升到95Mbps,延迟稳定在35ms以内。效果很明显。
最后提醒一句:
TCP调优没有银弹。每个项目都要根据实际情况测试。我习惯先在测试环境跑一周,观察吞吐量、延迟、丢包率,再微调参数。别指望一次调好,迭代才是王道。
好了,第四章就到这里。下一章我们聊聊UDP协议栈的配置,那又是另一番天地了。