☰
手写C#网络调试助手:Socket编程与Hex收发实战
2026/10/14 4:10:43 网站建设 项目流程

简介:这是一款面向C#开发者的网络调试辅助工具,主要覆盖串口通讯、Socket、TCP/IP及UDP等常见协议调试场景,很适合需要排查网络通信故障、验证服务端交互逻辑或学习报文传输细节的初中级开发者。整个压缩包共含13个文件,总体积约1.92MB,包含两个可执行程序、6个核心DLL、配置清单、调试符号以及一个SQL脚本。其中SQL脚本可将调试结果直接写入MySQL数据库,方便对通信过程进行持久化记录与分析;可执行文件及配套DLL则提供了完整的图形界面与底层调用支持,安装配置后即可上手测试。目前已有1259人学习下载。借助PDB文件与界面库(如TX.Framework.WindowUI)可以进一步理解工具内部结构;需要二次开发或获取源码的读者,可按作者预留的联系通道进一步交流,以扩展自定义协议或对接业务系统。

1. 为什么还自己写一个C#网络调试助手:现成工具不够用的时候

手头在调一个某设备厂商的 Modbus TCP 网关,连着好几台终端,现成的网络调试工具只能建立一个 TCP 连接,想同时盯三路会话就必须开三个窗口,而其中一个窗口的 Hex 显示还总把高低字节弄反。折腾了半个下午,我决定自己用 C# 写一个网络调试助手——Socket 层的东西不神秘,收发字节、Hex 显示、定时发送、多连接管理,全都可以自己做主。这篇就沿着「从零搭骨架 → 处理收发格式 → 踩坑 → 上进阶功能」的顺序,把我的实现方案和参数选择讲清楚。适合两类人:想深度调协议但现有工具不够灵活的嵌入式工程师,以及想通过一个小型网络工具把 C# Socket 编程彻底吃透的入门开发者。

2. 从 Socket 开始:搭建 TCP 客户端与服务端的核心骨架

2.1 同步还是异步:先想清楚接收循环怎么写

网络调试助手最核心的诉求就一句话:把对端发来的字节流原样显示出来,同时允许你随时把字节流发过去。这里第一个分岔路口是采用同步 Socket 还是异步 Socket。很多教程一上来就推 SocketAsyncEventArgs 做高性能异步,但对调试助手这种交互型工具,在异步模型里处理「收到数据后立刻刷新 UI」这件事反而更绕:回调线程和 UI 线程之间的切换写多了,代码很快就看不懂了。

我一般会选同步 Socket + 后台线程,原因是调试助手的并发量极低,通常就几个连接,单连接吞吐也远达不到要压测网卡的程度。同步模型里,接收逻辑就是一个直观的 while 循环,收没收到数据、断没断开,都非常好判断,Log 也容易打。真正需要异步的场景是服务端要同时管理几百上千个连接,或者要做大流量压力测试——这两种情况调试助手都不太会碰到。

但同步接收有一个必须处理的细节:Socket 的 Receive 方法是阻塞的,一旦没有数据到达,它会一直停在那一行。所以接收循环必须放在独立线程里,绝不能放在 UI 线程上。下面是一段我常用的接收线程骨架:

private void ReceivingLoop(object state) { byte[] buffer = new byte[4096]; Socket socket = (Socket)state; while (!_isClosing) { try { // 阻塞式接收,有数据才返回,否则一直等待 int received = socket.Receive(buffer); if (received == 0) { // 对端正常关闭,Receive 返回 0 RaiseLog("连接已关闭 (对端主动断开)"); break; } // 把收到的字节交给上层处理,例如转成 Hex 或 ASCII 显示 RaiseDataReceived(buffer, received); } catch (SocketException ex) { // 连接被强制关闭时,会抛 SocketException,错误码 10054 表示连接被重置 if (ex.SocketErrorCode == SocketError.ConnectionReset) { RaiseLog("连接被对端重置"); } break; } catch (ObjectDisposedException) { // 主动关闭 Socket 后,Receive 会抛这个异常,属于正常退出路径 break; } } }

这里的核心参数是缓冲区大小 4096,这个值不是随便定的。对绝大多数工业协议、私有协议来说,一帧数据不会超过 4KB;如果你调的协议单帧可能更大,比如文件传输或者多媒体流,建议改成 8192 或者 16384,代价只是多占一点内存。注意我用了RaiseDataReceived把数据抛出去,而不是在接收线程里直接改 UI,这是为了后面接入各种显示模式时不需要改动接收逻辑本体。

2.2 一个能跑通的 TCP 客户端:连接、发送与接收的代码骨架

客户端部分要做三件事:连接、发送、接收。我用一个封装类把这三件事合在一起,构造时传入目标 IP 和端口。连接必须设超时,如果不设,连一个不可达地址时界面会僵住几秒到几十秒,这个体验在调试工具里非常糟糕。

public class TcpClientSession { private Socket _socket; private bool _closing; public void Connect(string ip, int port, int timeoutMs = 3000) { _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); // 异步连接 + 等待信号量,用信号量实现超时控制 IAsyncResult result = _socket.BeginConnect(ip, port, null, null); bool ok = result.AsyncWaitHandle.WaitOne(timeoutMs, true); if (!ok) { _socket.Close(); throw new TimeoutException("连接超时,请检查 IP、端口和目标设备是否开机"); } _socket.EndConnect(result); // 连接成功后启动接收线程 _closing = false; var receiver = new Thread(ReceivingLoop) { IsBackground = true }; receiver.Start(_socket); } public int Send(byte[] data) { // 发送时先加锁,避免定时发送和手动发送在同一个 socket 上并发写 lock (_sendLock) { return _socket.Send(data); } } }

关键点有两个。第一是BeginConnect配AsyncWaitHandle.WaitOne的做法:它本质上是异步连接但用信号量等结果,比Socket.Connect直连多了一个可配置的超时时间,我习惯设 3000 毫秒,连不上的时候 3 秒内就能给出反馈,而不是让界面傻等十几秒。第二是发送加锁,这一点很容易被忽略。调试助手的发送来源不止一个:可能是界面上你手动点了一下发送,可能是定时发送器到点触发了一条,还可能是文件发送在逐条推。这三个来源如果同时调 Socket.Send,会造成数据交叉,对端会收到错乱字节。加一个_sendLock是最廉价的防错方案。

2.3 服务端监听与多客户端接入:Accept 循环与客户端管理

服务端模式的本质是反过来的:先监听,等别人来连。这里我用TcpListener做监听,Accept 循环同样放在后台线程里,每接进来一个客户端就开一个独立的接收线程。每个客户端需要一个会话 ID,以便在界面上区分数据来自谁。

public class TcpServer { private TcpListener _listener; private readonly Dictionary<int, Socket> _clientSockets = new(); public void Start(int port) { _listener = new TcpListener(IPAddress.Any, port); _listener.Start(10); // 参数 10 表示监听队列长度,超过后新连接会被内核丢弃 RaiseLog($"监听已启动,端口 {port}"); var acceptThread = new Thread(AcceptLoop) { IsBackground = true }; acceptThread.Start(); } private void AcceptLoop() { while (!_closing) { try { Socket client = _listener.AcceptSocket(); int sessionId = NextSessionId(); _clientSockets[sessionId] = client; RaiseLog($"客户端 {sessionId} 接入,远端地址 {client.RemoteEndPoint}"); // 每个客户端一条独立接收线程 var receiver = new Thread(ReceivingLoop) { IsBackground = true }; receiver.Start(new ClientContext { SessionId = sessionId, Socket = client }); } catch (SocketException ex) { if (!_closing) RaiseLog($"Accept 异常: {ex.SocketErrorCode}"); break; } } } }

监听队列长度参数我习惯设 10,这是大多数系统默认值附近的选择。如果你要接的客户端很多,可以设大一点,但要注意这个队列的作用只是暂存还没被 Accept 的连接请求,不是并发连接上限。IPAddress.Any表示监听本机所有网卡地址,这对调试助手是合理的——你不知道目标设备会从网口还是无线网卡连过来。还有一点,客户端 Socket 的ReceiveTimeout我没刻意设,因为调私有协议时,有些设备要隔很久才发一次心跳,设了超时反而会误杀连接。

3. 数据收发与十六进制处理:把调试助手做成能干活的样子

3.1 ASCII 与 Hex 两种显示模式:编码和字节流的转换逻辑

网络调试助手之所以叫「调试」助手,核心在于能看到原始字节。很多设备返回的是二进制帧,直接在文本框里按 UTF-8 显示就是一屏乱码;反过来,你下发的指令也可能是非打印字符,必须在 Hex 模式下拼字节。所以一个成熟的调试助手,收发区至少要同时支持 ASCII 和 Hex 两种显示,并且 Hex 模式下发送时要把「AA BB CC」这样的字符串解析回字节数组。

public static string BytesToHex(byte[] buffer, int count) { // 每字节转两位大写十六进制,字节之间用空格分隔,方便肉眼按帧阅读 var sb = new StringBuilder(count * 3); for (int i = 0; i < count; i++) { if (i > 0) sb.Append(' '); sb.Append(buffer[i].ToString("X2")); } return sb.ToString(); } public static byte[] HexToBytes(string hexString) { // 先去掉空格和常见分隔符,兼容 "AA-BB-CC" 或 "AA,BB,CC" 这类写法 string cleaned = hexString.Replace(" ", "").Replace("-", "").Replace(",", ""); if (cleaned.Length % 2 != 0) throw new FormatException("Hex 字符串长度必须为偶数,请检查是否漏了字节"); byte[] bytes = new byte[cleaned.Length / 2]; for (int i = 0; i < bytes.Length; i++) { bytes[i] = Convert.ToByte(cleaned.Substring(i * 2, 2), 16); } return bytes; }

这里最容易被新手忽略的坑是Convert.ToByte遇到非法字符时会抛异常,所以在界面层调用HexToBytes之前要先做一次校验,或者直接捕获FormatException弹提示。我实际项目中还遇到过一种情况:某些设备返回的 Hex 数据里带有类似0x前缀,比如0xAA 0xBB,这个函数就不兼容了。处理方式是在清理阶段把0X和0x前缀也替换掉,但要注意替换顺序别把正常的字符干掉。ToString("X2")里的 X 是大写十六进制,2 表示不足两位补零,这两个参数不要随便改,小写 x 会输出aa,虽然不影响数值,但和协议文档对照时会增加排查成本。

3.2 定时发送与文件发送:用状态机处理发送队列

定时发送是调试助手的灵魂功能之一。调设备时经常要周期性发一条查询指令,比如每 100 毫秒读一次传感器值。最粗暴的写法是开一个 Timer,到点就把文本框里的内容发出去——但这里有两个问题:一是 UI 文本框正被修改时读内容会读到半截字符串,二是定时触发和手动触发在同一个 Socket 上并发 Send 会交叉。

我的做法是引入一个发送队列,所有发送请求都进队列,由单独一个发送线程从队列里取任务执行。定时器到点后只是把「要发送的字节」和「发送时间」塞进队列,真正发数据的动作永远发生在同一个线程上。这样既解决了冲突,也为以后做「按行发送」这类批量操作铺好了路。

public class SendQueue { private readonly ConcurrentQueue<byte[]> _queue = new(); private readonly AutoResetEvent _signal = new(false); public void Enqueue(byte[] data) { _queue.Enqueue(data); _signal.Set(); // 唤醒发送线程 } public void SendLoop() { while (!_closing) { _signal.WaitOne(); // 队列为空时阻塞,避免空转占满 CPU while (_queue.TryDequeue(out byte[] data)) { _socket.Send(data); RaiseLog($"已发送 {data.Length} 字节: {BytesToHex(data, data.Length)}"); } } } }

AutoResetEvent在这里起的是「有活就干,没活睡觉」的作用。如果不用它,要么发送线程一直在死循环空转占 CPU,要么得上Thread.Sleep,但 Sleep 会让发送的节拍变得不准确。定时发送的间隔参数我一般只往下限约束:最小建议 50 毫秒。再小的话,Windows 的系统定时器精度也未必支持,而且多数设备的响应时间根本来不及。如果在定时发送的同时还要统计「已发送多少条」,可以直接在Enqueue或者发送成功处做累加,不要用计数器跨线程裸加,要加就去加Interlocked.Increment,原因很简单,跨线程的count++不是原子操作。

3.3 UDP 的广播与组播:ReceiveFrom 和 Bind 的边界

UDP 部分比 TCP 容易绕晕。TCP 是面向连接的,先 Connect 再收发;UDP 不用 Connect,它的核心方法是SendTo和ReceiveFrom,而 ReceiveFrom 在接收之前要知道「数据到哪个本地端口上找」。很多新手卡在这一步:建了一个 UdpClient,然后直接Receive,收不到任何数据,因为根本没有Bind到固定端口。

UdpClient udp = new UdpClient(); udp.EnableBroadcast = true; // 允许发送广播包 udp.Client.Bind(new IPEndPoint(IPAddress.Any, 8899)); // 绑定本地端口,不绑定则每次接收端口都可能变 // 接收循环里用 Receive 即可,数据来源用 RemoteEndPoint 标识 IPEndPoint remote = new IPEndPoint(IPAddress.Any, 0); byte[] data = udp.Receive(ref remote); RaiseLog($"收到来自 {remote} 的 {data.Length} 字节");

EnableBroadcast = true是为了给SendTo发送广播地址(比如 255.255.255.255)授权,不打开这个开关,向广播地址发送会直接抛SocketException。这里有个 Windows 平台的额外行为值得一提:向子网广播地址发送时,本机自己也会收到一份,因为网卡会把广播包镜像回本机协议栈。所以调试时会看到「我明明只发了一条,为什么接收区显示收到两条」,这不是 bug,是 UDP 广播的特性。要区分是不是自己发的那条,可以在发送前给数据加一个随机前缀字节,或者干脆在发送处理函数里把「本次发送的副本」也输出到接收区,并在日志里标记来源是本地发送。组播场景更绕一点,需要JoinMulticastGroup加入组地址,而且同一个组播地址在不同的网卡上需要分别加入,这个等真遇到再展开,初期调试助手用单播加广播就覆盖了绝大多数场景。

4. 避坑指南:写 C# 网络调试助手最容易踩的 5 个坑

4.1 界面卡死:UI 线程被 Socket 接收循环堵住

现象:连接一建立,界面立刻白屏,标题栏显示「未响应」,几秒后整个窗体动不了。原因:接收循环跑在了 UI 线程上。Socket.Receive是阻塞方法,没数据时就一直占着线程,UI 根本没法处理鼠标消息。解决:接收循环必须开独立后台线程,跨线程访问 UI 控件用BeginInvoke或Task.Run回调,绝不在接收线程里直接写文本框。这是我第一次写这工具时翻的最大的车,一度以为是 Socket 的问题,后来打印线程 ID 才发现全是自己线程模型搞错了。

4.2 粘包与半包:调试助手要不要自己拆包

现象:设备按协议一帧发 32 字节,但接收区经常一次显示 64 字节甚至 96 字节,而且帧和帧之间的边界对不上。原因:TCP 是字节流协议,它不保证每次Receive拿到的正好是发送方调用一次Send的数据。收发双方之间有缓冲,内核会把多次 Send 的数据合并成一次 Recv(粘包),也可能把一次 Send 的数据拆成多次 Recv(半包)。解决:调试助手要看清卡在这个问题上的人分两类。第一类是普通调协议,这类人不需要拆包——把收到的字节原样显示出来,自己肉眼对照协议文档找帧边界即可。第二类是想让助手自动解析出每一帧,这时候就必须做拆包:根据帧头、帧长字段、校验位来切分。我的建议是普通调试先不拆,遇到实在需要时,在数据入口处增加一个按「帧头 + 长度字段」拆包的解析器,但拆包逻辑不要写死在接收循环里,而是做成可插拔的过滤器,否则换个协议就得改一遍核心代码。

4.3 端口占用与 TIME_WAIT:重启服务端老报地址已在使用

现象:服务端程序关闭后立刻重启,抛出SocketException: 地址已在使用中。原因:TCP 连接关闭后,对端或本端会进入 TIME_WAIT 状态,持续约 2 个 MSL(默认约 1 到 4 分钟),这个状态下的端口不会立刻释放。解决:监听 Socket 上设置ReuseAddress选项:

listener.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);

但要注意,ReuseAddress只能让新的监听尽快绑上同一个端口。如果你既要监听又要连出,同一个端口被两个 socket 使用,极端情况下可能出现收包错乱,调试助手这种低频场景基本不会触发。另一个实用技巧:开发调试时如果端口确实卡住了,直接换一个高位端口启动,比等 TIME_WAIT 过期快得多,这也是我实际项目里最常用的逃避式解法。

4.4 中文乱码:Encoding.Default 不是万能的

现象:接收区的 ASCII 模式显示中文全是问号或乱码,发送中文对端也解不对。原因:TCP 只传字节,不传编码。设备端可能是 GB2312、GBK、UTF-8、UTF-16 这几种编码中的任意一种,而Encoding.Default在 Windows 上取的是系统区域设置对应的代码页,换台机器结果可能就不一样。解决:把编码选择做成下拉框,让用户手动指定,不要默认取系统值。通常选 UTF-8 或 GB2312 能覆盖九成场景。顺带提一个血泪经验:Hex 模式下发送中文是发不了实际可读文字的,必须先自己把中文字符串转成对应编码的字节(比如Encoding.UTF8.GetBytes("你好")),然后把这段字节的 Hex 值拼进发送框。如果直接拿文本框里的中文转 ToString("X2"),那转的是这个字符的 Unicode 码点,而不是字节序列,发出去对端根本认不得。

4.5 Socket 关闭时抛 ObjectDisposedException:先关闭再释放

现象:点「断开」按钮时程序崩了,异常信息指向Socket.Receive。原因:断开时你可能先调了socket.Shutdown,再调socket.Close,但接收线程还停在 Receive 上。Receive 在 Socket 被关闭的瞬间会抛ObjectDisposedException或者SocketException,如果没有在接收循环里捕获,这个异常直接穿过线程边界,把整个进程带崩。解决:接收循环的 catch 里显式捕获ObjectDisposedException并把它当正常退出路径处理;断开操作按规范化顺序来:先置_closing = true,再Shutdown(SocketShutdown.Both),最后Close()。注意Shutdown之后Receive会立刻返回 0,走正常关闭分支,这是最干净的顺序。

5. 进阶能力:自动重连、多连接管理与会话日志

5.1 自动重连:心跳超时与指数退避

调试现场经常遇到设备断电重启、网线松动的情况。如果 TCP 连接断了,调试助手不会自动恢复,你得手动点重连——但如果人不在设备旁边,等回来时可能已经错过了设备重启后的第一个报文。所以进阶版一定要做自动重连。重连逻辑不是简单地while true里调Connect,那样连不上时会在 3 秒超时、重试、再超时的循环里疯狂试探,CPU 空转,而且每次 TCP 握手都会消耗资源。

我的做法是配合心跳与指数退避:接收循环里如果超过 N 秒没收到任何字节,就判定链路异常,主动断开并进入重连流程。每一次连接失败之后,等待时长翻倍:1 秒、2 秒、4 秒、8 秒…… 封顶 30 秒。这样既保证设备恢复后能自动接回来,又不会在设备彻底宕机时制造一堆无效连接请求。

private void ReconnectLoop(string ip, int port) { int delayMs = 1000; while (_needReconnect && !_closing) { try { Connect(ip, port, 3000); RaiseLog("自动重连成功"); _needReconnect = false; return; // 重连成功后退出循环,由接收线程负责后续检测 } catch { RaiseLog($"重连失败,{delayMs} 毫秒后重试"); Thread.Sleep(delayMs); delayMs = Math.Min(delayMs * 2, 30000); // 指数退避,封顶 30 秒 } } }

心跳超时参数的设置有个权衡:设太短,设备正常但暂时没数据时会被误判断开;设太长,断了之后恢复慢。对大多数调试场景,我习惯设 15 秒无数据就算超时,但这个值要根据协议里对端的心跳频率来调,如果对端本身每 5 秒就发一跳,那你设 15 秒就偏长了,8 到 10 秒更合适。

5.2 多连接管理:ConcurrentDictionary 管理客户端会话

服务端模式接进来多个客户端后,界面上需要往下发数据时要能选择「发给谁」。一开始我用普通的 Dictionary 存客户端 Socket,结果在多个接收线程同时增删客户端时,Dictionary 内部结构被破坏,遍历时抛「集合已修改」异常。换成ConcurrentDictionary后这类问题直接消失,它内部做了分段加锁,对调试助手这个量级的并发完全够用。

private readonly ConcurrentDictionary<int, ClientSession> _clients = new(); public void SendToClient(int sessionId, byte[] data) { if (_clients.TryGetValue(sessionId, out ClientSession session)) { session.Socket.Send(data); RaiseLog($"已发送给会话 {sessionId}: {BytesToHex(data, data.Length)}"); } }

这里要留意:ConcurrentDictionary解决的是「字典本身的并发访问」,不保证「一个 Session 的 Socket 只有一个线程在用它发送」。也就是说,只要一个副作用:同一个客户端 sessionId 可能同时被定时发送、手动发送两个逻辑触发,所以 Sending 侧仍然要各自持有锁。真正稳妥的跨线程模型是:维护每个 Session 自己独立的发送队列与发送线程,从结构上杜绝交叉写 Socket。调试助手如果只做单客户端调试,两者差别不大,但一旦做多客户端仪表盘展示,发送队列的独立化就必须一次到位,不然调两天就返工。

5.3 会话日志:滚动文件输出怎么做

会做日志比会收数据更重要。协议调试经常是「等一个偶发问题复现」,问题没复现前你盯着屏幕没有任何意义,等复现了才发现忘了保存接收数据,那就只能再等下一次偶发。所以从第一天起,就应该把收发数据原样追加到文件里。日志格式最实用的是 CSV,因为它天然被 Excel/WPS 识别,每一行包含四个字段:时间戳、方向(收/发)、会话 ID(如果是服务端模式)、内容(Hex 或 ASCII)。

这里直接定时写文件也有风险,我见过有人每次收一条就File.AppendAllText一次,结果数据流量一大,写盘把接收线程拖慢了,直接丢包。正确做法是日志走异步队列:

public class RollingLogger { private readonly ConcurrentQueue<string> _lines = new(); private readonly string _basePath; private int _currentSize; public void Write(string line) { _lines.Enqueue(line); _currentSize += line.Length; if (_currentSize > 5 * 1024 * 1024) { RollFile(); // 超过 5MB 就切换到新文件,避免单文件过大 } } }

滚动条件按 5MB 切文件是我个人偏好,你可以按实际流量调整。切分时保留最近 5 个文件,旧文件直接删除,这样不会让一次长时间调试把磁盘写满。日志内容我建议 Hex 和 ASCII 都写进同一行,中间用管道符分隔,排查时就不用切换显示模式来回看。

6. 验证你自己的调试助手:用对端软件做完整回环测试

写完了不代表能直接用。我给自己的调试助手做验证有一套固定流程,先测 TCP 客户端模式,再测服务端,最后测 UDP。第一轮验证:启动一个本地监听端口(可以用同机的两个实例互相连,也可以用一个简单的TcpListener程序做对端)。客户端连上后,从对端定时发 100 字节的递增序列,比如0x00 0x01 0x02 ... 0x63,然后看接收区能否按顺序显示,且每包长度一致。这一步能快速暴露粘包处理、半包处理和显示效率问题,比用真实设备调快得多。

第二轮验证:测服务端多连接。开三个不同 IP 的客户端连进来,每个客户端发不同前缀的数据,确认接收区能显示每个包的来源会话 ID。同时手动断开其中一个客户端,确认整个服务端进程不崩,日志里能正确记录断开事件。真实设备经常有异常断开的操作,这一步漏了会砸在现场。

第三轮是最重要的:性能试探。用对端程序一次性连续发送 5000 条数据,每条 128 字节,中间不做延迟。这时你的助手如果接收区还能一条不丢地显示出来,UI 不卡死,说明接收线程、队列和日志设计基本合格了。如果出现数据越攒越多、界面刷新滞后,说明显示部分在拖后腿——通常需要把文本框刷新改成批量追加,而不是每条数据都触发一次Text赋值。我常用的优化是在接收逻辑里做一个 20 毫秒的批量刷新窗口,把这一窗口内收到的所有内容拼成一个大字符串一次性更新 UI,刷新性能能提升一个数量级。

我踩过的最后一个坑是发送方向:用助手连续定时发送 100 条数据后,对比对端实际收到的数据条数,发现少了几条,排查后确认是Thread.Sleep(50)定时精度抖动导致的发送间隔不均。后来把定时发送改成了System.Threading.Timer,并把发送间隔粒度提高到 50 毫秒以上,问题就没有再出现。网络调试助手这东西,本质上就是你和设备之间的一个「字节观察窗」,窗口做得好不好,直接决定你在现场排查协议问题时是十分钟定位问题还是拖到半夜。每次写完这种断言感很强的工具,我再回头用原先的现成软件做对比验证,都会觉得这些底层实现值得自己再花点时间做得更顺手。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询