C#是Windows平台工业软件开发的主流语言,广泛应用于上位机、SCADAsystem、数据采集软件等领域。本文提供完整的C# Modbus TCP服务器和客户端实现代码,包括TCP监听、多客户端管理、Modbus协议解析、회원가입 Mapping、客户端读写操作以及完整的使用示例。代码基于.NET Framework 4.5+,可直接在Visual Studio中编译运行。
一、项目结构与依赖
1.1 开发环境
| 项目 | 要求 |
|---|---|
| 开发工具 | Visual Studio 2017+ / Visual Studio Code |
| .NET版本 | .NET Framework 4.5+ 或 .NET Core 2.0+ |
| 语言 | C# 7.0+ |
| NuGet包 | 无需第三方库,使用System.Net.Sockets |
| 项目类型 | 控制台应用 / Windows Forms / WPF |
1.2 회원가입 Mapping
| 地址范围 | Type | 说明 |
|---|---|---|
| 0x0000-0x03FF | 线圈(Coil) | 1024个可读写布尔量 |
| 0x0000-0x03FF | 离散输入 | 1024个只读布尔量 |
| 0x0000-0x03FF | 保持寄存器 | 1024个可读写16位整数 |
| 0x0000-0x03FF | 输入寄存器 | 1024个只读16位整数 |
二、Modbus TCP服务器实现
2.1 数据存储类(ModbusDataStore.cs)
using System;
namespace ModbusTcpServer
{
/// <summary>
/// Modbus数据存储,线程安全
/// </summary>
public class ModbusDataStore
{
private readonly object _lock = new object();
// 线圈(可读写布尔量)
private readonly bool[] _coils = new bool[1024];
// 离散输入(只读布尔量)
private readonly bool[] _inputs = new bool[1024];
// 保持寄存器(可读写16位)
private readonly ushort[] _holdingRegs = new ushort[1024];
// 输入寄存器(只读16位)
private readonly ushort[] _inputRegs = new ushort[1024];
// 线圈操作
public bool GetCoil(ushort address)
{
lock (_lock) { return _coils[address]; }
}
public void SetCoil(ushort address, bool value)
{
lock (_lock) { _coils[address] = value; }
}
public bool[] GetCoils(ushort startAddr, ushort count)
{
lock (_lock)
{
bool[] result = new bool[count];
Array.Copy(_coils, startAddr, result, 0, count);
return result;
}
}
public void SetCoils(ushort startAddr, bool[] values)
{
lock (_lock)
{
Array.Copy(values, 0, _coils, startAddr, values.Length);
}
}
// 离散输入操作
public bool[] GetInputs(ushort startAddr, ushort count)
{
lock (_lock)
{
bool[] result = new bool[count];
Array.Copy(_inputs, startAddr, result, 0, count);
return result;
}
}
public void SetInput(ushort address, bool value)
{
lock (_lock) { _inputs[address] = value; }
}
// 保持寄存器操作
public ushort GetHoldingReg(ushort address)
{
lock (_lock) { return _holdingRegs[address]; }
}
public void SetHoldingReg(ushort address, ushort value)
{
lock (_lock) { _holdingRegs[address] = value; }
}
public ushort[] GetHoldingRegs(ushort startAddr, ushort count)
{
lock (_lock)
{
ushort[] result = new ushort[count];
Array.Copy(_holdingRegs, startAddr, result, 0, count);
return result;
}
}
public void SetHoldingRegs(ushort startAddr, ushort[] values)
{
lock (_lock)
{
Array.Copy(values, 0, _holdingRegs, startAddr, values.Length);
}
}
// 输入寄存器操作
public ushort[] GetInputRegs(ushort startAddr, ushort count)
{
lock (_lock)
{
ushort[] result = new ushort[count];
Array.Copy(_inputRegs, startAddr, result, 0, count);
return result;
}
}
public void SetInputReg(ushort address, ushort value)
{
lock (_lock) { _inputRegs[address] = value; }
}
}
}
2.2 Modbus TCP服务器类(ModbusTcpServer.cs)
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace ModbusTcpServer
{
public class ModbusTcpServer
{
private TcpListener _listener;
private Thread _listenThread;
private bool _isRunning;
private readonly ModbusDataStore _dataStore;
private readonly List<TcpClient> _clients = new List<TcpClient>();
private ushort _transactionId = 0;
public int Port { get; } = 502;
public ModbusDataStore DataStore => _dataStore;
public ModbusTcpServer(int port = 502)
{
Port = port;
_dataStore = new ModbusDataStore();
}
public void Start()
{
_listener = new TcpListener(IPAddress.Any, Port);
_listener.Start();
_isRunning = true;
_listenThread = new Thread(ListenForClients)
{
IsBackground = true
};
_listenThread.Start();
Console.WriteLine($"Modbus TCP服务器已启动,监听端口:{Port}");
}
public void Stop()
{
_isRunning = false;
_listener?.Stop();
lock (_clients)
{
foreach (var client in _clients)
client.Close();
_clients.Clear();
}
Console.WriteLine("Modbus TCP服务器已停止");
}
private void ListenForClients()
{
while (_isRunning)
{
try
{
TcpClient client = _listener.AcceptTcpClient();
lock (_clients) { _clients.Add(client); }
Thread clientThread = new Thread(HandleClient)
{
IsBackground = true
};
clientThread.Start(client);
Console.WriteLine($"客户端连接:{client.Client.RemoteEndPoint}");
}
catch (SocketException)
{
// 服务器停止时会触发
break;
}
}
}
private void HandleClient(object obj)
{
TcpClient client = (TcpClient)obj;
NetworkStream stream = client.GetStream();
byte[] buffer = new byte[260]; // Modbus TCP最大ADULength
try
{
while (_isRunning && client.Connected)
{
int bytesRead = stream.Read(buffer, 0, buffer.Length);
if (bytesRead == 0) break; // 客户端断开
// ParseMBAP Head
ushort transId = (ushort)((buffer[0] << 8) | buffer[1]);
ushort protoId = (ushort)((buffer[2] << 8) | buffer[3]);
ushort length = (ushort)((buffer[4] << 8) | buffer[5]);
byte unitId = buffer[6];
byte funcCode = buffer[7];
// 处理请求并生成响应
byte[] response = ProcessRequest(buffer, bytesRead, transId, unitId, funcCode);
// 发送响应
stream.Write(response, 0, response.Length);
stream.Flush();
}
}
catch (Exception ex)
{
Console.WriteLine($"客户端异常:{ex.Message}");
}
finally
{
client.Close();
lock (_clients) { _clients.Remove(client); }
Console.WriteLine($"客户端断开:{client.Client.RemoteEndPoint}");
}
}
private byte[] ProcessRequest(byte[] request, int length,
ushort transId, byte unitId, byte funcCode)
{
try
{
switch (funcCode)
{
case 0x01: return ReadCoils(request, transId, unitId);
case 0x02: return ReadDiscreteInputs(request, transId, unitId);
case 0x03: return ReadHolding회원가입s(request, transId, unitId);
case 0x04: return ReadInput회원가입s(request, transId, unitId);
case 0x05: return WriteSingleCoil(request, transId, unitId);
case 0x06: return WriteSingle회원가입(request, transId, unitId);
case 0x0F: return WriteMultipleCoils(request, transId, unitId);
case 0x10: return WriteMultiple회원가입s(request, transId, unitId);
default:
return BuildExceptionResponse(transId, unitId, funcCode, 0x01);
}
}
catch (IndexOutOfRangeException)
{
return BuildExceptionResponse(transId, unitId, funcCode, 0x02);
}
catch (Exception)
{
return BuildExceptionResponse(transId, unitId, funcCode, 0x04);
}
}
// function code01:读线圈
private byte[] ReadCoils(byte[] req, ushort transId, byte unitId)
{
ushort startAddr = (ushort)((req[8] << 8) | req[9]);
ushort quantity = (ushort)((req[10] << 8) | req[11]);
bool[] coils = _dataStore.GetCoils(startAddr, quantity);
byte byteCount = (byte)((quantity + 7) / 8);
byte[] response = new byte[9 + byteCount];
BuildMBAPHeader(response, transId, unitId, (ushort)(3 + byteCount));
response[7] = 0x01;
response[8] = byteCount;
for (int i = 0; i < quantity; i++)
{
if (coils[i])
response[9 + i / 8] |= (byte)(1 << (i % 8));
}
return response;
}
// function code03:read holding registers
private byte[] ReadHolding회원가입s(byte[] req, ushort transId, byte unitId)
{
ushort startAddr = (ushort)((req[8] << 8) | req[9]);
ushort quantity = (ushort)((req[10] << 8) | req[11]);
ushort[] regs = _dataStore.GetHoldingRegs(startAddr, quantity);
byte byteCount = (byte)(quantity * 2);
byte[] response = new byte[9 + byteCount];
BuildMBAPHeader(response, transId, unitId, (ushort)(3 + byteCount));
response[7] = 0x03;
response[8] = byteCount;
for (int i = 0; i < quantity; i++)
{
response[9 + i * 2] = (byte)(regs[i] >> 8);
response[9 + i * 2 + 1] = (byte)(regs[i] & 0xFF);
}
return response;
}
// function code05:write single coil
private byte[] WriteSingleCoil(byte[] req, ushort transId, byte unitId)
{
ushort addr = (ushort)((req[8] << 8) | req[9]);
ushort value = (ushort)((req[10] << 8) | req[11]);
_dataStore.SetCoil(addr, value == 0xFF00);
// 回显请求
byte[] response = new byte[12];
BuildMBAPHeader(response, transId, unitId, 6);
Array.Copy(req, 7, response, 7, 5);
return response;
}
// function code06:write single register
private byte[] WriteSingle회원가입(byte[] req, ushort transId, byte unitId)
{
ushort addr = (ushort)((req[8] << 8) | req[9]);
ushort value = (ushort)((req[10] << 8) | req[11]);
_dataStore.SetHoldingReg(addr, value);
byte[] response = new byte[12];
BuildMBAPHeader(response, transId, unitId, 6);
Array.Copy(req, 7, response, 7, 5);
return response;
}
// function code10:write multiple registers
private byte[] WriteMultiple회원가입s(byte[] req, ushort transId, byte unitId)
{
ushort startAddr = (ushort)((req[8] << 8) | req[9]);
ushort quantity = (ushort)((req[10] << 8) | req[11]);
ushort[] values = new ushort[quantity];
for (int i = 0; i < quantity; i++)
{
values[i] = (ushort)((req[13 + i * 2] << 8) | req[13 + i * 2 + 1]);
}
_dataStore.SetHoldingRegs(startAddr, values);
byte[] response = new byte[12];
BuildMBAPHeader(response, transId, unitId, 6);
response[7] = 0x10;
response[8] = req[8];
response[9] = req[9];
response[10] = req[10];
response[11] = req[11];
return response;
}
// 构建MBAP Head
private void BuildMBAPHeader(byte[] buffer, ushort transId, byte unitId, ushort length)
{
buffer[0] = (byte)(transId >> 8);
buffer[1] = (byte)(transId & 0xFF);
buffer[2] = 0x00; // ProtocolID高
buffer[3] = 0x00; // ProtocolID低
buffer[4] = (byte)(length >> 8);
buffer[5] = (byte)(length & 0xFF);
buffer[6] = unitId;
}
// 构建异常响应
private byte[] BuildExceptionResponse(ushort transId, byte unitId,
byte funcCode, byte exceptionCode)
{
byte[] response = new byte[9];
BuildMBAPHeader(response, transId, unitId, 3);
response[7] = (byte)(funcCode | 0x80);
response[8] = exceptionCode;
return response;
}
}
}
三、Modbus TCP客户端实现
using System;
using System.Net.Sockets;
namespace ModbusTcpClient
{
public class ModbusTcpClient : IDisposable
{
private TcpClient _client;
private NetworkStream _stream;
private ushort _transactionId = 0;
public string Host { get; }
public int Port { get; }
public byte UnitId { get; set; } = 1;
public int Timeout { get; set; } = 3000;
public ModbusTcpClient(string host, int port = 502)
{
Host = host;
Port = port;
}
public void Connect()
{
_client = new TcpClient();
_client.ReceiveTimeout = Timeout;
_client.SendTimeout = Timeout;
_client.Connect(Host, Port);
_stream = _client.GetStream();
Console.WriteLine($"已连接到 {Host}:{Port}");
}
public void Disconnect()
{
_stream?.Close();
_client?.Close();
}
// read holding registers(function code03)
public ushort[] ReadHolding회원가입s(ushort startAddr, ushort quantity)
{
byte[] request = BuildReadRequest(0x03, startAddr, quantity);
byte[] response = SendAndReceive(request);
if (response[7] != 0x03)
throw new ModbusException(response[7], response[8]);
byte byteCount = response[8];
ushort[] result = new ushort[byteCount / 2];
for (int i = 0; i < result.Length; i++)
{
result[i] = (ushort)((response[9 + i * 2] << 8) | response[9 + i * 2 + 1]);
}
return result;
}
// 读线圈(function code01)
public bool[] ReadCoils(ushort startAddr, ushort quantity)
{
byte[] request = BuildReadRequest(0x01, startAddr, quantity);
byte[] response = SendAndReceive(request);
if (response[7] != 0x01)
throw new ModbusException(response[7], response[8]);
byte byteCount = response[8];
bool[] result = new bool[quantity];
for (int i = 0; i < quantity; i++)
{
result[i] = (response[9 + i / 8] & (1 << (i % 8))) != 0;
}
return result;
}
// write single register(function code06)
public void WriteSingle회원가입(ushort addr, ushort value)
{
byte[] request = new byte[12];
BuildMBAPHeader(request, 6);
request[7] = 0x06;
request[8] = (byte)(addr >> 8);
request[9] = (byte)(addr & 0xFF);
request[10] = (byte)(value >> 8);
request[11] = (byte)(value & 0xFF);
byte[] response = SendAndReceive(request);
if (response[7] != 0x06)
throw new ModbusException(response[7], response[8]);
}
// write single coil(function code05)
public void WriteSingleCoil(ushort addr, bool value)
{
byte[] request = new byte[12];
BuildMBAPHeader(request, 6);
request[7] = 0x05;
request[8] = (byte)(addr >> 8);
request[9] = (byte)(addr & 0xFF);
request[10] = (byte)(value ? 0xFF : 0x00);
request[11] = 0x00;
byte[] response = SendAndReceive(request);
if (response[7] != 0x05)
throw new ModbusException(response[7], response[8]);
}
// write multiple registers(function code10)
public void WriteMultiple회원가입s(ushort startAddr, ushort[] values)
{
byte byteCount = (byte)(values.Length * 2);
byte[] request = new byte[13 + byteCount];
BuildMBAPHeader(request, (ushort)(7 + byteCount));
request[7] = 0x10;
request[8] = (byte)(startAddr >> 8);
request[9] = (byte)(startAddr & 0xFF);
request[10] = (byte)(values.Length >> 8);
request[11] = (byte)(values.Length & 0xFF);
request[12] = byteCount;
for (int i = 0; i < values.Length; i++)
{
request[13 + i * 2] = (byte)(values[i] >> 8);
request[13 + i * 2 + 1] = (byte)(values[i] & 0xFF);
}
byte[] response = SendAndReceive(request);
if (response[7] != 0x10)
throw new ModbusException(response[7], response[8]);
}
private byte[] BuildReadRequest(byte funcCode, ushort startAddr, ushort quantity)
{
byte[] request = new byte[12];
BuildMBAPHeader(request, 6);
request[7] = funcCode;
request[8] = (byte)(startAddr >> 8);
request[9] = (byte)(startAddr & 0xFF);
request[10] = (byte)(quantity >> 8);
request[11] = (byte)(quantity & 0xFF);
return request;
}
private void BuildMBAPHeader(byte[] buffer, ushort length)
{
_transactionId++;
buffer[0] = (byte)(_transactionId >> 8);
buffer[1] = (byte)(_transactionId & 0xFF);
buffer[2] = 0x00;
buffer[3] = 0x00;
buffer[4] = (byte)(length >> 8);
buffer[5] = (byte)(length & 0xFF);
buffer[6] = UnitId;
}
private byte[] SendAndReceive(byte[] request)
{
_stream.Write(request, 0, request.Length);
_stream.Flush();
byte[] buffer = new byte[260];
int bytesRead = _stream.Read(buffer, 0, buffer.Length);
byte[] response = new byte[bytesRead];
Array.Copy(buffer, response, bytesRead);
return response;
}
public void Dispose()
{
Disconnect();
}
}
public class ModbusException : Exception
{
public byte FunctionCode { get; }
public byte ExceptionCode { get; }
public ModbusException(byte funcCode, byte exceptionCode)
: base($"Modbus异常:function code=0x{funcCode:X2},Exception Code=0x{exceptionCode:X2}")
{
FunctionCode = funcCode;
ExceptionCode = exceptionCode;
}
}
}
四、完整使用示例
using System;
using System.Threading;
namespace ModbusTcpDemo
{
class Program
{
static void Main(string[] args)
{
// 启动服务器
var server = new ModbusTcpServer.ModbusTcpServer(502);
server.Start();
// 设置一些初始数据
server.DataStore.SetHoldingReg(0, 100);
server.DataStore.SetHoldingReg(1, 200);
server.DataStore.SetHoldingReg(2, 300);
server.DataStore.SetCoil(0, true);
server.DataStore.SetInputReg(0, 512);
// 等待服务器启动
Thread.Sleep(500);
// 创建客户端
using (var client = new ModbusTcpClient.ModbusTcpClient("127.0.0.1", 502))
{
client.Connect();
client.UnitId = 1;
// 1. read holding registers
Console.WriteLine("=== read holding registers ===");
ushort[] regs = client.ReadHolding회원가입s(0, 5);
for (int i = 0; i < regs.Length; i++)
Console.WriteLine($"register{i}:{regs[i]}");
// 2. write single register
Console.WriteLine("\n=== write single register ===");
client.WriteSingle회원가입(3, 999);
ushort[] check = client.ReadHolding회원가입s(3, 1);
Console.WriteLine($"写入后读取:{check[0]}");
// 3. 读线圈
Console.WriteLine("\n=== 读线圈 ===");
bool[] coils = client.ReadCoils(0, 4);
for (int i = 0; i < coils.Length; i++)
Console.WriteLine($"线圈{i}:{(coils[i] ? "ON" : "OFF")}");
// 4. 写线圈
Console.WriteLine("\n=== 写线圈 ===");
client.WriteSingleCoil(1, true);
coils = client.ReadCoils(1, 1);
Console.WriteLine($"线圈1:{(coils[0] ? "ON" : "OFF")}");
// 5. 批量写寄存器
Console.WriteLine("\n=== 批量写寄存器 ===");
client.WriteMultiple회원가입s(10, new ushort[] { 11, 22, 33, 44 });
regs = client.ReadHolding회원가입s(10, 4);
for (int i = 0; i < regs.Length; i++)
Console.WriteLine($"register{10 + i}:{regs[i]}");
}
Console.WriteLine("\n按任意键退出...");
Console.ReadKey();
server.Stop();
}
}
}
五、关键技术说明
5.1 MBAP Head结构
Modbus TCP使用MBAP(Modbus Application Protocol)头替代RTU的从站地址和CRC。MBAP Head共7字节:Transaction identifier(2字节)、Protocol Identifier(2字节,固定0x0000)、Length(2字节)、Unit identifier(1字节)。事务ID用于匹配请求和响应,服务器必须原样返回。
5.2 多客户端处理
服务器使用TcpListener.AcceptTcpClient()接受连接,每个客户端分配独立线程处理。数据存储类使用lock实现线程安全,确保多客户端同时访问时数据一致性。
5.3 异常处理
Mod bus 예외 응답 의 가장 높은 기능 코 드는 1 (원 래 기능 코드 + 0 x 8 0) 이며 1 바 이트 예외 코 드가 뒤 따 릅니다 .일반적인 예외 코드 : 0 x 01 불법 기능 코드 , 0 x 02 불법 주소 , 0 x 03 불법 데이터 값 , 0 x 04 슬 레이 브 장치 장애 .
6. 테스트 및 검 증
- 使用Modbus Poll测试
- 使用Modbus Slave模拟
- Wireshark抓包
- 压力测试
VII . 확장 제안
- 异步模式
- 日志记录
- 配置文件
- 数据持久化
- Modbus RTU网关
- 心跳检测
- 权限控制
이 문서 에서 제공하는 C # Mod bus TCP 서버 및 클 라이언 트 코 드는 산업 자동 화 프로젝트 개발 에 직접 사용할 수있는 완전한 표준 기능 코드를 구현 합니다 .서버 는 다 중 클 라이언 트 동 시 액세 스를 지원 하며 클 라이언 트는 간단한 API 인터페 이 스를 제공합니다 .로 컬 백 루 프 주소 (12 7. 0. 0. 1) 에서 테스트 를 통과 한 다음 실제 장치를 연결 하여 검 증을 하는 것이 좋습니다 .
Leave a Reply