
通信嵌入式物联网【免费下载链接】libmodbusA Modbus library for Linux, Mac OS, FreeBSD and Windows项目地址https://gitcode.com/gh_mirrors/li/libmodbus点击查看免费下载modbus_tcp_pi_accept 是 libmodbus 中基于协议无关Protocol IndependentPI寻址的 TCP 后端在服务器端的核心接入函数负责从监听队列中提取挂起连接并接管到 libmodbus 上下文。本文以 docs/modbus_tcp_pi_accept.md 为主线结合 src/modbus-tcp.c 的实现源码与 tests/unit-test-server.c 的测试用例系统讲解该函数的调用方式、底层原理、错误处理与完整可运行示例帮助你搭建同时支持 IPv6/IPv4 的 Modbus TCP 服务器。函数概览modbus_tcp_pi_accept()是 libmodbus 公开 API 之一声明位于 src/modbus-tcp.hMODBUS_API int modbus_tcp_pi_accept(modbus_t *ctx, int *s);项目说明所属后端TCP PI_modbus_tcp_pi_backend由modbus_new_tcp_pi()创建上下文原型int modbus_tcp_pi_accept(modbus_t *ctx, int *s)功能从挂起连接队列中提取第一个连接创建新 socket 并存入上下文返回值成功返回新 socket失败返回 -1 并设置 errno该函数只用于服务器端。作为客户端发起连接时应使用modbus_connect()内部由_modbus_tcp_pi_connect完成 socket 创建与getaddrinfo解析参见 src/modbus-tcp.c。工作原理从监听队列到上下文接管根据 docs/modbus_tcp_pi_accept.md 的描述该函数的行为本质上是 POSIXaccept()的封装提取监听 socket 挂起队列中的第一个连接为其创建新的已连接 socket并保存到modbus_t上下文中。如果系统提供accept4()则使用带SOCK_CLOEXEC标志的版本否则回退到accept()。底层实现位于 src/modbus-tcp.c关键流程如下int modbus_tcp_pi_accept(modbus_t *ctx, int *s) { struct sockaddr_in6 addr; socklen_t addrlen; if (ctx NULL) { errno EINVAL; return -1; } addrlen sizeof(addr); #ifdef HAVE_ACCEPT4 /* Inherit socket flags and use accept4 call */ ctx-s accept4(*s, (struct sockaddr *) addr, addrlen, SOCK_CLOEXEC); #else ctx-s accept(*s, (struct sockaddr *) addr, addrlen); #endif ... }对照实现可以提炼出以下几个关键点地址结构使用 IPv6 的sockaddr_in6。这是 PI 后端与 IPv4 专用modbus_tcp_accept()的核心差异——后者使用struct sockaddr_in见 src/modbus-tcp.c。由于accept()接受通用struct sockaddr *指针同一个监听 socket 既能接受 IPv4 也能接受 IPv6 客户端取决于监听地址与系统配置。accept4()SOCK_CLOEXEC在编译时通过HAVE_ACCEPT4宏判断由 configure 探测。SOCK_CLOEXEC保证新 socket 在exec()时自动关闭避免子进程意外继承网络描述符造成句柄泄漏这是多进程服务器如 fork 后 exec 的场景的重要安全特性。描述符写入上下文新连接 socket 被赋值给ctx-s。此后所有收发、超时控制、调试输出都基于该描述符进行即 libmodbus 已接管这条连接后续直接调用modbus_receive()/modbus_reply()即可处理请求。FD_SETSIZE防御性检查实现中对ctx-s FD_SETSIZE做了检查超限时关闭 socket、将ctx-s复位为 -1 并返回EINVAL。这与库内所有基于select()的实现一致内部_modbus_tcp_select同样要求描述符小于FD_SETSIZE见 src/modbus-tcp.c。调试输出开启调试模式modbus_set_debug(ctx, TRUE)后连接被接受时会调用inet_ntop(AF_INET6, addr.sin6_addr, buf, INET6_ADDRSTRLEN)打印客户端地址输出形如Client connection accepted from ::1.地址解析失败时打印Client connection accepted from unparsable IP.。返回值与错误处理按 docs/modbus_tcp_pi_accept.md 的约定函数成功时返回新 socket否则返回 -1 并设置 errno。具体而言成功返回ctx-s即新连接的已连接 socket 描述符同时已存入上下文。ctx NULL返回 -1errno EINVAL见实现前段。accept()/accept4()失败返回 -1errno 由系统调用设置常见值包括EAGAIN/EWOULDBLOCK监听 socket 为非阻塞且队列为空EINTR被信号中断EMFILE/ENFILE进程或系统文件描述符耗尽ECONNABORTED连接在建立过程中被对端中止。描述符超限返回 -1errno EINVAL。调用方应使用modbus_strerror(errno)获取可读的错误描述。注意返回 -1 并不代表监听 socket 失效服务器通常可以继续循环调用本函数接受后续连接。完整使用示例原文档给出的是最小骨架下面结合 tests/unit-test-server.c文档明确推荐的详细示例补全为一个可编译、可运行的 IPv6 Modbus TCP 服务器流程#include errno.h #include stdio.h #include stdlib.h #include unistd.h #include modbus.h int main(void) { int s -1; modbus_t *ctx; modbus_mapping_t *mb_mapping; uint8_t query[MODBUS_TCP_MAX_ADU_LENGTH]; int rc; /* 1. 创建 TCP PI 上下文监听所有 IPv6 地址的 1502 端口 */ ctx modbus_new_tcp_pi(::0, 1502); if (ctx NULL) { fprintf(stderr, Unable to allocate libmodbus context\n); return -1; } /* 2. 建立数据映射表线圈、输入、保持寄存器、输入寄存器 */ mb_mapping modbus_mapping_new(500, 500, 500, 500); if (mb_mapping NULL) { fprintf(stderr, Failed to allocate the mapping: %s\n, modbus_strerror(errno)); modbus_free(ctx); return -1; } /* 3. 创建监听 socket允许 1 个挂起连接 */ s modbus_tcp_pi_listen(ctx, 1); if (s -1) { fprintf(stderr, Unable to listen TCP PI connection\n); modbus_mapping_free(mb_mapping); modbus_free(ctx); return -1; } /* 4. 接受连接新 socket 存入 ctx随后由 libmodbus 接管 */ if (modbus_tcp_pi_accept(ctx, s) -1) { fprintf(stderr, Unable to accept connection: %s\n, modbus_strerror(errno)); close(s); modbus_mapping_free(mb_mapping); modbus_free(ctx); return -1; } /* 5. 服务器主循环接收请求并应答 */ for (;;) { rc modbus_receive(ctx, query); if (rc -1) { break; } modbus_reply(ctx, query, rc, mb_mapping); } /* 6. 清理关闭监听 socket释放映射与上下文 */ close(s); modbus_mapping_free(mb_mapping); modbus_free(ctx); return 0; }代码中的MODBUS_TCP_MAX_ADU_LENGTH260 字节见 src/modbus-tcp.h是 TCP 模式下请求缓冲区的推荐大小对应253 字节 PDU 7 字节 MBAP 头的 Modbus TCP ADU 规范。测试用例中的真实调用链tests/unit-test-server.c 在tcppi模式下正是按上述顺序使用这三个 TCP PI 函数L86-L88、L128-L130} else if (use_backend TCP_PI) { ctx modbus_new_tcp_pi(ip_or_device, 1502); /* 默认 ip_or_device 为 ::1 */ ... s modbus_tcp_pi_listen(ctx, 1); modbus_tcp_pi_accept(ctx, s);配合 tests/unit-test-client.c 中的tcppi客户端模式即可通过./unit-test-server tcppi ::1与./unit-test-client tcppi完成端到端的 IPv6 协议自检两个程序均解析tcppi命令行参数。这与 tests/unit-tests.sh 驱动的自动化测试流程一致。与 modbus_tcp_accept 的对比PI 版本与 IPv4 专用版本行为高度相似差异集中在地址族与相关常量上可对照 docs/modbus_tcp_accept.md维度modbus_tcp_pi_acceptmodbus_tcp_accept适用地址族IPv6亦可处理 IPv4 映射地址仅 IPv4上下文创建modbus_new_tcp_pi(node, service)modbus_new_tcp(ip, port)地址结构struct sockaddr_in6struct sockaddr_in调试输出缓冲INET6_ADDRSTRLENinet_ntop(AF_INET6, ...)INET_ADDRSTRLENinet_ntop(AF_INET, ...)accept4/SOCK_CLOEXEC同样支持HAVE_ACCEPT4同样支持两函数均受HAVE_ACCEPT4宏控制使用accept4()还是accept()均对FD_SETSIZE做防御检查。选择依据很简单需要 IPv6 或主机名/服务名解析用 PI 系列固定 IPv4 点分地址用传统系列。配套函数与典型服务器搭建流程modbus_tcp_pi_accept不能独立使用完整的 PI 服务器链路是modbus_new_tcp_pi(node, service)创建上下文src/modbus-tcp.cnode为主机名或 IP 字符串如::1、server.com传NULL或空串表示监听任意地址service为服务名或端口字符串如502、1502传NULL或空串时默认502v3.1.8 起支持NULL详情见 docs/modbus_new_tcp_pi.md。modbus_tcp_pi_listen(ctx, nb_connection)创建并绑定监听 socketsrc/modbus-tcp.c内部通过getaddrinfo()以AF_UNSPECSOCK_STREAMAI_PASSIVE解析地址因此一套代码同时支持 IPv4 与 IPv6nb_connection是listen()的挂起连接队列上限返回监听 socket详情见 docs/modbus_tcp_pi_listen.md。modbus_tcp_pi_accept(ctx, s)接受连接本文主题。modbus_receive()/modbus_reply()处理请求或使用modbus_receive_confirmation()/modbus_send_raw_request()进行更底层的报文收发。上下文内部结构modbus_tcp_pi_t保存了t_id事务 ID、端口、node与service字符串指针定义在 src/modbus-tcp-private.h。监听 socket 与已连接 socket 是两个独立描述符本函数只接管已连接 socket 到ctx-s监听 socket 仍由调用方持有。资源释放与注意事项监听 socket 与已连接 socket 分开释放原文档示例结尾的close(s)关闭的是监听 socket已连接 socketctx-s应在退出前通过modbus_close(ctx)关闭内部执行shutdown(SHUT_RDWR)close见 src/modbus-tcp.c文档见 docs/modbus_close.md最后modbus_free(ctx)释放上下文内存。端口权限Modbus 默认端口 502 小于 1024在多数 Unix 系统上需要 root 权限才能绑定测试与开发时建议使用1502等非特权端口tests 目录中的服务器示例即统一使用 1502。一次一连接模型如 docs/modbus_tcp_pi_listen.md 所述unit-test-server.c是简单但仅处理单个连接的示例需要同时服务多个客户端时可参考 tests/bandwidth-server-many-up.c 用select() 多 socket 管理的方式改造该示例基于 IPv4 的modbus_tcp_listen/accept但多连接管理思路对 PI 后端同样适用。ctx NULL检查调用前务必确认modbus_new_tcp_pi()返回非空内存不足时返回NULL且errno ENOMEM否则本函数会返回EINVAL。错误可恢复性accept()失败如瞬时资源不足不代表监听 socket 失效服务器循环中应区分致命错误与可重试错误必要时配合 errno 判断后继续监听。小结modbus_tcp_pi_accept()是连接 libmodbus 监听层与应用处理层的桥梁它把 POSIXaccept()的语义封装进 libmodbus 上下文模型通过accept4()SOCK_CLOEXEC提供进程安全通过sockaddr_in6与getaddrinfo体系打通 IPv6 与现代主机名解析。掌握它再配合modbus_new_tcp_pi()、modbus_tcp_pi_listen()与modbus_receive()/modbus_reply()即可在 Linux、macOS、FreeBSD 与 Windows 上构建协议无关的 Modbus TCP 服务器——这正是 libmodbus 作为跨平台 Modbus 库的核心能力所在参见项目 README.md。赞分享通信嵌入式物联网【免费下载链接】libmodbusA Modbus library for Linux, Mac OS, FreeBSD and Windows项目地址https://gitcode.com/gh_mirrors/li/libmodbus点击查看免费下载相关推荐使用 libmodbus 构建 Modbus TCP 服务器modbus_tcp_listen 详解与多连接实战使用 libmodbus 构建 Modbus TCP 服务器modbus_tcp_listen 详解与多连接实战 导读 modbus_tcp_listen 是通信嵌入式物联网libmodbus 的 modbus_new_tcp 函数创建 Modbus TCP/IPv4 客户端与服务器上下文libmodbus 的 modbus_new_tcp 函数创建 Modbus TCP/IPv4 客户端与服务器上下文 导读 modbus_new_tcp 是通信嵌入式物联网libmodbus 的 modbus_tcp_pi_listen()基于 Protocol Independent 接口创建 IPv6 Modbus TCP 服务器libmodbus 的 modbus_tcp_pi_listen 基于 Protocol Independent 接口创建 IPv6 Modbus TCP 服通信嵌入式物联网上一篇10分钟完成黑苹果配置OpCore Simplify图形化工具终极指南下一篇技术揭秘猫抓如何实现MPD/DASH多格式流媒体解析 | 完整实践指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考