????????在嵌入式開發中,使用 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
?