Modbus RTU 与 Modbus TCP 深入指南-Modbus TCP 连接管理
六、Modbus TCP 连接管理6.1 TCP 连接生命周期客户端 服务器 | | |------- SYN (SEQx) --------| (1) 三次握手 |------ SYNACK (SEQy,ACKx1)| |------- ACK (ACKy1) ------| | | |------- Modbus Request ------| (2) 数据传输 |------ Modbus Response ------| | | |------- FIN (SEQz) --------| (3) 四次挥手 |------ ACK (ACKz1) --------| |------ FIN (SEQw) ----------| |------- ACK (ACKw1) -------|6.2 端口说明端口用途502/tcpModbus TCP 标准端口需特权用户绑定802/tcpModbus Secure (TLS)5020-5029常用非特权端口避免与标准冲突Linux 绑定低端口# 方法1使用 sudo sudo python modbus_server.py # 方法2授权特定程序 sudo setcap cap_net_bind_serviceep /usr/bin/python3 # 方法3端口转发推荐 sudo iptables -t nat -A PREROUTING -p tcp --dport 502 -j REDIRECT --to-port 50206.3 TCP Keep-Alive 配置为防止僵尸连接启用 Keep-Alive# Python 配置 sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) # Linux 系统级配置/etc/sysctl.conf net.ipv4.tcp_keepalive_time 7200 # 空闲2小时后发送探测 net.ipv4.tcp_keepalive_intvl 75 # 探测间隔75秒 net.ipv4.tcp_keepalive_probes 9 # 探测9次后断开6.4 服务器端实现支持多客户端import socket import threading class ModbusTCPServer: def __init__(self, host0.0.0.0, port502): self.host host self.port port self.holding_registers [0] * 65536 # 模拟寄存器 def start(self): server_sock socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_sock.bind((self.host, self.port)) server_sock.listen(5) print(fModbus TCP server listening on {self.host}:{self.port}) while True: client_sock, addr server_sock.accept() print(fConnection from {addr}) thread threading.Thread(targetself.handle_client, args(client_sock,)) thread.daemon True thread.start() def handle_client(self, sock): try: while True: # 读取MBAP头部7字节 mbap sock.recv(7) if len(mbap) 7: break trans_id, proto_id, length, unit_id struct.unpack(HHHB, mbap) if proto_id ! 0: continue # 读取PDU pdu_len length - 1 pdu sock.recv(pdu_len) # 处理请求 response_pdu self.process_pdu(pdu) # 发送响应 response_mbap struct.pack(HHHB, trans_id, 0, len(response_pdu) 1, unit_id) sock.send(response_mbap response_pdu) except Exception as e: print(fError: {e}) finally: sock.close() def process_pdu(self, pdu): 处理PDU并返回响应PDU if len(pdu) 1: return b func pdu[0] if func 0x03: # 读保持寄存器 if len(pdu) 5: return self.exception_response(func, 0x03) address (pdu[1] 8) | pdu[2] count (pdu[3] 8) | pdu[4] if count 1 or count 125: return self.exception_response(func, 0x03) # 读取寄存器值 data bytearray() for i in range(count): val self.holding_registers[address i] data.append((val 8) 0xFF) data.append(val 0xFF) return bytes([0x03, 2*count]) data elif func 0x06: # 写单个寄存器 if len(pdu) 5: return self.exception_response(func, 0x03) address (pdu[1] 8) | pdu[2] value (pdu[3] 8) | pdu[4] self.holding_registers[address] value return pdu # 回显请求 else: return self.exception_response(func, 0x01) # 非法功能 def exception_response(self, func, exception_code): return bytes([func | 0x80, exception_code]) # 启动服务器 server ModbusTCPServer() server.start()6.5 防火墙与监控命令# 查看端口监听状态 sudo netstat -tulnp | grep 502 # 查看当前TCP连接 ss -tn | grep :502 # 实时监控连接数 watch -n 1 ss -tn | grep :502 | wc -l # 抓包分析 sudo tcpdump -i eth0 -s 0 -A tcp port 502 and host 192.168.1.100 -w modbus.pcap # Ubuntu 防火墙开放端口 sudo ufw allow from 192.168.1.0/24 to any port 502 proto tcp # CentOS 防火墙 sudo firewall-cmd --permanent --add-rich-rulerule familyipv4 source address192.168.1.0/24 port protocoltcp port502 accept sudo firewall-cmd --reload