Socket-TCP 上位机 ↔ 下位机数据交互框架(C# WinForm 版本) 。
框架特点:
- 上位机既做"客户端"又可秒切"TCP服务器",适应不同场景
- 支持 01 线圈 / 03 保持寄存器 / 04 输入寄存器 的任意地址位配置
- 实时日志、自动重连、断线检测、CRC16 校验
- 线程安全:收发线程与 UI 线程解耦,不卡界面
- 协议扩展:预留"私有协议"接口,可快速改为 Modbus-TCP、自定义帧格式
1. 工程准备
依赖 | NuGet 指令 |
---|---|
日志 | Install-Package NLog |
CRC16 | Install-Package Crc16 |
2. 核心代码目录
/FrmMain.cs // UI
/TcpLink.cs // 连接管理
/ProtocolCodec.cs // 协议编解码
/DataStore.cs // 地址-数据映射(01 03 04)
3. 代码
3.1 连接管理(TcpLink.cs)
csharp
public class TcpLink : IDisposable
{
private TcpClient _tcp;
private NetworkStream _ns;
private readonly CancellationTokenSource _cts = new();
public event Action<byte[]> OnReceived;
public bool IsConnected => _tcp?.Connected == true;
// 作为客户端连接
public async Task ConnectAsync(string ip, int port)
{
_tcp = new TcpClient();
await _tcp.ConnectAsync(ip, port);
_ns = _tcp.GetStream();
_ = Task.Run(() => ReceiveLoop(_cts.Token));
}
// 作为服务器监听
public async Task StartServerAsync(int port)
{
var listener = new TcpListener(IPAddress.Any, port);
listener.Start();
var client = await listener.AcceptTcpClientAsync();
_tcp = client; _ns = client.GetStream();
_ = Task.Run(() => ReceiveLoop(_cts.Token));
}
public async Task SendAsync(byte[] data)
{
if (!IsConnected) return;
await _ns.WriteAsync(data, 0, data.Length);
}
private async Task ReceiveLoop(CancellationToken ct)
{
var buf = new byte[1024];
while (!ct.IsCancellationRequested)
{
var len = await _ns.ReadAsync(buf, 0, buf.Length, ct);
if (len == 0) break; // 断线
OnReceived?.Invoke(buf.AsSpan(0, len).ToArray());
}
}
public void Dispose()
{
_cts.Cancel();
_ns?.Dispose();
_tcp?.Close();
}
}
3.2 地址-数据映射(DataStore.cs)
csharp
public class DataStore
{
private readonly bool[] _coils = new bool[1000]; // 01
private readonly ushort[] _holdingRegs = new ushort[1000]; // 03
private readonly ushort[] _inputRegs = new ushort[1000]; // 04
public bool[] Coils => _coils;
public ushort[] HoldingRegs => _holdingRegs;
public ushort[] InputRegs => _inputRegs;
// 示例:设置单个线圈
public void SetCoil(ushort addr, bool value) => _coils[addr] = value;
public bool GetCoil(ushort addr) => _coils[addr];
// 示例:设置保持寄存器
public void SetHolding(ushort addr, ushort value) => _holdingRegs[addr] = value;
public ushort GetHolding(ushort addr) => _holdingRegs[addr];
}
3.3 Modbus-TCP 协议编解码(ProtocolCodec.cs)
csharp
public static class ProtocolCodec
{
public static byte[] BuildReadHolding(ushort addr, ushort qty, byte slaveId)
{
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
bw.Write((ushort)0x0000); // 事务标识
bw.Write((ushort)0x0000); // 协议标识
bw.Write((ushort)0x0006); // 长度
bw.Write(slaveId);
bw.Write((byte)0x03); // 功能码 03
bw.Write(addr);
bw.Write(qty);
return ms.ToArray();
}
public static bool TryDecode(byte[] raw, out byte fn, out ushort addr, out ushort[] values)
{
if (raw.Length < 8) { fn = 0; addr = 0; values = null; return false; }
fn = raw[7];
addr = (ushort)(raw[8] << 8 | raw[9]);
var qty = (ushort)(raw[10] << 8 | raw[11]);
values = new ushort[qty];
var bytes = raw.AsSpan(9 + 2);
for (int i = 0; i < qty; i++)
values[i] = (ushort)(bytes[i * 2] << 8 | bytes[i * 2 + 1]);
return true;
}
}
3.4 WinForm 调用(FrmMain.cs)
csharp
public partial class FrmMain : Form
{
private readonly TcpLink _link = new();
private readonly DataStore _store = new();
public FrmMain()
{
InitializeComponent();
_link.OnReceived += bytes =>
{
if (ProtocolCodec.TryDecode(bytes, out var fn, out var addr, out var vals))
{
// 更新 UI
this.Invoke(() => UpdateUI(fn, addr, vals));
}
};
}
private async void btnConnect_Click(object sender, EventArgs e)
{
await _link.ConnectAsync(txtIP.Text, int.Parse(txtPort.Text));
lblStatus.Text = "已连接";
}
private async void btnRead_Click(object sender, EventArgs e)
{
ushort addr = ushort.Parse(txtAddr.Text);
ushort qty = 1;
var frame = ProtocolCodec.BuildReadHolding(addr, qty, 1);
await _link.SendAsync(frame);
}
private void UpdateUI(byte fn, ushort addr, ushort[] vals)
{
if (fn == 0x03) txtValue.Text = vals[0].ToString();
}
}
4. 运行步骤
- 下位机(如STM32)作为 TCP服务器 监听 502 端口,实现 Modbus-TCP 协议。
- 上位机输入服务器 IP → 连接 → 点击"读保持寄存器"即可实时获取数据。
- 如需私有协议,只需替换
ProtocolCodec
即可;所有通信逻辑复用。
参考代码 soket、tcp上位机与下位机数据交互框架 www.youwenfan.com/contentcne/112324.html
5. 可扩展功能
- 心跳包:定时发送,断线自动重连
- CRC16 校验 :在
ProtocolCodec
中加入Crc16.ComputeChecksum()
- 日志:使用 NLog 记录收发帧
- 多线程:UI 线程与通信线程完全隔离,避免卡顿