背景
有Modbus和scpi两种协议,在使用过程中要切换,每次切换要给给对应协议绑定端口
问题
在每次切换协议之前都这样操作,把所有连接都关闭仔进行重新绑定
c
// 尝试禁用Modbus
eMBDisable();
// 尝试关闭SCPI服务器的监听和客户端连接
if (server_state.listen_pcb != NULL) {
tcp_close(server_state.listen_pcb);
server_state.listen_pcb = NULL;
}
if (server_state.client_pcb != NULL) {
tcp_close(server_state.client_pcb);
server_state.client_pcb = NULL;
server_state.connected = 0;
}
但是失败
结果:这样修改后,开机后protocol=1能直接连上设置的 192.168.1.111 port: 501 Modbus
面板改成选scpi后两种协议都不能使用,再改回Modbus后modbus可以使用,scpi不能用
开机后protocol=0能直接连上设置的 192.168.1.111 port: 501 scpi , 此时端口成功绑定
改成Modbus后modbus可以使用,scpi不能用
面板再改成选scpi后两种协议都不能使用,此时端口绑定失败
原因
这次失败的原因在于 FreeModbus 库的 eMBDisable() 函数默认只停止协议状态机,并不会释放底层的 TCP 监听端口(Listening Socket)。
- 资源未释放 :在 Modbus TCP 模式下,
eMBDisable()最终调用的是vMBTCPPortDisable(),而在porttcp.c中,这个函数仅尝试关闭已连接的客户端 PCB,完全没有处理正在监听的 PCB (pxPCBListen)。 - 库配置限制 :FreeModbus 默认将
MB_PORT_HAS_CLOSE设置为0,这导致eMBClose()函数实际上是空的。 - 现象解释 :
- 当从 Modbus 切换到 SCPI 时,Modbus 的监听端口依然在占用(因为它没被关闭)。
- SCPI 尝试
tcp_bind同一个端口,自然返回8(ERR_USE)。 - 当切回 Modbus 时,Modbus 重新初始化并覆盖了之前的指针,但旧的监听 PCB 依然挂在协议栈里(资源泄露),虽然能连上,但此时协议栈状态已经非常混乱。
最终解决方法
我们需要强制开启 Modbus 库的资源释放功能,并在切换时显式调用它。
步骤 1:开启 Modbus 库的关闭功能
在 mbconfig.h中,将 MB_PORT_HAS_CLOSE 定义为 1。
添加了:
c
#define MB_PORT_HAS_CLOSE ( 1 )
步骤 2:在切换逻辑中添加彻底关闭调用
在 user_mb_app_m.c的 cleanup_all_protocols 函数中,添加对 eMBClose() 的调用。
核心修改如下:
c
static void cleanup_all_protocols(void)
{
// 1. 禁用 Modbus 状态机
eMBDisable();
// 2. 彻底关闭 Modbus 端口监听(关键:释放 pxPCBListen)
eMBClose();
// 3. 彻底清理 SCPI 资源
if (server_state.client_pcb != NULL) {
tcp_abort(server_state.client_pcb);
server_state.client_pcb = NULL;
server_state.connected = 0;
}
if (server_state.listen_pcb != NULL) {
tcp_close(server_state.listen_pcb);
server_state.listen_pcb = NULL;
}
}
验证说明
- 为什么这次会成功?
- 因为
eMBClose()会通过pvMBFrameCloseCur指针最终调用到porttcp.c中的vMBTCPPortClose()。 vMBTCPPortClose()会显式调用prvvMBPortReleaseClient( pxPCBListen ),这才会真正向 LwIP 发出关闭监听端口的指令。
- 因为
- 后续测试 :
再次切换。切换到 SCPI 后,观察到端口顺利绑定。