libmodbus 在 Windows 环境下报 “Invalid argument“ 的排错记录

libmodbus 在 Windows 环境下报 "Invalid argument" 的排错记录

最近在用 Qt 开发上位机,使用开源 C 语言库 libmodbus 与下位机进行 Modbus TCP 通信。在跨平台编译和测试时,遇到了一个底层网络机制导致的错误,这里记录一下排查和解决过程。

问题现象

在代码中,我使用 modbus_new_tcpmodbus_connect 成功建立了与下位机(模拟器)的连接。但在使用定时器轮询调用 modbus_read_registers 读取保持寄存器的数据时,程序立即报错:

数据读取失败: Invalid argument

检查了以下几点,均未发现问题:

  1. 接收数据的数组大小和指针传递规范(使用了 QVector.data() 传入底层,没有越界)。
  2. Modbus Slave 模拟器端显示 TCP 连接正常(处于 1 Connection 状态)。

排查过程

为了定位问题,我在连接代码中加入了 modbus_set_debug(mb, TRUE); 开启底层报文的日志打印。

再次运行后,控制台输出了请求报文,但在等待下位机回复时,暴露了真正的底层错误信息:

[00][09][00][00][00][06][01][03][00][00][00][06]

Waiting for a confirmation...

ERROR Invalid socket descriptor 1060

数据读取失败: Invalid argument

日志表明:请求报文已经成功发出,但在等待接收的瞬间,底层的 Socket 描述符(1060)被判定为非法。

原因分析

这个问题的根源在于 libmodbus 在 Windows 和 Linux 系统下对 select() 函数及 fd_set 结构体的处理差异。

  1. FD_SETSIZE 限制 :在等待网络回复时,libmodbus 调用了底层的 select() 函数,这涉及到 fd_set 结构。系统默认规定 FD_SETSIZE = 64
  2. 跨平台机制差异
  • 在 Linux 下,Socket 描述符的值通常较小,可以直接作为数组索引。源码中存在 if (socket_fd >= FD_SETSIZE) 的安全越界检查。
  • 在 Windows (Winsock) 中,Socket 描述符是一个系统分配的句柄值(比如我这次被分配的是 1060),它完全有可能大于 64,且这在 Windows 下是合法的。
  1. 幽灵报错 :由于 1060 大于 64,代码触发了原作者针对 Linux 写的拦截逻辑,直接报错退出连接。同时,Windows 底层发生错误时并没有更新 C 语言的全局变量 errno,导致 libmodbus 读到了内存中的残留值,最终向外层抛出了一个毫无关联的 Invalid argument 错误。

解决方法

解决思路是解除系统默认的 64 限制,扩大 FD_SETSIZE 的值。

尝试过直接在 CMakeLists 中使用 target_compile_definitions 添加宏定义,但由于 Windows 的 <winsock2.h> 头文件内部默认定义了 #define FD_SETSIZE 64,如果包含顺序靠前,CMake 的指令会被覆盖失效。

最终有效方案:直接修改源码头文件

在项目中找到 libmodbus 的核心私有头文件(例如 modbus-private.h),在文件的最顶端(必须在所有 #include 之前) ,强制重定义 FD_SETSIZE

c 复制代码
#ifdef _WIN32
#undef FD_SETSIZE
#define FD_SETSIZE 65536
#endif

// 下方是文件原有的代码
#include <stdio.h>
// ...

最后一步 :关闭 IDE,手动删除项目生成的整个 build 构建目录,彻底清除旧的编译缓存。重新构建并运行项目,成功读取到下位机的返回报文,问题解决。


相关推荐
xcyxiner2 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner2 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner3 天前
DicomViewer (添加模型类)3
qt
xcyxiner4 天前
DicomViewer (目录调整) 2
qt
xcyxiner4 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
clint4565 天前
C++进阶(1)——前景提要
c++
夜悊5 天前
C++代码示例:进制数简单生成工具
c++
郝学胜_神的一滴5 天前
CMake 021: IF 条件判据详诠
c++·cmake
_wyt0015 天前
洛谷 B3930 [GESP202312 五级] 烹饪问题 题解
c++·gesp
玖玥拾6 天前
C/C++ 数据结构(七)栈、容器适配器
c语言·数据结构·c++··容器适配器