
干PLC通信调试这些年汇川EASY系列我接触得不算少但真正把它的以太网口拿来做socket从站、而不是单纯做Modbus TCP从站的项目反而是最近两三年才多起来。原因也很简单现场设备越来越需要“主动连接”“批量上传”“自定义帧交互”这类能力标准的Modbus TCP虽然稳定但灵活性很受限。这篇文章就把我自己的做法完整捋一遍覆盖设计思路、PLC侧从站程序编写、上位机联调以及一堆只有踩过坑才会注意到的细节。不管你是第一次听说socket从站这个概念还是已经写了几版但总在某个环节卡住这篇都适合你花几分钟过一遍。1. 内容整体设计与思路拆解1.1 为什么选择socket而不是Modbus TCP很多人一提到PLC以太网通信第一反应就是Modbus TCP。这东西确实好上手HMI、上位机组态软件基本都原生支持但真正做项目的时候你会发现它的局限挺明显。Modbus TCP本质上是一个主从轮询协议上位机作为主站不停地发请求PLC作为从站只能被动应答。这种模型在数据量小、点位固定、采集周期不敏感的场合没什么问题可一旦牵扯到变长数据、大批量数组上传或者需要PLC主动上报故障信息Modbus TCP就非常别扭。比如要上传一段几百字节的配方数据用Modbus TCP就要拆成多个寄存器地址反复读写不仅慢协议帧还显得臃肿。socket从站就完全是另一套思路。它基于TCP/IPPLC在以太网上创建一个TCP服务端打开一个端口等着客户端来连接。一旦连接建立数据交换是双向的、主动的想发什么就发什么。帧格式可以自己定义今天用固定长度结构体明天换成JSON都行。对于汇川EASY系列这种小型PLC来说这个能力其实是被很多工程师忽略的。我自己的偏好很明确只要能自己定义通信协议、数据量又不小、且不希望被轮询周期绑死的项目我优先用socket从站方案而不是硬塞进Modbus TCP的框架里。对比项Modbus TCP从站Socket TCP从站通信模型主从轮询只响应不主动客户端-服务端连接后双向收发协议帧固定Modbus帧结构可读但冗余自定义帧结构按需设计大数据包需拆寄存器多次读写一个帧直接承载任意长度数据主动上报基本实现困难天然支持开发门槛低现成控件多略高需自行约定协议调试手段简单需要抓包工具辅助1.2 从站角色的设计思路为什么让PLC做服务端这个项目标题里有个关键词是“从站”放在socket语境下我的理解就是PLC在通信里充当TCP服务端上位机软件、触摸屏或者另一个控制器作为TCP客户端主动来连。为什么要把PLC放在服务端位置而不是反过来让PLC当客户端主动去连上位机很多第一次做这个事的人会有疑问。我实际做下来的结论是对于车间通信这类场景上位机通常才是那条“主线”。MES、SCADA、视觉系统它们都是统一调度的角色由它们发起到各台PLC的连接逻辑上更顺也更好管理。再者如果PLC做客户端就得让它知道上位机的IP和端口上位机一旦换了IP或服务没启PLC这边就要处理各种连接失败的重试逻辑浪费PLC程序资源不说排查问题也麻烦。反过来PLC做服务端的好处非常明显。PLC启动后在固定端口监听就行不关心谁来连、什么时候连客户端连不上可以自己重试。即使上位机软件崩溃重启PLC端服务依然在等客户端恢复后再次接入即可。这个模型下PLC程序核心就三件事建服务、等连接、处理收发数据清爽很多。我见过有的方案是用smart PLC做智能从站把socket服务端跑在PLC里对外提供稳定的数据接口上位机侧再多的客户端都能同时接入这种架构在产线设备数据集中采集时特别实用。1.3 项目解决的痛点与适用场景梳理清楚这套socket从站方案本质上是为了解决三类项目的共性痛点第一类是设备数据需要主动上抛。比如设备发生故障停机时PLC希望立刻把故障码推送给上位机而不是等上位机下一轮轮询才能读到。第二类是通信协议需要定制。有些上位机是C#、C工程师写的他们希望后端拿到的是一段带校验的原始数据帧而不是把Modbus地址一一映射到数据库里。socket能让他们用最顺手的方式收发数据。第三类是多客户端并存。一台PLC的数据可能要同时发给本地HMI、远程监控系统、扫码枪系统等多个接收方socket服务端天然支持多连接虽然EASY系列在连接数量上不会太多但胜在每个连接都是独立通道互不干扰。这些场景合起来就是该项目标题背后最常见的实际需求。如果你正好也遇到这几种情况之一这篇文章能给你省下不少自己摸索的时间。2. 硬件与软件准备先确认你手头的EASY系列有哪些底牌2.1 硬件选型与端口能力确认做技术方案最怕的就是拿着型号臆想功能结果到了现场发现硬件根本没这个能力。所以第一步一定是打开选型手册确认你手里的汇川EASY系列PLC具体型号是不是自带以太网口、以太网口是否支持socket通信。EASY系列是一个家族不同子型号的网口能力并不完全一致。大部分带网口的型号都支持标准以太网通信但个别低成本型号可能只支持工程下载和Modbus TCP未必开放灵活的socket接口。这里我给一个非常实在的建议直接去汇川官网下载对应型号的硬件手册翻到“通信”章节看通信规格表里有没有TCP、UDP、Socket字样有的话再放心往下做。另外要留意网口类型。大部分EASY系列用的是标准RJ45百兆口用普通网线就能连。接法上如果PLC和上位机直连建议用直通线也行现在绝大多数网口都支持自动翻转如果走交换机那就更无所谓了。要注意的是网口指示灯状态一定要会看Link灯不亮就说明物理链路都有问题别急着查程序。硬件层面还有一个容易被忽略的指标——CPU处理能力。小型PLC的socket通信解包、组帧都是需要CPU参与的工作如果程序里还有大量运动控制或者高速逻辑CPU负荷一高通信响应就会变慢。所以做方案时最好把socket从站的数据量控制在合理范围比如每秒几十次交互完全没压力但如果你要它每秒收发几千包数据那就不现实了。2.2 编程软件与Socket指令库汇川EASY系列对应的编程软件不同时期的型号配套会有差异。老一批的EASY型号用AutoShop体系比较多新一些的或者中大型系列则更常见InoProShop但无论哪一套socket通信的编程思路本质上都是相通的。我的建议是开工前先把你软件的指令手册电子版下载到本地搜关键字“socket”“TCP”“通信”这几个词看清楚软件提供哪些现成功能块。很多工程师一上来就想自己造轮子从零写TCP协议栈这完全没必要。官方提供的socket功能块或者通信库一般至少包含这几个能力创建服务端、监听端口、等待客户端连接、接收数据、发送数据、关闭连接。你把它们当成串口通信里的初始化、读、写就行只是底层从RS485换成了以太网。我实际用下来更推荐用汇川官方提供的通信例程或者封装好的协议库而不是完全裸调socket底层接口。因为你从零写很可能漏掉连接管理、超时处理这些繁琐环节而官方库这些逻辑都已经处理过可靠性高得多。具体用ST语言还是梯形图取决于个人习惯。ST语言写通信状态机更直观尤其是多状态跳转的时候梯形图会变得非常臃肿。如果你对ST不太熟建议借这个项目顺便练一下通信程序用ST写真的比梯形图舒服太多。3. PLC侧从站程序设计核心配置与实操步骤3.1 配置IP与端口从站的关键起点PLC做socket从站第一件事是固定一个IP地址别用DHCP。现场交换机一旦重启DHCP分配的地址变了上位机就找不到了。固定IP的配置在各品牌PLC里都有专门页面EASY系列一般是在通信参数或者以太网配置里设置操作很简单填IP、子网掩码、默认网关就行。IP规划上有一条很重要的经验PLC和控制它的上位机最好都在同一个网段比如PLC设192.168.1.10/24上位机网卡设192.168.1.20/24。如果跨网段访问必须保证路由可达现场很多人卡在这一步两边IP在不同网段数据包根本送不进来还以为是程序有问题。端口选择上千万别随便用。Modbus TCP默认占用502端口使用socket通信时尽量避开一些知名端口选择一个高位端口比如60000、65000这类。我习惯用60000工位号这样多台设备连到同一台上位机时端口不容易冲突排查起来也直观。如果你在PLC程序里同时启用了Modbus TCP服务和socket TCP服务要注意它们不要绑同一个端口否则软件层面就会撞车轻则服务起不来重则整个以太网通信异常。记得提前把端口规划写进项目文档里。3.2 Socket服务端功能块调用过程接下来是核心部分在PLC里搭建socket服务端。整体流程可以抽象成这么几个厚实的状态初始化服务端 → 开始监听端口 → 等待客户端连接 → 连接建立后循环处理收发 → 客户端断开后回到等待连接状态。我建议把这个状态机写在ST程序里结构清晰后期改起来也方便。初始化阶段主要做两件事绑定端口、启动监听。不同软件版本里功能块名字可能略有不同但参数基本逃不出本地端口、连接超时、最大连接数这几项。拿EASY系列举例假设我开放端口60000就让服务端在该端口上做好监听准备。之后程序进入等待客户端连接阶段。有客户端请求接入时功能块会返回成功信号同时返回一个连接标识符。这个标识符在后续收发数据时必须带着因为通信库要用它来区分是哪条连接在收发数据。如果你支持多个客户端同时接入每个连接标识符都对应一个独立的通信通道。连接建立后收数据和发数据是两个并行处理的任务。收数据时PLC要做好接收缓冲区的管理及时把收到的帧读走并清空缓冲区不然下一帧数据来了没地方放就会丢帧。发送数据时要把待发送的帧内容放到发送缓冲区调用发送功能块一次性发出去。有点要注意的是小型PLC里的socket收发数据都是基于循环扫描的方式。数据不会像PC那样有独立线程去等待每条指令执行完就会继续跑后面的逻辑所以我们必须用“数据到达标志位”配合轮询来处理。收到一帧完整数据后程序的通信处理区会解析帧内容执行指令组装响应帧再发送出去。// 伪代码示意socket服务端主流程非实际厂商指令仅表达逻辑 bListenDone : FALSE; bClientConnected : FALSE; WHILE bServerRunning DO IF NOT bListenDone THEN bListenDone : SocketListen(Port : 60000); END_IF; IF bListenDone AND NOT bClientConnected THEN bClientConnected : SocketAccept(ClientHandle hClient); END_IF; IF bClientConnected THEN IF SocketRecv(hClient, RxBuffer, RxLen) THEN IF RxLen 0 THEN ProcessFrame(RxBuffer, RxLen); SocketSend(hClient, TxBuffer, TxLen); END_IF; END_IF; END_IF; END_WHILE;这段伪代码不是某个具体型号的真实指令但它体现的状态机结构是通用的。实际工程中有的EASY型号软件可能把上述接收、处理、回发逻辑拆得更细但主脉络一定是这个流程。3.3 自定义协议与数据解析建议既然走了socket通信协议就得自己定。我见过不少项目没有正式定义好协议就上手写代码结果联调时两边各按各的理解发包对不上然后互相推诿非常浪费时间。我的建议是在写代码前先花半小时在文档里把帧格式固定下来。一个成熟的通信帧至少要包含这几个要素帧头、命令字、数据长度、数据体、校验码。帧头是固定的20字节或32字节用来给接收方识别帧起始位置。命令字告诉对方这条帧是要做什么比如读取设备状态、写入参数、下发配方。数据长度用来标明数据体有多少字节方便接收方判断一帧是否完整。数据体放实际交互的数据。校验码用CRC16或CRC32都行用来防干扰、防错帧。字节序这个问题必须特别提醒。PLC端和上位机端的CPU平台可能不同大小端规则并不一定一致。比如西门子和汇川PLC内部多字节变量的存储方式就可能不一样。如果通信双方约定用UInt16存储温度值但一边按大端解析一边按小端发送那读出来的数值永远是错的。办法就是在协议文档里明确写清大小端规则并在上位机测试阶段专门验证一次。心跳机制也不能省。上位机每隔1到2秒发一条心跳帧PLC收到后清一次超时计数。如果超过设定时间没收到任何数据基本可以断定这条连接已经死了PLC侧就主动关闭这个连接释放资源。很多通信问题表面上是“连不上”底层其实是“死连接占坑”心跳机制能很好地把这种隐患处理掉。4. 上位机客户端模拟与联调测试4.1 用Python快速搭建TCP测试客户端现场做socket从站联调最核心的验证工具就是一个能主动连接PLC的TCP客户端。虽然上位机组态软件通常自带通信测试工具但很多时候不够灵活没法自定义帧内容。我习惯直接用Python写一个几十行的小脚本当测试客户端改起来快还不用安装什么重型软件。Python的socket库是标准库不需要额外安装依赖。在Windows或者Linux上都直接用。下面这段代码是一个最简单的TCP客户端连接PLC的60000端口发送一条测试帧然后接收PLC返回的数据import socket import time PLC_IP 192.168.1.10 PLC_PORT 60000 def send_frame(sock, data: bytes): sock.sendall(data) print(f[TX] {data.hex().upper()}) def recv_frame(sock) - bytes: try: data sock.recv(1024) print(f[RX] {data.hex().upper()}) return data except socket.timeout: print([WARN] recv timeout) return b sock socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(2) sock.connect((PLC_IP, PLC_PORT)) print(f[INFO] connected to {PLC_IP}:{PLC_PORT}) test_frame bytes.fromhex(AA 55 01 02 03 04 05 06) send_frame(sock, test_frame) recv_frame(sock) time.sleep(1) sock.close()这段代码的关键点有三处connect方法建立TCP连接sendall把完整帧发出去recv在设定超时内接收PLC返回的数据。真正做测试时你只需要改test_frame的内容为你协议里的任何一帧即可。跑这个脚本之前先把PLC侧程序下载进去并运行起来确认服务端已经处于监听状态。连不上时脚本会直接抛ConnectionRefusedError或者超时异常这些信息对后面排查问题非常有价值。4.2 抓包验证与常见参数核对写完测试客户端别急着当成调试结束抓包这一步绝对不能省。我用Wireshark抓现场通信包已经成了固定习惯因为很多问题光靠两端打印日志根本看不出来比如TCP重传、半开连接、数据帧字节序错乱这些都得从报文层面才能定位。抓包时先打开Wireshark选择对应网卡设置一个过滤条件把范围缩小。比如PLC地址是192.168.1.10端口是60000就填ip.addr 192.168.1.10 tcp.port 60000这个过滤可以同时看到发给PLC的客户端请求和PLC返回的数据。正常情况下抓包能看到完整的TCP三次握手然后是客户端发数据、PLC回数据的交替帧。抓包过程中有几个关键参数要核对确认SYN包是否发到PLC如果SYN包一直在重传但没有SYN-ACK回应那多半是PLC侧服务没起或者端口没监听确认数据帧内容里各字节的顺序是否与协议一致大小端问题在这里一眼就能看穿确认TCP的ACK、SEQ是否正常如果出现大量重传或者乱序说明网络链路质量有问题查网线、查交换机、查现场干扰。很多工程师对抓包有畏难情绪觉得Wireshark太专业。实际上你不需要理解TCP/IP协议的每一个细节只需要抓住连接建立、数据收发、挥手断开这三段关键流程能看懂标志位和数据内容就已经够解决90%的通信问题了。5. 常见问题与排查技巧实录5.1 端口占用问题bind only one usage的根源与应对做socket通信的人一定见过这么一条报错bind: only one usage of each socket address。这句话的意思是同一个IP加端口只能被一个socket绑定如果你试图再次绑定就会触发这个错误。PLC侧虽然不直接在屏幕上给你弹这个报错但套接字资源管理逻辑是完全一样的。PLC重启后如果旧连接没有被及时清理再重新监听同一个端口时就会失败具体表现是服务端一直启动不了或者上位机怎么都连不上。解决办法有几个层面。第一PLC重启后本身会清空所有运行时状态只要程序中正确释放了之前的socket句柄通常不会有问题。第二断开连接时一定要显式调用关闭函数很多通信死锁都是漏了这一步。第三如果上位机软件是自开发的调试的时候CtrlC中断程序旧socket可能还处于TIME_WAIT状态紧接着重新运行就会报同样的错。这时候不是代码有重大bug而是系统还没释放完端口等几十秒或者把进程彻底杀干净再启动就行了。排查这个问题的命令也很简单。Windows下用netstat命令看一下端口占用情况netstat -ano | findstr 60000如果看到有进程占用再配合tasklist命令查是哪个PID占用的确认是残留进程就直接结束掉。这个问题在开发调试阶段几乎人人都能遇到心态放平就好处理手法熟了一次就会。5.2 客户端连不上PLCIP、网关、防火墙三板斧客户端连不上PLC是最常见的故障但原因往往并不复杂我排查时基本就是冲这三板斧去的。第一板斧查IP连通性。先用ping命令验证上位机和PLC是不是真的能互相访问。能ping通说明链路通ping不通就依次检查网线是否松动、交换机端口状态、两边的网段是否一致。很多现场“连不上”问题最终定位就是上位机网卡配了个172段的地址PLC却是192.168.1.10两边完全不同网段数据包根本出不去。第二板斧查防火墙。Windows系统默认会开启防火墙如果测试客户端是自开发的防火墙可能直接把入站连接或者出站连接拦截掉了。之前一个项目PLC侧服务完全正常但上位机一查询就是超时折腾了一圈发现是Windows防火墙默认拦截了60000端口的入站连接在防火墙“高级设置”里加了一条自定义入站规则允许TCP 60000后才彻底解决。第三板斧查PLC侧实际监听状态。如果IP通、防火墙没拦就看PLC侧程序是不是真的跑到了监听状态。判断方法有一个特别直接在PLC编程软件的在线监视里看监听状态位有没有置位。如果状态位没置位说明socket初始化还没成功去查端口是否被其他功能占用、函数块的参数配置是否正确。这三板斧依次过一遍基本能把95%的连不上问题解决掉剩下5%再考虑抓包定位。5.3 通信中断与重连机制设计通信跑着跑着突然断了或者上位机软件重启后重连不上这类问题也很典型。socket has closed unexpectedly这类的报错我看到的次数特别多它表明通信对端已经关闭了连接但本端还在尝试收发数据。PLC作为服务端遇到这类问题的处理思路要非常明确客户端断开是正常现象服务端程序必须能自动感知断开然后回到等待连接状态。实现上一般有两种方式第一种是当recv返回0或者错误码时认为连接已断开然后主动关闭socket并释放连接标识符第二种是依赖前面提到的心跳超时机制如果长时间收不到客户端的数据帧就主动断开这条僵死连接。上位机侧则必须实现断线重连的逻辑。TCP连接天生不携带永久状态客户端程序要每隔几秒尝试重新连接一次不能一失败就退出或者无限卡在等待里。我见过太多自研上位机重连逻辑写得敷衍断一次就再也连不回来最后背锅的往往是PLC这边的“通信不稳定”。这里分享一个我自己的设计习惯在协议帧里加一个递增的帧序号字段。每发一帧数据序号就加一。接收方通过检查序号是否连续来判断是否丢过帧一旦发现序号跳变就可以触发重连或者请求补发。这个设计成本极低但能让整个通信链路的可靠性提升一个台阶。还有一条很关键的实操经验多客户端连接时每个客户端都对应一个连接资源如果客户端频繁断开重连PLC端的连接资源可能会出现堆积。建议在PLC程序中定期检查当前活动连接数超过配置上限时自动断开最先建立的连接或者所有僵死连接只保留最新的一条。这个“淘汰旧连接”的策略虽然有点暴力但在现场非常见效能避免很多说不清道不明的运行时崩溃。常见现象排查方向快速处理上位机提示连接被拒绝PLC服务未启动 / 端口不符确认监听状态位核对端口号连接后立即断开防火墙拦截 / PLC主动断开检查防火墙规则查看PLC日志偶发通信超时网络链路质量 / 缓冲溢出抓包看重传率优化接收缓冲区上位机重启后始终连不上端口残留占用netstat查端口状态等系统释放数据帧能收到但解析错误大小端 / 协议格式不一致抓包对比字节序统一协议6. 项目经验与避坑总结这个项目做完我最大的感触是socket从站本身不复杂难的是整个过程里的细节管理。协议格式、端口规划、大小端约定、心跳机制、超时处理哪一项没想清楚都会在联调阶段以各种方式给你添堵。我的建议是做这类项目一定先把“通信协议说明书”写在代码前面。不需要很长就两页纸把帧结构、字段定义、示例数据写清楚然后PLC工程师和上位机工程师拿这同一份文档开发两边就不会跑偏。很多项目协调不好说白了就是没有这一份双方都认可的契约。开发顺序上也有讲究。我通常是先用Python脚本模拟上位机把PLC侧逻辑调通再让上位机组的同事按照约定好的协议对接。这样等上位机软件正式出手时通信链路已经被验证过一遍了问题面会小很多。最后再留一个我觉得特别值的小技巧。socket调试时可以先在两台PC之间做一次回环测试一台跑Python TCP服务端脚本一台跑TCP客户端脚本先验证你写的协议帧和收发逻辑本身没有毛病然后再把服务端换成PLC。这样可以帮你把“通信框架问题”和“PLC实现问题”彻底隔离排查起来会极其有效率。这个习惯我一直保留到现在每次做新的通信协议都先走一遍这个流程省下来的时间是实打实的。