当前位置: 首页 > news >正文

C# TCP - 串口转发

C# TCP - 串口转发服务器代码笔记

一、项目概述

该项目是一个基于 C# WinForms 的TCP 服务器与串口通信结合的转发系统,核心功能是实现 “TCP 客户端 ↔ 服务器 ↔ 串口设备” 之间的双向数据转发,适用于需要通过网络远程控制串口设备或读取串口设备数据的场景(如工业控制、物联网设备监控等)。

二、核心功能模块

1. 配置管理模块

1.1 功能说明
  • 自动加载并显示本机 IPv4 地址、串口列表及通信参数(波特率、数据位等)

  • 支持从配置文件(App.config)读取历史配置,实现 “配置持久化”

  • 提供配置保存功能,修改后需重启服务器生效

1.2 关键代码解析
// 1. 绑定串口参数(波特率、数据位等)
private void BindData()
{// 加载本机IPv4地址string hostName = Dns.GetHostName();IPAddress[] addresses = Dns.GetHostAddresses(hostName);foreach (IPAddress address in addresses){if (address.AddressFamily == AddressFamily.InterNetwork) // 筛选IPv4IP = address.ToString();}
​// 从配置文件读取历史配置(优先级:配置文件 > 自动获取)txtIP.Text = IP != ConfigurationManager.AppSettings["IP"] ? IP : ConfigurationManager.AppSettings["IP"];txtPort.Text = ConfigurationManager.AppSettings["Port"];cbbPortNames.Text = ConfigurationManager.AppSettings["PortName"];// ... 其他参数(波特率、数据位等)同理
}
​
// 2. 保存配置到App.config
private void btnSave_Click(object sender, EventArgs e)
{// 输入校验(非空判断)if (string.IsNullOrWhiteSpace(txtIP.Text)){MessageBox.Show("服务器IP不能为空!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Error);return;}
​// 写入配置文件Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);config.AppSettings.Settings["IP"].Value = txtIP.Text.Trim();config.AppSettings.Settings["Port"].Value = txtPort.Text.Trim();// ... 其他参数同理config.Save(); // 保存配置MessageBox.Show("保存配置成功!\n请重新启动服务器!", "提示");
}
1.3 注意事项
  • 配置文件需提前在项目中创建,需包含IPPortPortName等关键字段

  • 保存配置后需重启服务器,配置才会生效

2. 服务器启停模块

2.1 功能说明
  • 启动:同时初始化 TCP 服务器和串口,禁用配置修改控件,更新状态指示(绿色 = 运行)

  • 停止:关闭 TCP 服务器和串口,启用配置修改控件,更新状态指示(红色 = 停止)

2.2 关键代码解析
// 1. 启动服务器(TCP+串口)
private void StartServer()
{// 初始化串口serialPort1.PortName = cbbPortNames.Text;serialPort1.BaudRate = int.Parse(cbbBaudRate.Text);serialPort1.DataBits = int.Parse(cbbDataBits.Text);serialPort1.StopBits = (StopBits)Enum.Parse(typeof(StopBits), cbbStopBits.Text); // 枚举转换serialPort1.Parity = (Parity)Enum.Parse(typeof(Parity), cbbParity.Text);serialPort1.Open(); // 打开串口
​// 初始化TCP服务器server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);int port = int.Parse(ConfigurationManager.AppSettings["Port"]);IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Any, port); // 监听所有网卡的指定端口server.Bind(ipEndPoint); // 绑定IP和端口server.Listen(100); // 最大监听队列100
​// 更新UI状态btnStart.Text = "停止";txtIP.Enabled = false; // 禁用配置修改panelSerialPort.BackColor = Color.Lime; // 串口运行(绿色)panelNetwork.BackColor = Color.Lime; // 网络运行(绿色)
}
​
// 2. 停止服务器
private void StopServer()
{server.Close(); // 关闭TCP服务器serialPort1.Close(); // 关闭串口
​// 恢复UI状态btnStart.Text = "启动";txtIP.Enabled = true; // 启用配置修改panelSerialPort.BackColor = Color.Red; // 串口停止(红色)panelNetwork.BackColor = Color.Red; // 网络停止(红色)
}
​
// 3. 启停触发按钮
private void btnStart_Click(object sender, EventArgs e)
{try{if (!serialPort1.IsOpen) // 未启动 → 启动{StartServer();  AcceptData(); // 启动数据接收任务}else // 已启动 → 停止{CacelAcceptData(); // 取消数据接收任务StopServer();       }}catch (Exception ex){MessageBox.Show(ex.Message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);}
}
2.3 注意事项
  • 启动前需确保串口未被其他程序占用,TCP 端口未被占用

  • 枚举转换(StopBits/Parity)需保证下拉框值与枚举名一致(如 “One” 对应StopBits.One

3. 数据转发模块

3.1 核心流程
  1. TCP 客户端 → 服务器 → 串口设备:服务器接收 TCP 客户端数据,通过串口转发给设备

  2. 串口设备 → 服务器 → TCP 客户端:服务器接收串口设备应答数据,广播转发给所有 TCP 客户端

3.2 关键代码解析
3.2.1 接收 TCP 客户端数据并转发到串口
private void AcceptData()
{cts1 = new CancellationTokenSource(); // 任务取消令牌(用于停止时中断任务)Task.Run(() => // 异步任务(避免阻塞UI){while (!cts1.IsCancellationRequested){// 1. 接收TCP客户端连接Socket client = server.Accept(); // 阻塞等待新连接string clientKey = client.RemoteEndPoint.ToString(); // 客户端标识(IP:端口)// 去重:移除已存在的相同客户端if (clients.ContainsKey(clientKey))clients.Remove(clientKey);clients.Add(clientKey, client); // 加入客户端字典
​// 2. 接收该客户端的持续数据cts2 = new CancellationTokenSource();Task.Run(() =>{while (!cts2.IsCancellationRequested){byte[] buffer = new byte[client.Available]; // 根据可用数据长度创建缓冲区int len = client.Receive(buffer); // 接收客户端数据
​if (len > 0){// 转发到串口serialPort1.Write(buffer, 0, len);
​// 异步更新UI(数据统计、状态闪烁)Invoke(new Action(async () =>{txtSendByte.Text = (int.Parse(txtSendByte.Text) + len).ToString(); // 发送字节数统计panelReceive.BackColor = Color.Lime; // 接收状态闪烁(绿色)await Task.Delay(70); // 闪烁时长panelReceive.BackColor = Color.Gray;}));}}}, cts2.Token);}}, cts1.Token);
}
3.2.2 接收串口数据并广播到所有 TCP 客户端
// 串口数据接收事件(设备应答数据)
private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{byte[] buffer = new byte[serialPort1.BytesToRead]; // 缓冲区长度=可用数据长度int len = serialPort1.Read(buffer, 0, buffer.Length); // 读取串口数据
​if (len > 0){// 广播到所有TCP客户端foreach (var dict in clients){Socket client = dict.Value;if (client != null && client.Connected) // 确保客户端连接正常{client.Send(buffer); // 转发数据
​// 异步更新UI(接收字节数统计、状态闪烁)Invoke(new Action(async () =>{txtReceiveByte.Text = (int.Parse(txtReceiveByte.Text) + len).ToString(); // 接收字节数统计panelSend.BackColor = Color.Lime; // 发送状态闪烁(绿色)await Task.Delay(70);panelSend.BackColor = Color.Gray;}));}}}
}
3.3 注意事项
  • 数据缓冲区长度使用client.Available/serialPort1.BytesToRead,避免内存浪费

  • 跨线程更新 UI 需使用Invoke(WinForms 控件线程安全限制)

  • 客户端管理使用Dictionary<string, Socket>,键为客户端IP:端口,便于去重和广播

4. 安全与异常处理模块

4.1 功能说明
  • 任务取消:通过CancellationTokenSource安全中断异步数据接收任务

  • 窗体关闭保护:服务器运行时禁止关闭窗体,避免资源泄漏

  • 输入校验:保存配置前检查关键参数非空

4.2 关键代码解析
// 1. 取消数据接收任务
private void CacelAcceptData()
{cts2?.Cancel(); // 取消单个客户端数据接收任务cts1?.Cancel(); // 取消客户端连接监听任务
}
​
// 2. 窗体关闭保护
private void Server_FormClosing(object sender, FormClosingEventArgs e)
{if (serialPort1.IsOpen) // 服务器运行中 → 禁止关闭{e.Cancel = true; // 取消关闭操作MessageBox.Show("服务器正在运行中,请停止服务器后,再关闭!", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);}
}

三、UI 控件说明

控件类型控件名称用途
TextBoxtxtIP显示 / 输入服务器 IP
TextBoxtxtPort显示 / 输入 TCP 端口
ComboBoxcbbPortNames选择串口名称(如 COM3)
ComboBoxcbbBaudRate选择波特率(如 9600、115200)
ComboBoxcbbDataBits选择数据位(5-8)
ComboBoxcbbStopBits选择停止位(One、Two 等)
ComboBoxcbbParity选择校验位(None、Odd 等)
ButtonbtnStart启动 / 停止服务器
ButtonbtnSave保存配置到 App.config
TextBoxtxtSendByte统计转发到串口的字节数
TextBoxtxtReceiveByte统计从串口接收的字节数
PanelpanelSerialPort串口状态指示(绿 = 运行,红 = 停)
PanelpanelNetworkTCP 服务器状态指示
PanelpanelReceiveTCP 数据接收状态闪烁
PanelpanelSend串口数据转发状态闪烁

四、常见问题与解决方案

  1. 串口打开失败

    • 原因:串口被其他程序占用、串口名称选择错误

    • 解决方案:关闭占用串口的程序,重新选择正确的串口

  2. TCP 端口绑定失败

    • 原因:端口被其他程序占用、端口号超出范围(0-65535)

    • 解决方案:更换未占用的端口,确保端口号合法

  3. 跨线程更新 UI 报错

    • 原因:WinForms 控件不允许非 UI 线程直接修改

    • 解决方案:使用Invoke(new Action(() => { ... }))包裹 UI 更新代码

  4. 配置保存后不生效

    • 原因:配置需重启服务器加载

    • 解决方案:保存后关闭服务器,重新启动

  5. 客户端连接后无法接收数据

    • 原因:客户端未正确连接、数据缓冲区长度不足

    • 解决方案:检查客户端 IP 和端口是否正确,确保缓冲区长度足够(建议使用固定长度缓冲区如 1024,避免client.Available=0时缓冲区为空)

五、扩展建议

  1. 增加日志功能:记录客户端连接 / 断开、数据转发详情,便于问题排查

  2. 客户端心跳检测:定期检测客户端连接状态,移除断开的客户端

  3. 数据格式解析:支持自定义协议(如帧头 + 数据 + 校验位),过滤无效数据

  4. 多串口支持:扩展为多串口转发,适配多个设备

  5. UI 优化:增加客户端列表显示(当前连接的客户端 IP: 端口),支持手动断开指定客户端

C# TCP 客户端(Modbus 协议)代码笔记

一、项目概述

该客户端是基于 C# WinForms + TCP 协议 + Modbus-RTU 协议 的设备通信工具,核心功能是与前文的 “TCP - 串口转发服务器” 交互,实现对串口设备的 数据读取(Modbus 功能码 03)数据写入(Modbus 功能码 06),适用于工业设备(如传感器、控制器)的远程监控与控制场景。

二、核心技术栈

  1. 网络通信Socket 类实现 TCP 客户端,支持异步连接、发送、接收

  2. 协议处理:Modbus-RTU 协议(功能码 03 读保持寄存器、功能码 06 写单个寄存器)

  3. 数据校验:CRC16 循环冗余校验(确保 Modbus 报文完整性)

  4. 异步编程Task + CancellationTokenSource 实现非阻塞通信,避免 UI 卡顿

三、核心功能模块

1. TCP 连接管理模块

1.1 功能说明
  • 建立连接:根据输入的服务器 IP 和端口,异步创建 TCP 连接

  • 断开连接:关闭 Socket,释放资源,恢复 UI 可编辑状态

  • 状态控制:连接成功后禁用 IP / 端口输入,切换按钮文本为 “断开”

1.2 关键代码解析

csharp

private async void btnConnOrClose_Click(object sender, EventArgs e)
{try{if (btnConnOrClose.Text == "连接"){// 1. 初始化TCP Socket(IPv4、流式传输、TCP协议)client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);// 2. 异步连接服务器(避免阻塞UI)// 解析IP和端口:IPAddress.Parse(txtIP.Text) → 转换为IP地址对象;int.Parse(txtPort.Text) → 转换为端口号await client.ConnectAsync(new IPEndPoint(IPAddress.Parse(txtIP.Text), int.Parse(txtPort.Text)));// 3. 更新UI状态(连接成功)btnConnOrClose.Text = "断开"; // 按钮文本切换为“断开”txtIP.Enabled = false; // 禁用IP输入txtPort.Enabled = false; // 禁用端口输入}else{// 1. 断开连接:先关闭连接,再释放Socketclient.Disconnect(false); // false = 不允许后续重用Socketclient.Close(); // 关闭Socket,释放资源client = null; // 置空,避免空引用// 2. 恢复UI状态(断开成功)btnConnOrClose.Text = "连接"; // 按钮文本切换为“连接”txtIP.Enabled = true; // 启用IP输入txtPort.Enabled = true; // 启用端口输入}}catch (Exception ex){// 异常处理(如IP格式错误、端口占用、服务器未启动等)MessageBox.Show(ex.Message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);return;}
​// 连接成功后,启动数据接收任务(持续接收服务器转发的设备应答)await ReceiveMessage();
}
1.3 注意事项
  • 异步连接:使用 ConnectAsync 而非同步 Connect,避免 UI 卡死

  • 异常捕获:需处理 FormatException(IP / 端口格式错误)、SocketException(连接失败)等

  • 资源释放:断开时必须调用 Close(),否则会导致 Socket 资源泄漏

2. Modbus 数据读取模块(功能码 03)

2.1 功能说明
  • 实时读取:勾选 “实时读取” 后,每 3 秒自动发送 Modbus 读指令(功能码 03)

  • 报文构造:生成包含 “从站地址、功能码、寄存器地址、寄存器数量、CRC 校验” 的完整 Modbus 报文

  • 数据解析:接收设备应答报文后,解析寄存器值并显示到 UI

2.2 关键代码解析
2.2.1 实时读取触发(复选框事件)

csharp

private void cbRealTimeRead_CheckedChanged(object sender, EventArgs e)
{if (cbRealTimeRead.Checked){// 校验连接状态:未连接则提示if (client == null || !client.Connected){MessageBox.Show("先建立连接,再读取!", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);cbRealTimeRead.Checked = false; // 取消勾选return;}
​// 启动数据发送任务(每3秒发一次读指令)_ = SendMessage(); // 用_忽略Task返回值,避免编译器警告}else{// 取消实时读取:终止发送任务cts1?.Cancel(); // cts1是发送任务的取消令牌}
}
2.2.2 构造 Modbus 读报文并发送

csharp

private Task SendMessage()
{// 初始化取消令牌(用于终止实时发送任务)cts1 = new CancellationTokenSource();return Task.Run(async () =>{while (!cts1.IsCancellationRequested) // 任务未取消则循环{// 1. 构造Modbus读指令核心报文(功能码03)// 格式:[从站地址(1字节)][功能码(1字节)][起始寄存器地址高8位(1字节)][起始寄存器地址低8位(1字节)][读取寄存器数量高8位(1字节)][读取寄存器数量低8位(1字节)]byte[] modbusCore = new byte[6] { 0x01,          // 从站地址:1(默认设备地址)0x03,          // 功能码:03(读保持寄存器)0x00, 0x00,    // 起始寄存器地址:0x0000(从第0个寄存器开始读)0x00, 0x02     // 读取寄存器数量:0x0002(读2个寄存器)};
​// 2. 计算CRC16校验(Modbus-RTU必须加CRC,确保报文无差错)byte[] crc = CRC16(modbusCore);
​// 3. 组装完整报文(核心报文 + CRC校验)byte[] fullPacket = new byte[8]; // 6字节核心 + 2字节CRC = 8字节Array.Copy(modbusCore, 0, fullPacket, 0, modbusCore.Length); // 复制核心报文Array.Copy(crc, 0, fullPacket, modbusCore.Length, crc.Length); // 复制CRC
​// 4. 发送报文到服务器(由服务器转发给串口设备)client.Send(fullPacket);
​// 5. 延迟3秒(避免频繁发送,减轻设备压力)await Task.Delay(3000, cts1.Token); // 传入取消令牌,支持延迟中取消}}, cts1.Token);
}
2.2.3 接收并解析设备应答报文

csharp

private Task ReceiveMessage()
{// 初始化取消令牌(用于终止接收任务)cts2 = new CancellationTokenSource();return Task.Run(async () =>{while (!cts2.IsCancellationRequested) // 任务未取消则循环{// 1. 创建缓冲区(长度=当前可用数据长度,避免内存浪费)byte[] buffer = new byte[client.Available];// 2. 接收服务器转发的设备应答数据int receiveLen = client.Receive(buffer); // 返回实际接收的字节数// 3. 校验应答报文长度(Modbus读2个寄存器的应答应为9字节:1+1+1+2*2+2)// 格式:[从站地址][功能码][字节数][寄存器1高8位][寄存器1低8位][寄存器2高8位][寄存器2低8位][CRC高8位][CRC低8位]if (receiveLen == 9){// 跨线程更新UI(WinForms控件不允许非UI线程直接修改)Invoke(new Action(() =>{// 解析寄存器1值:高8位*256 + 低8位(16位无符号整数)int reg1Value = buffer[3] * 256 + buffer[4];txtReadData1.Text = reg1Value.ToString(); // 显示到UI// 解析寄存器2值:同理int reg2Value = buffer[5] * 256 + buffer[6];txtReadData2.Text = reg2Value.ToString(); // 显示到UI}));}// 4. 延迟1秒(降低CPU占用)await Task.Delay(1000, cts2.Token);}}, cts2.Token);
}

3. Modbus 数据写入模块(功能码 06)

3.1 功能说明
  • 单寄存器写入:根据输入的数值,构造 Modbus 写指令(功能码 06),写入指定寄存器

  • 输入校验:确保输入为合法整数,避免无效指令发送

  • 报文构造:包含 “从站地址、功能码、目标寄存器地址、写入值、CRC 校验”

3.2 关键代码解析(以写入寄存器 1 为例)

csharp

private void button1_Click(object sender, EventArgs e)
{// 1. 输入校验:确保输入是合法整数bool isInt = int.TryParse(txtSetData1.Text, out int writeValue);if (!isInt){MessageBox.Show("输入正确格式的数据!", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);return;}// 2. 拆分写入值为高低8位(Modbus寄存器是16位,需分高低字节传输)byte highByte = (byte)(writeValue / 256); // 高8位:数值除以256取整byte lowByte = (byte)(writeValue % 256);  // 低8位:数值除以256取余// 3. 构造Modbus写指令核心报文(功能码06)// 格式:[从站地址(1字节)][功能码(1字节)][目标寄存器地址高8位(1字节)][目标寄存器地址低8位(1字节)][写入值高8位(1字节)][写入值低8位(1字节)]byte[] modbusCore = new byte[6] { 0x01,          // 从站地址:10x06,          // 功能码:06(写单个保持寄存器)0x00, 0x00,    // 目标寄存器地址:0x0000(写入第0个寄存器)highByte, lowByte // 写入值的高低8位};// 4. 计算CRC16校验byte[] crc = CRC16(modbusCore);// 5. 组装完整报文(核心报文 + CRC)byte[] fullPacket = new byte[8];Array.Copy(modbusCore, 0, fullPacket, 0, modbusCore.Length);Array.Copy(crc, 0, fullPacket, modbusCore.Length, crc.Length);// 6. 发送报文(前提:已建立连接)if (client != null && client.Connected)client.Send(fullPacket);
}
3.3 写入寄存器 2 的差异

目标寄存器地址 不同:将 0x00, 0x00 改为 0x00, 0x01,对应写入第 1 个寄存器,其余逻辑完全一致(代码见 button2_Click 方法)。

4. CRC16 校验模块

4.1 功能说明

Modbus-RTU 协议要求所有报文末尾必须添加 2 字节 CRC16 校验,用于检测报文在传输过程中是否出现差错(如丢包、错码)。该模块实现标准的 CRC16 算法(多项式 0xA001,初始值 0xFFFF)。

4.2 关键代码解析

csharp

private static byte[] CRC16(byte[] data)
{int dataLen = data.Length;if (dataLen == 0) // 空数据返回空校验return new byte[] { 0, 0 };ushort crc = 0xFFFF; // 初始值:0xFFFF// 1. 遍历数据字节,计算CRCfor (int i = 0; i < dataLen; i++){crc = (ushort)(crc ^ data[i]); // 当前CRC与数据字节异或// 2. 每字节循环8位(处理每一位)for (int j = 0; j < 8; j++){// 若最低位为1:右移1位后与多项式0xA001异或;否则仅右移1位crc = (crc & 1) != 0 ? (ushort)((crc >> 1) ^ 0xA001) : (ushort)(crc >> 1);}}// 3. CRC结果高低位交换(Modbus-RTU要求小端序:低字节在前,高字节在后)byte crcLow = (byte)(crc & 0x00FF);  // 低8位byte crcHigh = (byte)((crc & 0xFF00) >> 8); // 高8位return new byte[] { crcLow, crcHigh }; // 返回小端序CRC
}

四、UI 控件说明

控件类型控件名称用途
TextBoxtxtIP输入服务器 IP 地址(如 192.168.1.100)
TextBoxtxtPort输入服务器 TCP 端口(如 9999)
ButtonbtnConnOrClose建立 / 断开 TCP 连接
CheckBoxcbRealTimeRead勾选启用实时读取设备数据
TextBoxtxtReadData1显示读取的寄存器 1 数值
TextBoxtxtReadData2显示读取的寄存器 2 数值
TextBoxtxtSetData1输入要写入寄存器 1 的数值
TextBoxtxtSetData2输入要写入寄存器 2 的数值
Buttonbutton1触发写入寄存器 1
Buttonbutton2触发写入寄存器 2

五、常见问题与解决方案

  1. 连接失败,提示 “无法连接到远程服务器”

    • 原因:服务器未启动、IP / 端口输入错误、网络不通(防火墙拦截)

    • 解决方案:确认服务器已启动,检查 IP 和端口是否与服务器一致,关闭防火墙或开放对应端口

  2. 实时读取无数据,UI 无显示

    • 原因:Modbus 报文格式错误(从站地址、寄存器地址错误)、CRC 校验错误、服务器未转发数据

    • 解决方案:

      • 检查从站地址是否与设备匹配(默认 0x01,若设备地址不同需修改)

      • 用串口工具(如 SSCOM)抓取报文,验证 CRC 是否正确

      • 确认服务器已正常连接串口设备

  3. 写入数据后,设备无响应

    • 原因:写入值超出寄存器范围(如 16 位寄存器最大 65535)、目标寄存器地址错误

    • 解决方案:确认写入值在设备寄存器允许范围内,检查目标寄存器地址是否与设备手册一致

  4. UI 卡顿

    • 原因:未使用异步编程,同步发送 / 接收阻塞 UI 线程

    • 解决方案:确保所有网络操作(ConnectSendReceive)使用异步方法(ConnectAsyncSendAsync),并用Task.Run将循环逻辑放入后台线程

  5. 取消实时读取后,任务仍在运行

    • 原因:未正确调用CancellationTokenSource.Cancel(),或取消后未释放令牌

    • 解决方案:确保cbRealTimeRead取消勾选时,调用cts1?.Cancel(),且任务循环中检查cts1.IsCancellationRequested

六、扩展建议

  1. 增加报文日志:记录发送 / 接收的原始字节(如01 03 00 00 00 02 D4 0B),便于调试协议问题

  2. 支持多从站:增加从站地址输入框,支持同时与多个地址的设备通信

  3. 批量读写:扩展 Modbus 功能码(如功能码 16 批量写寄存器),支持一次写入多个寄存器

  4. 数据格式转换:支持十进制 / 十六进制显示切换,适配不同设备的数据格式

  5. 断线重连:增加自动重连机制,服务器断开后无需手动重新连接

  6. 错误处理增强:解析 Modbus 异常响应(如功能码 + 0x80 表示错误),提示具体错误

http://www.dtcms.com/a/418979.html

相关文章:

  • 广州 济南网站建设公司 网络服务江苏网站备案流程图
  • Ceph 分布式存储学习笔记(一):介绍、部署与集群配置(上)
  • 先做他个天猫网站产品外观设计图片
  • Accelerate 与 torchrun 分布式训练LLM对比
  • 建设部网站哪里可以报名考监理员优化优化
  • 农家乐怎么做网站上海app开发网站建设
  • 海口建站价格网站建设的物流
  • Leetcode 26. 删除有序数组中的重复项
  • Linux 的文本编辑器vim食用指南
  • 开发实战:从0到1实现Chrome元素截图插件的完整过程
  • ue编辑器视口鼠标消失的问题
  • 【数据结构】多项式的基本运算
  • 在ubuntu下载企业微信
  • 基于Chrome140的FB账号自动化——需求分析环境搭建(一)
  • MCP:cursor、claude code接入chrome-devtools-mcp。
  • 台风“桦加沙”袭击大,盈电智控物联网漏水检测系统为关键场所筑牢“隐形堤坝”!
  • 北京比较好的互联网公司晋中seo排名
  • 高通平台WiFi学习---深入了解 WLAN host crash调试
  • 在 Ubuntu 上可以用几个常用命令查看系统运行情况(内存、CPU、硬盘占用等
  • 子路由器如何设置 路由器LAN-WAN级联的设置方法
  • 【Ubuntu】请问,『kill -9』跟『kill -15』有区别吗?
  • 科网站建设免费查公司的网站
  • SCDN-保护网站安全的有效方案
  • Go 的跨平台编译详解
  • docker命令总结
  • 2、user-service 企业级代码目录结构规范
  • 网站开发的自适应wordpress 万能搜索页
  • Linux设置定时作业执行node.js脚本
  • XXE - 实体注入(xml外部实体注入)
  • MySQL查询性能优化核心知识点总结