You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

作为TCP服务器,如何维持与PLC客户端的永久连接?

问题与解决方案

问题背景

作为TCP服务器,需连接一台TCP客户端角色的PLC:

  • PLC不会主动发送消息,仅接收服务器下发的控制指令
  • 要求服务器每0.5秒发送一次数据,PLC需在0.5秒内接收后进入下一循环
  • 当前核心问题:PLC主动断开连接时,如何自动重新建立并维持稳定的TCP连接
  • 现有实现思路:主线程运行图像分类模型(仅拥有0.5秒图像预测时间),负责发送业务重要数据;子线程专门承担连接维持的职责

现有代码

主线程代码

def send_data(self, data, tcpCilSock):
    self.setting.send_data = data.encode('utf-8')
    respMsg = self.setting.send_data
    tcpCilSock.sendall((bytes(respMsg)))
    log.info(data)

def run_thread(self):
    while True:
        try:
            if setting.is_tcp_client:
                tcpSvr.send_data(send_data, setting.tcpCilSock)
                break
        except:
            log.info('main thread is failed')

子线程代码

def run(self):
    while True:
        try:                    
            self.tcpSvr.send_data(content, setting.tcpcilsock)
        except:
            log.info('sub thread is failed')

子线程维持连接的实现方案

子线程需承担监听新连接、实时检测连接状态、断开后自动触发重连的核心职责,同时要保证与主线程共享socket资源的线程安全性:

  1. 子线程核心逻辑

    • 初始化时启动TCP服务器监听,等待PLC客户端发起连接
    • 连接建立后,通过定期发送心跳包(工业场景建议使用PLC可识别的专用心跳指令)检测连接存活状态
    • 一旦捕获到发送/接收异常(如ConnectionResetError、BrokenPipeError),立即标记当前socket失效,重新进入监听等待PLC重连
    • 用线程锁保护全局socket对象和连接状态标记,避免主线程使用失效连接
  2. 优化后的子线程代码示例

import threading
import socket
import time
import log

class ConnectionMaintainThread(threading.Thread):
    def __init__(self, setting):
        super().__init__()
        self.setting = setting
        # 初始化服务器socket,设置端口复用避免断开后端口占用
        self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.server_socket.bind(('你的服务器IP', 目标端口))  # 替换为实际IP和端口
        self.server_socket.listen(1)
        self.conn_lock = threading.Lock()  # 线程安全锁
        self.daemon = True  # 设置为守护线程,随主线程退出

    def run(self):
        while True:
            log.info("等待PLC客户端连接...")
            try:
                # 等待PLC发起连接
                client_socket, addr = self.server_socket.accept()
                log.info(f"PLC已连接,设备地址:{addr}")
                # 线程安全更新全局连接状态
                with self.conn_lock:
                    self.setting.tcpCilSock = client_socket
                    self.setting.is_tcp_client = True
                
                # 进入连接维持循环,定期发送心跳检测
                while True:
                    try:
                        # 发送专用心跳指令(可根据PLC协议调整)
                        client_socket.sendall("PLC_HEARTBEAT".encode('utf-8'))
                        time.sleep(0.2)  # 心跳间隔小于0.5秒,保证及时检测断开
                    except (socket.error, BrokenPipeError, ConnectionResetError):
                        log.info("PLC连接已断开,重新等待连接...")
                        # 标记连接失效并关闭旧socket
                        with self.conn_lock:
                            self.setting.is_tcp_client = False
                            self.setting.tcpCilSock.close()
                        break
            except Exception as e:
                log.info(f"连接监听异常:{str(e)}")
                time.sleep(1)  # 异常后延迟重试,避免资源占用
  1. 主线程配合调整
    主线程发送数据前需通过线程锁确认连接有效性,避免使用失效socket:
def send_data(self, data, tcpCilSock):
    try:
        with self.setting.conn_lock:
            if not self.setting.is_tcp_client:
                log.info("无有效PLC连接,跳过本次数据发送")
                return
            send_bytes = data.encode('utf-8')
            tcpCilSock.sendall(send_bytes)
            log.info(f"已下发控制指令:{data}")
    except Exception as e:
        log.info(f"指令发送失败:{str(e)}")
        # 主动标记连接失效,触发子线程重连
        with self.setting.conn_lock:
            self.setting.is_tcp_client = False
  1. 工业场景注意事项
    • 心跳包需使用PLC可识别的格式,避免干扰业务指令执行
    • 线程锁需严格控制临界区(socket操作、连接状态更新),防止线程冲突
    • 服务器socket必须设置SO_REUSEADDR,避免断开后端口被占用无法重启监听
    • 异常捕获需精准区分网络超时与真实连接断开,避免误判

内容的提问来源于stack exchange,提问作者TT Aano

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 20:48:19