在嵌入式开发中,使用 lwIP 实现 WebSocket 客户端时,偶尔会遇到反复连接导致 TCP PCB(Protocol Control Block)泄漏,最终连接数达到上限(如 4)后无法再建立新连接的问题。本文将结合实际案例,分析问题原因并给出彻底解决方案。
问题现象
设备端 WebSocket 客户端反复连接服务器,运行一段时间后,发现无法再建立新连接。通过调试 lwIP,发现 TCP PCB 数量不断增加,达到最大值后,后续连接全部失败。
#define MEMP_NUM_TCP_PCB 4
原因分析
lwIP 的 TCP PCB 用于管理每个 TCP 连接的状态。正常情况下,连接关闭后 PCB 会被释放。但在实际代码中,WebSocket 客户端反复连接时,旧的 PCB 没有被及时释放,导致 PCB 泄漏。主要原因有:
- 连接关闭时未主动调用
altcp_close
或altcp_abort
彻底释放 PCB。 - 新连接初始化前未检查并释放旧 PCB。
解决方案
1.关闭连接时彻底释放 PCB,在 wsock_close()
函数中,主动调用 altcp_close
,如失败则调用 altcp_abort
:
if (pws->pcb) {
altcp_arg(pws->pcb, NULL);
altcp_recv(pws->pcb, NULL);
altcp_err(pws->pcb, NULL);
altcp_poll(pws->pcb, NULL, 0);
altcp_sent(pws->pcb, NULL);
if (altcp_close(pws->pcb) != ERR_OK) {
altcp_abort(pws->pcb);
close_err = ERR_ABRT;
}
pws->pcb = NULL;
}
2. 修改lwipopts.h的LWIP_SOCKET宏定义:
#define LWIP_SOCKET 0
总结
问题的根本原因是同事一开始没有改LWIP_SOCKET这个宏,默认为1,出现连接失败会自动调用wsock_close()导致出现HardFault_handler,然后他把这段释放处理屏蔽了,能正常使用,但又导致TCP PCB未能正确释放。
#define LWIP_SOCKET 1
if(pws->pcb)
{
altcp_arg(pws->pcb, NULL);
altcp_recv(pws->pcb, NULL);
altcp_err(pws->pcb, NULL);
altcp_poll(pws->pcb, NULL, 0);
altcp_sent(pws->pcb, NULL);
// 主动关闭连接,彻底释放PCB资源
// if(altcp_close(pws->pcb) != ERR_OK)
// {
// altcp_abort(pws->pcb);
// close_err = ERR_ABRT;
// }
pws->pcb = NULL;
}
因为 lwIP WebSocket 客户端是基于 lwIP 的 TCP/ALTCP 原生 API 和 PCB 机制实现的,而不是基于 Socket API,LWIP_SOCKET 1
使能了 Socket API,导致 lwIP 内部在连接失败时自动调用 wsock_close()
,而如果 PCB 或相关资源未正确初始化或已被释放,wsock_close()
内部访问空指针或非法内存就会触发 HardFault。
正确的做法就是只需要修改lwipopts.h的LWIP_SOCKET宏定义为0,websocket_client.c源文件不需要修改:
#define LWIP_SOCKET 0