六、Modbus TCP 连接管理
6.1 TCP 连接生命周期
客户端 服务器 | | |------- SYN (SEQ=x) -------->| (1) 三次握手 |<------ SYN+ACK (SEQ=y,ACK=x+1)| |------- ACK (ACK=y+1) ------>| | | |------- Modbus Request ------>| (2) 数据传输 |<------ Modbus Response ------| | | |------- FIN (SEQ=z) -------->| (3) 四次挥手 |<------ ACK (ACK=z+1) --------| |<------ FIN (SEQ=w) ----------| |------- ACK (ACK=w+1) ------->|6.2 端口说明
| 端口 | 用途 |
|---|---|
| 502/tcp | Modbus TCP 标准端口(需特权用户绑定) |
| 802/tcp | Modbus Secure (TLS) |
| 5020-5029 | 常用非特权端口(避免与标准冲突) |
Linux 绑定低端口:
# 方法1:使用 sudo sudo python modbus_server.py # 方法2:授权特定程序 sudo setcap 'cap_net_bind_service=+ep' /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, host='0.0.0.0', port=502): 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(f"Modbus TCP server listening on {self.host}:{self.port}") while True: client_sock, addr = server_sock.accept() print(f"Connection from {addr}") thread = threading.Thread(target=self.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(f"Error: {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-rule='rule family="ipv4" source address="192.168.1.0/24" port protocol="tcp" port="502" accept' sudo firewall-cmd --reload