ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

C# TCP调试助手实战:从TcpClient到Modbus TCP联调与避坑指南

C# TCP调试助手实战:从TcpClient到Modbus TCP联调与避坑指南 简介C# TCP调试助手完整源码包面向需要进行网络通信调试、接口联调及C#网络编程学习的开发者。该工具基于.NET框架TcpClient/TcpListener实现客户端与服务器双向通信可帮助快速验证服务端逻辑、模拟并发请求、自定义随机数据包有效提升TCP协议调试效率。压缩包共56个文件整体仅3.53MB主要包含C#源码文件、Visual Studio解决方案及工程配置、编译后的exe程序、界面资源文件和少量说明文档结构清晰打开即可运行或二次开发。代码覆盖数据发送与接收、多线程并发、十六进制/字符串格式化显示、详细日志记录、随机数据生成、多连接管理与异常处理等功能适合测试服务器数据校验规则、边界条件及并发处理能力。目前已有603人学习下载无论是C#初学者对照源码理解TCP网络编程还是开发者在实际项目中快速搭建调试工具都极具参考价值。1. 拿到TCP_HELPER.zip你要的其实不是代码是一个趁手的TCP调试工具一个写着TCP_HELPER.zip的压缩包躺在下载目录里时大概率你正被TCP联调折磨要么是PLC的Modbus TCP报文抓不出来要么是WPF上位机连不上服务端要么是设备一断开就再也连不上。这个包里的C# TCP助手代码最终用没用不重要大家真正想要的是一个熟悉场景里提起来就懂的网络调试助手——把TCP客户端、TCP服务端、Hex收发、粘包拆包、中文乱码全部变成界面上看得见、点得到的操作。这篇笔记不猜zip里有哪些具体文件只按一线工程师做同类工具的思路从TcpClient/TcpListener骨架讲到Modbus TCP和Fins TCP联调从跨线程写UI的C#委托讲到端口占用、连接被重置这些现场坑。适合正在写C#上位机的人也适合刚接触TCP/IP协议、想找一条可靠落地路径的初学者。2. 从零搭一个TCP助手TcpClient客户端与TcpListener服务端的骨架2.1 为什么一个调试助手必须同时是客户端和服务端实际联调里你手里的设备身份不固定。PLC通常作为服务端监听端口上位机当客户端主动连过去但很多采集器、传感器反过来它作为客户端朝你的服务端口发起连接。所以一个真正好用的TCP调试助手必须同时具备两种角色既能主动连接别人也能被动等别人连。这也是大多数C#网络助手类工具界面上一定要放客户端模式和服务端模式两个页签的原因。从C#技术选型看实现TCP通信有三种常见路径直接操作Socket类、使用TcpClient/TcpListener封装、再往上走NetworkStream配合异步API。我的建议很明确调试助手这种交互式工具用TcpClient和TcpListener就够了。它们把Socket的绑定、监听、连接状态管理都包了一层你只需要关心连接、读、写三个动作。真等到要写高并发网关或者自己拼TCP协议栈时再退回去用Socket不迟。这个选择不是性能妥协而是可维护性优先——调试工具代码越直白越不容易留暗坑。另外一个容易忽略的点是TCP三次握手的表现。你点击连接按钮从Connect到能收发数据中间是操作系统替你完成了SYN、SYNACK、ACK。但Connect不一定会快速失败对端防火墙丢包、IP不可达、端口黑洞会让Connect一直挂在那里。因此自己做助手时必须给Connect加超时在同步代码里就用BeginConnect加WaitOne。这个习惯要保留到上位机里不然界面点一下连接卡住几十秒交互体验非常糟。2.2 客户端最小实现连接、发送、接收三步走给出一段核心类代码这是我在WinForms里常用的写法。你可以直接把它塞进窗体也可以单独封装成一个TcpClientHelper类。using System; using System.Net.Sockets; using System.Text; using System.Threading; using System.Windows.Forms; public class TcpClientHelper { private TcpClient _client; private NetworkStream _stream; private Thread _recvThread; private bool _running; public bool Connect(string ip, int port, int timeoutMs 10000) { try { _client new TcpClient(); // BeginConnect WaitOne 实现连接超时避免界面卡死 IAsyncResult ar _client.BeginConnect(ip, port, null, null); if (!ar.AsyncWaitHandle.WaitOne(timeoutMs)) throw new TimeoutException(连接超时); _client.EndConnect(ar); _stream _client.GetStream(); _running true; _recvThread new Thread(ReceiveLoop) { IsBackground true }; _recvThread.Start(); return true; } catch (Exception ex) { MessageBox.Show(连接失败: ex.Message); return false; } } public void Send(byte[] data) { if (_stream ! null _client.Connected) { _stream.Write(data, 0, data.Length); _stream.Flush(); } } private void ReceiveLoop() { byte[] buffer new byte[4096]; while (_running) { try { int len _stream.Read(buffer, 0, buffer.Length); if (len 0) break; // 对端主动关闭四次挥手已完成 string hex BitConverter.ToString(buffer, 0, len).Replace(-, ); string text Encoding.UTF8.GetString(buffer, 0, len); BeginInvoke((Action)(() AppendToLog(收到, hex, text))); } catch (Exception ex) { BeginInvoke((Action)(() AppendToLog(异常, ex.Message, ))); break; } } } private void AppendToLog(string tag, string hex, string text) { // 在真实窗体里把 tag/hex/text 追加到文本框 } }这段代码的核心逻辑在于Connect方法用BeginConnect加WaitOne控制超时WaitOne返回false就抛TimeoutException这样不会因为目标IP不可达把界面拖死。ReceiveLoop是一个后台阻塞线程NetworkStream.Read在没有数据时是阻塞的读返回0说明对端发了FIN触发四次挥手的下半段此时要主动退出循环并让上层感知断开。Send方法直接写NetworkStreamTCP是流式协议Write之后最好Flush一下确保数据从应用层缓冲区推下去。参数注意事项timeoutMs默认给10000局域网内建议改3000buffer 4096字节对多数指令型报文够用但如果你要调视频流或大文件需要把缓冲区提到64KB以上并在循环里连续读而不是一次读完。BeginInvoke是跨线程更新UI的标准做法WinForms和WPF形势不同但道理一样接收线程不能直接碰控件。如果你看到线程间操作无效的异常那就是漏了这一步。2.3 服务端最小实现TcpListener监听与多客户端管理助手切到服务端模式时最常见的翻车是第一个客户端连上来能用第二个一进来第一个就断。原因是很多人只在单线程里Accept处理完一个客户端才去Accept下一个。正确做法是Accept循环常驻每一个客户端连接单独分一个工作线程。给一段可直接落地的服务端骨架。using System; using System.Collections.Generic; using System.Net; using System.Net.Sockets; using System.Threading; public class TcpServerHelper { private TcpListener _listener; private Thread _acceptThread; private readonly object _lock new object(); private readonly ListTcpClient _clients new ListTcpClient(); private bool _running; public void Start(int port) { _listener new TcpListener(IPAddress.Any, port); _listener.Start(); _running true; _acceptThread new Thread(AcceptLoop) { IsBackground true }; _acceptThread.Start(); } public void Stop() { _running false; _listener?.Stop(); lock (_lock) { foreach (var c in _clients) c.Close(); _clients.Clear(); } } private void AcceptLoop() { while (_running) { try { TcpClient client _listener.AcceptTcpClient(); lock (_lock) _clients.Add(client); Thread t new Thread(() HandleClient(client)) { IsBackground true }; t.Start(); } catch { // Stop 时正在 Accept 的线程会抛 SocketException这里吞掉 } } } private void HandleClient(TcpClient client) { NetworkStream stream client.GetStream(); byte[] buffer new byte[4096]; try { while (_running client.Connected) { int len stream.Read(buffer, 0, buffer.Length); if (len 0) break; byte[] data new byte[len]; Array.Copy(buffer, data, len); Broadcast(data); // 简单广播实现 } } catch { } finally { lock (_lock) _clients.Remove(client); client.Close(); } } private void Broadcast(byte[] data) { lock (_lock) { foreach (var c in _clients) { if (c.Connected) c.GetStream().Write(data, 0, data.Length); } } } }逻辑说明Start里用IPAddress.Any监听本机所有网卡客户端连接进入AcceptLoop每Accept一个就加入_List集合并开新线程。HandleClient内部同样是一个阻塞Read读到0字节说明客户端断开最后在finally里移除并关闭。Broadcast方法遍历所有连接把数据转发给每一个客户端这样服务端就具备了简单的广播能力。参数说明IPAddress.Any监听所有网卡如果只想本机访问改成IPAddress.Loopback。端口小于1024在Windows上一般不拦但为了避嫌最好用高位端口。广播里直接Write并没有做异常保护某个客户端断开后再Write很容易抛出IOException当你点界面按钮发送时尤其明显。这个问题我会在避坑章展开先记住发送必须加锁接收必须分离线程这条原则。调试助手场景下这个服务端已经可以顶上半条上位机了。3. 数据从界面到网线Hex收发、粘包拆包与中文编码3.1 TCP调试里最常用的Hex与字符串互转TCP调试助手的界面上永远少不了一个HEX显示和HEX发送勾选框。原因很简单Modbus TCP报文、Fins TCP命令、大部分设备私有协议看到的都是十六进制字节。你不可能让用户去记字符编码所以工具必须在字符串和字节数组之间自由切换。给我常用的两个转换方法。// 字符串转Hex输入示例 01 03 00 00 00 0A C5 CD public static byte[] HexStringToBytes(string hex) { hex hex.Replace( , ).Replace(-, ).Replace(\r, ).Replace(\n, ); if (string.IsNullOrEmpty(hex)) return new byte[0]; if (hex.Length % 2 ! 0) throw new FormatException(Hex 字符串长度必须是偶数); byte[] bytes new byte[hex.Length / 2]; for (int i 0; i bytes.Length; i) bytes[i] Convert.ToByte(hex.Substring(i * 2, 2), 16); return bytes; } // 字节数组转Hex显示串加空格便于阅读 public static string BytesToHex(byte[] data, int offset, int count) { StringBuilder sb new StringBuilder(); for (int i 0; i count; i) sb.Append(data[offset i].ToString(X2)).Append( ); return sb.ToString().TrimEnd(); }逻辑说明HexStringToBytes先把输入里的空格、减号、换行全部清掉因为用户可能从设备手册或抓包工具里粘贴带格式的文本。然后用Substring按两位一组取字节Convert.ToByte(string, 16)负责把0A这种字符串转成10。BytesToHex是逆操作X2格式保证单字节一定显示两位比如0x0A显示0A而不是A。参数说明如果HEX发送框里用户输了GGConvert.ToByte会抛FormatException所以在UI层最好先做Try-Catch并且焦点在Send按钮上做合法性校验。另一个容易被忽略的地方是字符串模式下的编码设置对Hex模式无效HEX模式下发送框里的内容必须是一串合法的十六进制文本。混用是新手最常踩的坑。3.2 粘包和半包不是玄学简单可行的拆包约定TCP是字节流没有消息边界。你连发两条指令对端可能一次收到也可能一条指令被拆成两半。很多刚接触TCP/IP协议的人第一次遇到就说这是粘包然后四处找配置其实解法是应用层自己定义帧格式。绝大多数工业协议都有明确的帧头、长度、校验调试助手要做的不是防御式处理而是按协议把收到的流切成一帧一帧。给一个通用的增量解析器。public class FrameParser { private readonly Listbyte _buffer new Listbyte(); private readonly byte[] _head { 0xAA, 0x55 }; private int _frameLength; // 每次收到网络数据就往里喂内部缓存半包 public Listbyte[] Feed(byte[] data) { Listbyte[] frames new Listbyte[](); _buffer.AddRange(data); while (_buffer.Count 4) { int pos -1; for (int i 0; i _buffer.Count - _head.Length; i) { if (_buffer[i] _head[0] _buffer[i 1] _head[1]) { pos i; break; } } if (pos 0) { _buffer.Clear(); break; } // 丢掉帧头前面的垃圾字节 if (pos 0) _buffer.RemoveRange(0, pos); if (_buffer.Count 4) break; // 连完整头都没收齐 _frameLength (_buffer[2] 8) | _buffer[3]; int total 4 _frameLength; // 帧头 2字节长度 数据 if (_buffer.Count total) break; // 半包继续等 byte[] frame _buffer.GetRange(0, total).ToArray(); frames.Add(frame); _buffer.RemoveRange(0, total); } return frames; } }逻辑说明Feed方法的输入是ReceiveLoop里读到的原始字节流它把数据追加到内部缓存然后循环做三件事找帧头0xAA 0x55一旦找到就把帧头前面的垃圾字节清掉从帧头后两字节读取长度判定数据部分是否凑齐。凑齐一帧就输出并从缓存移除没凑齐就break出去等待下一次网络包。这样粘包被自动切分半包被缓存拼接整个过程对调用方完全透明。参数说明帧头0xAA55是常见的协议头你可以按实际设备改成0x68、0x66或者更多字节的头。长度字段用了大端序高字节在前如果你的协议是小端序要换成(_buffer[3] 8) | _buffer[2]。还有一个细节很多协议的长度字段是包含长度字节自身那total应该写成 4 _frameLength - 2否则每帧会多吃两个字节。这些问题只有对照真实设备报文才能发现调试助手把这套解析做成可配置选项是很实用的功能。3.3 中文乱码的根源发送方编码和接收方解码必须一致TCP调试里出现锟斤拷这类乱码十个里有九个是编码不一致。设备固件按GB2312发中文你的助手用UTF-8解码必乱反过来也一样。C#里Encoding类可以明确控制编码关键是别写成硬编码。给一个通用编码处理。private static readonly Encoding Utf8 Encoding.UTF8; private static readonly Encoding Gb2312 Encoding.GetEncoding(GB2312); public static byte[] EncodeText(string text, string encodingName) { return encodingName GB2312 ? Gb2312.GetBytes(text) : Utf8.GetBytes(text); } public static string DecodeText(byte[] data, string encodingName) { return encodingName GB2312 ? Gb2312.GetString(data) : Utf8.GetString(data); }逻辑说明EncodeText负责把界面输入的中文变成字节流再发送DecodeText负责把接收到的字节流变回可读文本。边框上的编码下拉框决定了最终走哪条分支。难点不在代码本身而在.NET Core / .NET 5之后GB2312不是默认可用编码。你需要在程序启动时注册代码页Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);这一行如果不写Encoding.GetEncoding(GB2312)会直接抛NotSupportedException很多人在本机运行正常、发布到新环境就炸就是漏了注册。参数说明实际项目里设备协议很少用中文发送但日志和人工描述常用中文。我一般建议助手接收区同时显示HEX和UTF-8解码文本把HEX放前头。遇到乱码先看HEX是不是预期字节再切换编码下拉框。尤其注意HEX显示和文本显示要分开别混在一个文本框里否则你根本没法判断设备回的是00 01还是文本字符。4. 从TCP助手到上位机Modbus TCP、Fins TCP与WPF联调实战4.1 用调试助手抓取和回放一条Modbus TCP报文Modbus TCP是工业现场最常用的应用层协议之一报文结构固定事务标识符2字节、协议标识符2字节、长度2字节、单元标识符1字节、功能码1字节、数据区若干字节。例如读保持寄存器00 01 00 00 00 06 01 03 00 00 00 64这12个字节分解开事务ID1协议ID0(Modbus固定)长度6单元号1功能码0x03起始地址0读100个字。在调试助手里你不需要手打每个字节可以把常用请求存成指令模板。下拉框一点就填入发送框这是很多C#上位机工具里很受欢迎的功能。private Dictionarystring, string CreateModbusTemplates() { return new Dictionarystring, string { { 读线圈 01区, 01 01 00 00 00 01 }, { 读保持寄存器 03区, 01 03 00 00 00 0A }, { 写单个线圈, 01 05 00 00 FF 00 }, { Fins TCP 读DM区, 46 49 4E 53 00 00 00 10 00 00 00 00 00 00 00 01 01 01 00 82 00 00 00 01 } }; }逻辑说明模板本质上是一串Hex字符串发送时调用第3章的HexStringToBytes转成字节流。Modbus TCP不会有CRC因为可靠性由TCP层保障所以模板里不需要算校验。但Fins TCP的模板要注意命令码和内存区代码不能写错它是欧姆龙PLC常见的以太网协议。用这些模板你就能快速验证上位机发送逻辑、PL响应逻辑、网关转发逻辑。参数说明Modbus TCP一次请求最长多个寄存器取决于PLC配置常见的是125个字。如果你的模板抄自网上最后两位长度可能对不上你要读的数量设备会回异常码0x02或0x03。判断方法是把助手接收区的HEX和协议手册逐字段对照不要迷信模板。再提醒一点Modbus功能码03的回应事务ID必须和请求一致不一致说明链路中网关做了转换要留意。4.2 模拟服务端让助手当PLC验证客户端重连与超时很多时候你要验证自己的C#客户端是否扛得住PLC重启、网线松动但这不能真把现场设备断电。把调试助手切成服务端模式立刻就能模拟出一个本机PLC。关键是要开启自动回显功能服务端收到什么就原样回什么这样客户端发完请求就能立刻收到响应验证链路是通的。private void OnServerDataReceived(TcpClient client, byte[] data) { if (checkBoxEcho.Checked) { _serverHelper.SendTo(client, data); } // 更高级的模拟如果收到特定帧头返回预设错误码 if (data[0] 0xAA data[1] 0x55) { byte[] fakeResponse { 0xAA, 0x55, 0x00, 0x02, 0x10, 0x00 }; _serverHelper.SendTo(client, fakeResponse); } }逻辑说明这段代码挂在服务端的数据接收事件里。勾选回显后数据原样返回。第二个分支是更接近真实设备的行为收到某个特定帧头立即回一条固定响应。用这种方式你可以测试客户端对错误码的处理逻辑——比如设备返回异常码时上位机是弹出提示还是自动重发。参数说明信号测试最重要的是模拟连接断开。正确步骤是开服务端、让客户端连上、发送一条、再关闭服务端、观察客户端是否在预期时间内抛出连接异常并进入重连。如果你的客户端用了WaitOne超时这里很快就能验证超时参数设得合不合理。我一般把重连间隔做成指数退避1秒、2秒、4秒、8秒封顶避免高频重连把服务端打崩。4.3 把助手代码收编进WPF上位机跨线程更新UI的委托写法调试助手最终往往是过渡真实需求是把通信代码集成进WPF上位机。许多人在这一步踩同一个坑接收线程直接操作TextBox然后抛调用线程无法访问此对象因为另一个线程拥有该对象。这是因为WPF的UI控件只能在UI线程操作而TcpClient接收是后台线程。C#里解决这个问题靠的是委托和Dispatcher。给一个标准写法。private void AppendLog(string msg) { if (Dispatcher.CheckAccess()) { textBoxLog.AppendText(msg Environment.NewLine); } else { // 异步回UI线程保证接收线程不被UI卡住 Dispatcher.BeginInvoke(new Action(() textBoxLog.AppendText(msg Environment.NewLine))); } } // 接收线程里调用 AppendLog(收到: hexData);逻辑说明Dispatcher是WPF的UI线程调度对象。CheckAccess判断当前调用线程是否持有UI访问权有就直接写没有就通过BeginInvoke排队到UI线程执行。注意这里用BeginInvoke而不是InvokeBeginInvoke是异步的不会让接收线程等待UI刷新多客户端场景下尤其重要。参数说明高频接收时BeginInvoke的委托会在UI消息队列里排队看似丢消息实际只是UI来不及刷新。如果你发现界面跟不上数据速度不要改成Invoke应该合并日志——比如每100毫秒取一次缓冲区内容批量显示。WinForms里的对应物是InvokeRequired和Invoke思路完全一样。还有一个隐藏问题窗口正在关闭时后台线程还在调BeginInvoke会抛TaskCanceled或ObjectDisposed异常关闭窗体前一定要把_running置false再等待线程退出。5. 避坑TCP调试最容易翻车的五个现场问题5.1 现象绑定端口报错only one usage of each socket address调试助手切到服务端模式点开始监听直接弹出异常信息是only one usage of each socket address常见于你之前启动过监听但没有关干净或端口被另一个程序占用。这个问题在工业现场尤其容易遇到现场机装了MES客户端、PLC编程软件、数据库连接池某个进程悄悄占用了你默认监听的端口。原因Windows下同一个IP和端口只能被一个Socket绑定。之前程序崩溃导致四字节队列里还有旧连接处于TIME_WAIT状态也会阻止重启后的监听。另一个可能是你的调试助手自己跑了两个实例两个实例同时绑定同一个端口。解决先在命令行里查端口占用netstat -ano | findstr 5300 tasklist | findstr PID找到占用进程的PID后在任务管理器里结束它或者改用更高位的端口比如14150。代码层面要处理两件事监听启动要套Try-Catch捕获SocketException后友善提示用户换端口窗口关闭时务必调用Stop方法把Listener和所有TcpClient全部Close。我的习惯是测试时固定用14150避开那些被系统服务抢走的常见端口。5.2 现象发送数据后马上收到远程主机强迫关闭了一个连接现象很经典上位机连上服务端第一次发送成功第二次发送抛IOException内层消息是无法将数据写入传输连接: 远程主机强迫关闭了一个连接。原因对端服务端进程崩溃、网线断开、或者服务端收到你的数据后主动RST。但为什么第一次发送没报错这是因为TCP发送数据先进了内核缓冲区发送并不会立即感知对端已关闭。第二次发送时对端的RST已经回来栈立刻抛出异常。解决不能在GetStream、Connect成功后就认为连接一直可用。每次发送前要检查Connected属性但Connected只反映上一次通信状态也会骗人。更可靠的办法是业务层定期发送心跳一旦Send抛IOException立即关闭当前TcpClient并进入带退避的重连循环。注意重连间隔不要固定1秒、3秒、10秒递增否则对端恢复时会被你的高频重连反复打断。5.3 现象服务端能连上但一发数据就被对端断开现象调试助手作为服务端时客户端连上后你可以从客户端往服务端发数据但服务端界面手动点发送客户端立刻掉线。原因典型的多线程写冲突。服务端的接收线程在Read界面按钮线程也在往同一个NetworkStream里Write。Windows Sockets允许一个读线程和一个写线程并发但不允许两个线程同时写。两个Write同时触发内核会抛异常连接直接关闭。解决给所有发送统一加锁保证同一时刻只有一个写入者。private readonly object _sendLock new object(); public void Send(byte[] data) { lock (_sendLock) { if (_stream ! null _client.Connected) { _stream.Write(data, 0, data.Length); _stream.Flush(); } } }对照检查自己写的代码看看是否在接收后自动回显和界面按钮发送两条逻辑线上都用了同一个包装好的方法。如果它们各写各的那翻车只是时间问题。这个坑在调试助手阶段发现成本最低等进了上位机再抓就要翻日志了。5.4 现象界面卡死点发送按钮转圈现象点击发送按钮后整个窗口无响应鼠标一直转圈最后只能任务管理器杀进程。原因在UI线程里做了阻塞网络操作。常见的是在按钮事件里直接调用TcpClient.Connect而没有用超时或者直接把NetworkStream.Read写在按钮事件里等数据回来。当对端网络不通、防火墙丢包时Connect或Read会一直阻塞UI线程被占死整个窗口就冻结了。解决所有网络收发都必须放到后台线程或使用异步方法。我的代码里Connect用BeginConnect加WaitOne接收用后台ReceiveLoop按钮事件只做两件事把发送数据写入流把操作记录追加到日志。如果你真的需要在按钮事件里等一包响应至少要用ManualResetEvent配合超时等待绝不能在UI线程里调用流式Read。界面卡死还有一个隐藏来源MessageBox.Show在接收线程里弹出也会让窗口失去响应。正确做法是把错误信息用BeginInvoke送回到UI线程再在UI线程里弹框。5.5 现象接收区中文变成锟斤拷现象服务端发来中文助手里显示锟斤拷或烫烫烫完全不可读。原因锟斤拷是UTF-8编码字节被GB2312解码后的经典乱码形态。比如中文这两个字的UTF-8字节是E4 B8 AD E6 96 87用GB2312解码就出现锟斤拷。烫烫烫一般来自未初始化的缓冲区如果收到的是垃圾字节基本是接收buffer没清零或者长度算错了。解决先用Hex模式看一眼原始字节。如果显示E4B8AD E69687那就是标准的UTF-8如果显示B1BD CEC4这是GB2312的中和文。确认编码后在DecodeText方法里选择对应编码。注意如果你在接收代码里用了Encoding.Default在不同系统区域设置下结果可能完全不同。我一般建议助手界面强制提供UTF-8和GB2312两种常见选项并且把当前编码名显示在接收区标题栏上减少误判。6. 进阶让TCP调试助手学会自己干活——日志回放与自动轮询工具用顺了之后我开始不满足于手点发送和收包。调试TCP助手真正有价值的三件事是完整日志、定时轮询、一键回放。第一完整日志。不要把收发数据只打在文本框里同时追加写入文件。每一条都要带毫秒时间戳例如File.AppendAllText(tcp_log.txt, string.Format({0:HH:mm:ss.fff} TX {1}{2}, DateTime.Now, hex, Environment.NewLine));毫秒级时间戳能帮你还原现场数据是卡在应用层还是网络层是发送延迟还是接收慢。很多丢包最后查到是设备回复时间不稳定没有时间戳根本看不出来。第二定时轮询。调试PLC或仪表时经常要每隔500毫秒读一次寄存器。在助手界面上加一个循环发送勾选框和一个间隔文本框用Timer按间隔发送。但发送前要检查连接状态并且勾选循环发送后禁止切换发送框内容防止用户手滑改了指令导致下一轮发错。第三一键回放。把日志文件里所有TX行提取出来按原时间戳间隔重新发送就能复现当时的问题。不用写复杂脚本PowerShell遍历日志或者直接在助手里加一个回放按钮读取日志逐条发送。设备供应商说我这边没问题时把回放结果甩过去是最有力的沟通方式。验证整套工具最可靠的办法是自环回测试同一台机器开服务端和客户端让客户端发送带自增序号的数据服务端原样返回校验序号连续性。连接断开、重连、粘包、乱码全部都能在五分钟内暴露出来。我做这类C#网络助手时最后一步永远是自环回跑一遍再交给现场。最后说一个血泪教训我早期把调试服务的端口设成5000结果在客户现场和他们的调度系统撞了个正着整个产线频繁掉线。排查到最后发现是两个进程争同一个端口。现在我把所有测试工具的默认端口统一改成14150并把这个决定写在项目文件第一行注释里。希望帮到你。本文还有配套的精品资源点击获取
返回列表