WebSocket传递ModbusTCP数据包
- 错误纠正
上一篇还有个错误,就是客户端写数据时服务端不需要响应,但我的服务端响应了的。我选择改客户端,把写数据时接收到的响应丢弃。
PrintBytes(ADUMessage.Serialze(request), "请求");
if (Client != null)
{
await Client.Client.SendAsync(new Memory<byte>(ADUMessage.Serialze(request)));
//丢弃可能的响应
await WebSocket.ReceiveAsync(new ArraySegment<byte>(new byte[1024*4]), CancellationToken.None);
}
现在我们同时有了服务器和客户端,就可以在tcp连接上面覆盖一个websocket连接,然后用WebSocket传递Modbus数据帧。
效果
这是基于WebSocket
连接的modbus通信,读写都没问题
- 服务器
- 客户端
主程序改造
我们的服务器和客户端可以*选择使用TCP
或者WebSocket
通信,所以
- 需要提供对应的命令行参数
- 根据参数选择
TCP
或者WebSocket
通信
首先是命令行参数
参数设计如下
- 服务器: tcp|websocket 服务器端口
- 客户端: tcp|websocket 客户端端口 服务器端口
static void Main(string[] args)
{
webModbusServer = new WebModbusServer();
//服务器
if (args.Length == 2)
{
if (args[0]=="tcp")
{
StartTcpServer(args[1]);
}
else if(args[0] == "websocket")
{
StartWebSocketServer(args[1]);
}
}
//客户端
else
{
if (args[0] == "tcp")
{
Task.Run(async () =>
{
await StartClient(args);
}).Wait();
}
else if (args[0] == "websocket")
{
Task.Run(async () =>
{
await StartWebsocketClient(args);
}).Wait();
}
}
}
然后就是实现StartTcpServer
,StartWebSocketServer
,StartClient
,StartWebsocketClient
这四个方法。
具体实现比较繁琐,我就放到最后的完整代码里面了。
串口传递ModbusTCP数据包
不同于网络通信的7层协议或者TCP/IP协议族为我们所熟悉。串口通信是如何进行的?也是分层的吗?串口通信与网络通信能融合吗?这个我们比较陌生。
串口通信和网络通信(例如通过以太网进行的网络通信)在其基本原理和工作方式上有一些显著的区别。
串口通信与网络通信比较
-
物理介质:
- 串口通信:通常通过物理导线(例如串口线)直接连接两个设备进行通信,例如 RS-232、RS-485、USB 等串口标准。
串口通信通常通过导线进行,但也可以通过其他媒介进行,如光纤或无线电波。就是插一个转换器到串口接口上,比如串口到光纤转换器。串口到无线电波转换器。我们平时经常用到的就有USB转WIFI、USB转4G。 - 网络通信:通过各种不同的物理介质进行,如以太网使用的双绞线、光纤、无线电波等。
- 串口通信:通常通过物理导线(例如串口线)直接连接两个设备进行通信,例如 RS-232、RS-485、USB 等串口标准。
-
协议栈和分层:
- 串口通信:虽然串口通信也可以分层,但通常它的分层结构较简单,主要包含物理层和数据链路层。常见的串口通信协议如
物理层 传输介质 接口 数据链路层 RS-232 串口电缆 9 针 D-Sub Modbus、CAN,自定义协议 传输距离相对较短,通常为数米 RS-422 两对绝缘的双绞线 9 针 D-Sub Modbus、CAN,自定义协议 通常可达几百米的距离 RS-485 双绞线 9 针或者 15 针 D-Sub Modbus、CAN,自定义协议 传输距离可达数百至数千米 - 网络通信:基于 TCP/IP 协议族的网络通信通常遵循 OSI 模型的七层协议结构,包括物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。每一层都有特定的功能和协议,如物理层负责传输比特流,网络层负责数据包的路由等。
相应的网络通信的标准:物理层 传输介质 数据链路层 10BASE-T 双绞线 IEEE 802.3 以太网 10BASE2 同轴电缆 IEEE 802.3 以太网 802.11a系列 无线电 IEEE 802.11 MAC WIFI LTE 无线电 LTE MAC 4G 1000BASE-SX 光纤 IEEE 802.3 以太网 - 串口通信只是物理层和数据链路层的标准,同样可以上层架设TCP/IP协议栈,实现远程监控、远程控制等功能。
- 串口通信:虽然串口通信也可以分层,但通常它的分层结构较简单,主要包含物理层和数据链路层。常见的串口通信协议如
-
速率和带宽:
- 串口通信:通常速率较低,受限于物理介质和串口协议的限制,不适合大量数据的高速传输。
- 网络通信:具有较高的传输速率和带宽,能够支持大规模数据的传输。
在程序中使用串口
就像网络通讯我们不用管TCP及以下的协议层一样,串口通信物理层不用管,操作系统已经帮我们搞定了。
我们只需要写数据链路层就行了。
使用System.IO.Ports.SerialPort
这个类就行了。
但有个问题是串口只负责发送接收比特流组织成字节放在缓冲区,至于里面是什么意思,我们一概不知。
在串口上覆盖ModbusTCP协议作为数据链路层实现复杂通信
为了减少代码,这里就由我手搓的ModbusTcp服务器和客户端来测试,但是串口的物理层只提供了数据编码、每个字符的数据校验的能力,不提供封装成帧的能力。
而ModbusTcp原本设计是在tcp上面使用,tcp及其下面的层已经提供了这些功能,所以ModbusTcp本身不提供封装成帧。
我们会面临看到数据来了就跑去接收,结果数据才接收了一半这些问题。
我们有两种选择。
- 写一个封装成帧、透明传输的协议作为数据链路层在物理层之上,然后把ModbusTcp作为第三层的协议
- 使用
ModbusRTU
协议作为数据链路层。
因为我们要使用ModbusTCP,所以就选第一种。
串口数据链路层
现成的是没有的,我们只好自己再来搓一个数据链路层。把一个ModbusTcp数据帧接收完了再交给上层处理。
- 这个协议应该有一个数据帧栈。
- 这一层不断读串口缓冲区,完整读出来一个帧后,就添加到数据帧栈中。
- 读下一个帧。
帧格式
帧开始符 | (modbustcp)数据 | 帧结束符 |
---|---|---|
SOH(0x01) | bytes | EOT(0x04) |
- 不透明的帧
帧开始符 (modbustcp)数据 帧结束符 SOH(0x01) ESC bytes SOH bytes EOT bytes EOT(0x04) - 透明的帧
用ESC来进行字节填充解决透明传输帧开始符 (modbustcp)数据 帧结束符 SOH(0x01) ESC ESC bytes ESC SOH bytes ESC EOT bytes EOT(0x04)
数据链路层实现
数据链路层的实现较为复杂,主要实现了透明传输、封装成帧。
其中有一个难点是在没有获取到数据帧时等待,但有数据帧到来后又要完成这个等待任务。
这就用到了TaskCompletionSource对象
//没有计算完成时等待
await dataReceived.Task;
//触发完成计算
dataReceived.TrySetResult(true);
使用时直接传入串口号创建一个数据链路层对象,然后阻塞调用他的发送数据和接收数据方法
//创建数据链路层对象
SerialCommunication serialComm = new SerialCommunication("COM1", 9600);
//开启modbustcp服务器
StartCommModbus(serialComm);
...
//在开启服务器里面
public static async Task StartCommModbus(SerialCommunication serialComm)
{
while (serialComm.isOpen)
{
// 接收数据
byte[] buffer = await serialComm.ReceiveDataAsync();
if (buffer.Length > 0)
{
PrintBytes(buffer, "请求 ");
ADUMessage response = webModbusServer.HandleRequest(buffer);
// 发送数据
await serialComm.SendDataAsync(ADUMessage.Serialze(response));
PrintBytes(ADUMessage.Serialze(response), "响应 ");
}
}
}
之后还要在主程序中添加一个使用串口的分支,以便我们指定使用那种方式传输数据。
效果
-
客户端
-
服务端
完整代码
SerialCommunication.cs
public class SerialCommunication
{
private const byte SOH = 0x01; // 起始标志
private const byte EOT = 0x04; // 结束标志
private const byte ESC = 0x1B; // 透明填充
private SerialPort serialPort;
public bool isOpen;
private Stack<byte[]> frames = new Stack<byte[]>();
private object lockObject = new object();
private TaskCompletionSource<bool> dataReceived = new TaskCompletionSource<bool>();
public SerialCommunication(string portName, int baudRate)
{
isOpen = false;
frames = new Stack<byte[]>();
serialPort = new SerialPort(portName, baudRate);
serialPort.Open();
isOpen = true;
readData();
}
private void readData()
{
Task.Run(() =>
{
byte[] frame = new byte[100]; // 假设最大帧长度为100字节
int index = -1;
while (true)
{
int rs= serialPort.BaseStream.ReadByte();
if (rs == -1)
{
index = -1;
continue;
}
byte b = (byte)rs;
if (b == SOH) // 如果读到起始标志
{
if (index>0 && frame[index-1]== ESC)
{
index--;
frame[index] = SOH;
index++;
}
else
{
index = 0;
frame[index] = b;
index++;
}
}
else if (b==ESC)
{
if (index==-1)
{
//丢弃byte
continue;
}
else if (index > 0 && frame[index - 1] == ESC)
{
continue;
}
else
{
frame[index] = b;
index++;
}
}
else if (b == EOT) // 如果读到结束标志
{
if (index == -1)
{
//丢弃byte
continue;
}
else if (index>0 && frame[index - 1] == ESC)
{
index--;
frame[index] = EOT;
index++;
}
else
{
frame[index] = EOT;
index++;
byte[] data = ParseFrame(frame, index);
if (data != null)
{
lock (lockObject)
{
frames.Push(data);
dataReceived.TrySetResult(true);
}
index = -1;
}
}
}
else
{
if (index==-1)
{
//丢弃byte
continue;
}
else
{
frame[index] = b;
index++;
}
}
}
});
}
// 发送数据
public async Task SendDataAsync(byte[] data)
{
byte[] frame = EncapsulateFrame(data);
await serialPort.BaseStream.WriteAsync(frame, 0, frame.Length);
}
// 接收数据
public async Task<byte[]> ReceiveDataAsync()
{
byte[] frame;
lock (lockObject)
{
if (frames.Count > 0)
{
frame = frames.Pop();
return frame;
}
}
// 没有数据时等待
bool rs = await dataReceived.Task;
frame = frames.Pop();
lock (lockObject)
{
dataReceived = new TaskCompletionSource<bool>();
}
return frame;
}
// 封装数据帧
private byte[] EncapsulateFrame(byte[] data)
{
byte[] frame = new byte[data.Length + 3];
frame[0] = SOH; // 添加起始标志
Array.Copy(data, 0, frame, 1, data.Length); // 添加数据内容
byte checksum = CalculateChecksum(data); // 计算校验字段
frame[data.Length + 1] = checksum; // 添加校验字段
frame[data.Length + 2] = EOT; // 添加结束标志
//透明传输处理
using (MemoryStream ms=new MemoryStream())
{
ms.Write(frame, 0, 1);
for (global::System.Int32 i = 1; i < frame.Length-1; i++)
{
if (frame[i]==SOH || frame[i] == ESC || frame[i] == EOT)
{
ms.Write(new byte[2] { ESC, frame[i] });
}
else
{
ms.Write(new byte[1] { frame[i] });
}
}
ms.Write(frame, (int)frame.Length-1, 1);
byte[] bytes = ms.ToArray();
//PrintBytes(bytes, "透明传输");
return bytes;
}
}
// 解析数据帧
private byte[] ParseFrame(byte[] frame, int length)
{
byte checksum = frame[length - 2];
byte[] data = new byte[length - 3];
Array.Copy(frame, 1, data, 0, length - 3);
if (CalculateChecksum(data) == checksum)
{
return data;
}
return null;
}
// 计算校验字段(简单求和校验)
private byte CalculateChecksum(byte[] data)
{
int sum = 0;
foreach (byte b in data)
{
sum += b;
}
return (byte)(sum % 256);
}
// 关闭串口
public void Close()
{
isOpen = false;
serialPort.Close();
}
public static void PrintBytes(byte[] bytes, string prefix = "")
{
Console.Write(prefix);
for (int i = 0; i < bytes.Length; i++)
{
if (i < 2)
{
Console.ForegroundColor = ConsoleColor.Red;
}
else if (i < 4)
{
Console.ForegroundColor = ConsoleColor.Green;
}
else if (i < 6)
{
Console.ForegroundColor = ConsoleColor.Blue;
}
else if (i < 7)
{
Console.ForegroundColor = ConsoleColor.Yellow;
}
else if (i < 8)
{
Console.ForegroundColor = ConsoleColor.DarkCyan;
}
else
{
Console.ForegroundColor = ConsoleColor.White;
}
Console.Write(bytes[i].ToString("X2") + " ");
}
Console.WriteLine();
}
}
WebModbus.cs
/// <summary>
/// 数据仓库,144KB
/// </summary>
public class DataStore
{
/// <summary>
/// 读写16位寄存器,64KB
/// </summary>
public ushort[] HoldingRegisters;
/// <summary>
/// 只读16位寄存器,64KB
/// </summary>
public ushort[] InputRegisters;
/// <summary>
/// 读写1位线圈,8KB
/// </summary>
public bool[] CoilDiscretes;
/// <summary>
/// 只读1位线圈,8KB
/// </summary>
public bool[] CoilInputs;
public DataStore()
{
HoldingRegisters = new ushort[65536];
InputRegisters = new ushort[65536];
CoilDiscretes = new bool[65536];
CoilInputs = new bool[65536];
}
/// <summary>
/// 读 读写16位寄存器
/// </summary>
/// <param name="startIndex"></param>
/// <param name="length"></param>
/// <returns></returns>
public ushort[] ReadHoldingRegisters(ushort startIndex, ushort length)
{
return HoldingRegisters.Take(new Range(new Index(startIndex), new Index(startIndex + length))).ToArray();
}
/// <summary>
/// 读 只读16位寄存器
/// </summary>
/// <param name="startIndex"></param>
/// <param name="length"></param>
/// <returns></returns>
public ushort[] ReadInputRegisters(ushort startIndex, ushort length)
{
return InputRegisters.Take(new Range(new Index(startIndex), new Index(startIndex + length))).ToArray();
}
/// <summary>
/// 读 读写1位线圈
/// </summary>
/// <param name="startIndex"></param>
/// <param name="length"></param>
/// <returns></returns>
public bool[] ReadCoilDiscretes(ushort startIndex, ushort length)
{
return CoilDiscretes.Take(new Range(new Index(startIndex), new Index(startIndex + length))).ToArray();
}
/// <summary>
/// 读 只读1位线圈
/// </summary>
/// <param name="startIndex"></param>
/// <param name="length"></param>
/// <returns></returns>
public bool[] ReadCoilInputs(ushort startIndex, ushort length)
{
return CoilInputs.Take(new Range(new Index(startIndex), new Index(startIndex + length))).ToArray();
}
/// <summary>
/// 写 读写16位寄存器
/// </summary>
/// <param name="startIndex"></param>
/// <param name="data"></param>
public void WriteHoldingRegisters(ushort startIndex, ushort[] data)
{
for (int i = 0; i < data.Length; i++)
{
if (startIndex+i < 65536)
{
HoldingRegisters[startIndex + i] = data[i];
}
}
}
/// <summary>
/// 写 读写1位线圈
/// </summary>
/// <param name="startIndex"></param>
/// <param name="data"></param>
public void WriteCoilDiscretes(ushort startIndex, bool[] data)
{
for (int i = 0; i < data.Length; i++)
{
if (startIndex + i < 65536)
{
CoilDiscretes[startIndex + i] = data[i];
}
}
}
}
/// <summary>
/// Modbus报文
/// </summary>
public class ADUMessage
{
/// <summary>
/// 事务标识符
/// </summary>
public ushort Transaction { get; set; }
/// <summary>
/// 协议标识符
/// </summary>
public ushort Protocol { get; set; }
/// <summary>
/// 报文长度
/// </summary>
public ushort Length { get; set; }
/// <summary>
/// 单元标识符
/// </summary>
public byte Unit { get; set; }
/// <summary>
/// 功能码
/// </summary>
public byte FunctionCode { get; set; }
/// <summary>
/// 数据
/// </summary>
public byte[] Data { get; set; }
public static ADUMessage Deserialize(byte[] buffer)
{
//BinaryReader读取方式是小端(右边是高字节),而modbus是大端传输(左边是高字节)
BinaryReader reader = new BigEndianBinaryReader(new MemoryStream(buffer));
ADUMessage adu = new ADUMessage()
{
Transaction = reader.ReadUInt16(),
Protocol = reader.ReadUInt16(),
Length = reader.ReadUInt16(),
Unit = reader.ReadByte(),
FunctionCode = reader.ReadByte(),
Data = reader.ReadBytes(buffer.Length - 8)
};
return adu;
}
public static byte[] Serialze(ADUMessage message)
{
using (MemoryStream ms=new MemoryStream())
{
BinaryWriter writer = new BigEndianBinaryWriter(ms);
writer.Write(message.Transaction);
writer.Write(message.Protocol);
writer.Write(message.Length);
writer.Write(message.Unit);
writer.Write(message.FunctionCode);
writer.Write(message.Data);
return ms.ToArray();
}
}
}
/// <summary>
/// Modbus服务器
/// </summary>
public class WebModbusServer
{
public DataStore store = new DataStore();
public ADUMessage HandleRequest(byte[] buffer)
{
ADUMessage request = ADUMessage.Deserialize(buffer);
switch (request.FunctionCode)
{
//读 读写线圈
case 0x01:
return Response_01(request);
//读 只读线圈
case 0x02:
return Response_02(request);
//读 读写寄存器
case 0x03:
return Response_03(request);
//读 只读寄存器
case 0x04:
return Response_04(request);
//写 读写一个线圈
case 0x05:
return Response_05(request);
//写 读写一个寄存器
case 0x06:
return Response_06(request);
//写 读写多个线圈
case 0x0f:
return Response_0f(request);
//写 读写多个寄存器
case 0x10:
return Response_10(request);
default:
return Response_01(request);
}
}
public byte[] CoilToBytes(bool[] bools)
{
int byteCount = (bools.Length + 7) / 8; // 计算所需的字节数
byte[] bytes = new byte[byteCount];
for (int i = 0; i < bools.Length; i++)
{
int byteIndex = i / 8; // 计算当前布尔值应该存储在哪个字节中
int bitIndex = i % 8; // 计算当前布尔值应该存储在字节的哪个位上
if (bools[i])
{
// 设置对应位为 1
bytes[byteIndex] |= (byte)(1 << bitIndex);
}
else
{
// 对应位保持为 0,无需额外操作
}
}
return bytes;
}
/// <summary>
/// 读 读写线圈
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
private ADUMessage Response_01(ADUMessage request)
{
BinaryReader reader = new BigEndianBinaryReader(new MemoryStream(request.Data));
BinaryWriter writer;
ushort StartAddress, DataNumber;
StartAddress = reader.ReadUInt16();
DataNumber = reader.ReadUInt16();
bool[] data = store.ReadCoilDiscretes(StartAddress, DataNumber);
byte[] coilBytes = CoilToBytes(data);
byte[] dataBytes = new byte[coilBytes.Length + 1];
writer = new BinaryWriter(new MemoryStream(dataBytes));
writer.Write((byte)coilBytes.Length);
writer.Write(coilBytes);
ADUMessage response = new ADUMessage()
{
Transaction = request.Transaction,
Protocol = request.Protocol,
Length = (ushort)(dataBytes.Length + 2),
Unit = request.Unit,
FunctionCode = request.FunctionCode,
Data = dataBytes,
};
return response;
}
/// <summary>
/// 读 只读线圈
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
private ADUMessage Response_02(ADUMessage request)
{
BinaryReader reader = new BigEndianBinaryReader(new MemoryStream(request.Data));
BinaryWriter writer;
ushort StartAddress, DataNumber;
StartAddress = reader.ReadUInt16();
DataNumber = reader.ReadUInt16();
bool[] data = store.ReadCoilInputs(StartAddress, DataNumber);
byte[] coilBytes = CoilToBytes(data);
byte[] dataBytes = new byte[coilBytes.Length + 1];
writer = new BinaryWriter(new MemoryStream(dataBytes));
writer.Write((byte)coilBytes.Length);
writer.Write(coilBytes);
ADUMessage response = new ADUMessage()
{
Transaction = request.Transaction,
Protocol = request.Protocol,
Length = (ushort)(dataBytes.Length + 2),
Unit = request.Unit,
FunctionCode = request.FunctionCode,
Data = dataBytes,
};
return response;
}
/// <summary>
/// 读 读写寄存器
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
private ADUMessage Response_03(ADUMessage request)
{
BinaryReader reader = new BigEndianBinaryReader(new MemoryStream(request.Data));
BinaryWriter writer;
ushort StartAddress, DataNumber;
StartAddress = reader.ReadUInt16();
DataNumber = reader.ReadUInt16();
ushort[] data = store.ReadHoldingRegisters(StartAddress, DataNumber);
byte[] dataBytes = new byte[data.Length * 2 + 1];
writer = new BigEndianBinaryWriter(new MemoryStream(dataBytes));
writer.Write((byte)(data.Length * 2));
foreach (ushort value in data)
{
writer.Write(value);
}
Array.Resize(ref dataBytes, dataBytes.Length + 1);
ADUMessage response = new ADUMessage()
{
Transaction = request.Transaction,
Protocol = request.Protocol,
Length = (ushort)(dataBytes.Length + 2),
Unit = request.Unit,
FunctionCode = request.FunctionCode,
Data = dataBytes,
};
return response;
}
/// <summary>
/// 读 只读寄存器
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
private ADUMessage Response_04(ADUMessage request)
{
BinaryReader reader = new BigEndianBinaryReader(new MemoryStream(request.Data));
BinaryWriter writer;
ushort StartAddress, DataNumber;
StartAddress = reader.ReadUInt16();
DataNumber = reader.ReadUInt16();
ushort[] data = store.ReadInputRegisters(StartAddress, DataNumber);
byte[] dataBytes = new byte[data.Length * 2 + 1];
writer = new BigEndianBinaryWriter(new MemoryStream(dataBytes));
writer.Write((byte)(data.Length * 2));
foreach (ushort value in data)
{
writer.Write(value);
}
Array.Resize(ref dataBytes, dataBytes.Length + 1);
ADUMessage response = new ADUMessage()
{
Transaction = request.Transaction,
Protocol = request.Protocol,
Length = (ushort)(dataBytes.Length + 2),
Unit = request.Unit,
FunctionCode = request.FunctionCode,
Data = dataBytes,
};
return response;
}
/// <summary>
/// 写 读写一个线圈
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
private ADUMessage Response_05(ADUMessage request)
{
BinaryReader reader = new BigEndianBinaryReader(new MemoryStream(request.Data));
ushort StartAddress, coli;
StartAddress = reader.ReadUInt16();
coli = reader.ReadUInt16();
store.WriteCoilDiscretes(StartAddress, new bool[] { coli ==0xff00?true:false});
return request;
}
/// <summary>
/// 写 读写一个寄存器
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
private ADUMessage Response_06(ADUMessage request)
{
BinaryReader reader = new BigEndianBinaryReader(new MemoryStream(request.Data));
ushort StartAddress, register;
StartAddress = reader.ReadUInt16();
register = reader.ReadUInt16();
store.WriteHoldingRegisters(StartAddress, new ushort[] { register });
return request;
}
/// <summary>
/// 写 读写多个线圈
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
private ADUMessage Response_0f(ADUMessage request)
{
BinaryReader reader = new BigEndianBinaryReader(new MemoryStream(request.Data));
ushort StartAddress, DataNumber;
StartAddress = reader.ReadUInt16();
DataNumber = reader.ReadUInt16();
byte byteNumber = reader.ReadByte();
//线圈是小端传输
byte[] bytes = reader.ReadBytes(byteNumber);
bool[] data=new bool[DataNumber];
byte index = 0;
foreach (var item in bytes)
{
//1000 0000
byte rr = (byte)0x01;
for (int i = 0; i < 8; i++)
{
if (index< DataNumber)
{
var result = rr & item;
if (result > 0)
{
data[index] = true;
}
else
{
data[index] = false;
}
//0100 0000
rr <<= 1;
index++;
}
else
{
break;
}
}
}
store.WriteCoilDiscretes(StartAddress, data);
return request;
}
/// <summary>
/// 写 读写多个寄存器
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
private ADUMessage Response_10(ADUMessage request)
{
//寄存器是大端传输
BinaryReader reader = new BigEndianBinaryReader(new MemoryStream(request.Data));
ushort StartAddress, DataNumber;
StartAddress = reader.ReadUInt16();
DataNumber = reader.ReadUInt16();
byte byteNumber = reader.ReadByte();
ushort[] data = new ushort[byteNumber / 2];
for (int i = 0; i < data.Length; i++)
{
data[i] = reader.ReadUInt16();
}
store.WriteHoldingRegisters(StartAddress, data);
return request;
}
}
/// <summary>
/// Modbus客户端
/// </summary>
public class WebModbusClient
{
public ushort Transaction { get; set; }
public TcpClient Client { get; }
public WebSocket WebSocket { get; set; }
public SerialCommunication SerialComm { get; }
public ADUMessage request { get; set; }
public ADUMessage response { get; set; }
public WebModbusClient(TcpClient client)
{
Transaction = 0x00;
Client = client;
}
public WebModbusClient(WebSocket webSocket)
{
Transaction = 0x00;
WebSocket = webSocket;
}
public WebModbusClient(SerialCommunication serialComm)
{
Transaction = 0x00;
SerialComm = serialComm;
}
private ADUMessage CreateMsg()
{
ADUMessage message = new ADUMessage();
message.Transaction = Transaction;
Transaction++;
message.Protocol = 0x00;
message.Unit = 0x00;
this.request = message;
return message;
}
public void PrintBytes(byte[] bytes, string prefix = "")
{
Console.Write(prefix);
for (int i = 0; i < bytes.Length; i++)
{
if (i < 2)
{
Console.ForegroundColor = ConsoleColor.Red;
}
else if (i < 4)
{
Console.ForegroundColor = ConsoleColor.Green;
}
else if (i < 6)
{
Console.ForegroundColor = ConsoleColor.Blue;
}
else if (i < 7)
{
Console.ForegroundColor = ConsoleColor.Yellow;
}
else if (i < 8)
{
Console.ForegroundColor = ConsoleColor.DarkCyan;
}
else
{
Console.ForegroundColor = ConsoleColor.White;
}
Console.Write(bytes[i].ToString("X2") + " ");
}
Console.WriteLine();
}
public bool[] BytesToBools(byte[] bytes,ushort dataNumber)
{
int index = 0;
bool[] bools = new bool[dataNumber];
foreach (var item in bytes)
{
//1000 0000
byte rr = (byte)0x01;
for (int i = 0; i < 8; i++)
{
if (index < dataNumber)
{
var result = rr & item;
if (result > 0)
{
bools[index] = true;
}
else
{
bools[index] = false;
}
//0100 0000
rr <<= 1;
index++;
}
else
{
break;
}
}
}
return bools;
}
private async Task<ADUMessage> SendWithResponse(ADUMessage request)
{
PrintBytes(ADUMessage.Serialze(request), "请求");
if (Client != null)
{
await Client.Client.SendAsync(new Memory<byte>(ADUMessage.Serialze(request)));
byte[] bytes = new byte[1024];
int msgLength = await Client.Client.ReceiveAsync(new ArraySegment<byte>(bytes));
this.response = ADUMessage.Deserialize(bytes.Take(msgLength).ToArray());
PrintBytes(bytes.Take(msgLength).ToArray(), "响应");
return response;
}
else if(WebSocket != null)
{
await WebSocket.SendAsync(new ArraySegment<byte>(ADUMessage.Serialze(request)),WebSocketMessageType.Binary,true,CancellationToken.None);
byte[] bytes = new byte[1024];
var result = await WebSocket.ReceiveAsync(new ArraySegment<byte>(bytes),CancellationToken.None);
this.response = ADUMessage.Deserialize(bytes.Take(result.Count).ToArray());
PrintBytes(bytes.Take(result.Count).ToArray(), "响应");
return response;
}
else if (SerialComm!=null)
{
await SerialComm.SendDataAsync(ADUMessage.Serialze(request));
byte[] bytes = await SerialComm.ReceiveDataAsync();
this.response = ADUMessage.Deserialize(bytes);
PrintBytes(bytes, "响应");
return response;
}
else
{
throw new Exception("没有传入连接");
}
}
private async Task SendNoResponse(ADUMessage request)
{
PrintBytes(ADUMessage.Serialze(request), "请求");
if (Client != null)
{
await Client.Client.SendAsync(new Memory<byte>(ADUMessage.Serialze(request)));
//丢弃可能的响应
await WebSocket.ReceiveAsync(new ArraySegment<byte>(new byte[1024*4]), CancellationToken.None);
}
else if (WebSocket != null)
{
await WebSocket.SendAsync(new ArraySegment<byte>(ADUMessage.Serialze(request)), WebSocketMessageType.Binary, true, CancellationToken.None);
//丢弃可能的响应
await WebSocket.ReceiveAsync(new ArraySegment<byte>(new byte[1024 * 4]), CancellationToken.None);
}
else if (SerialComm != null)
{
await SerialComm.SendDataAsync(ADUMessage.Serialze(request));
byte[] bytes = await SerialComm.ReceiveDataAsync();
this.response = ADUMessage.Deserialize(bytes);
PrintBytes(bytes, "响应");
}
else
{
throw new Exception("没有传入连接");
}
}
public byte[] BoolToBytes(bool[] bools)
{
int byteCount = (bools.Length + 7) / 8; // 计算所需的字节数
byte[] bytes = new byte[byteCount];
for (int i = 0; i < bools.Length; i++)
{
int byteIndex = i / 8; // 计算当前布尔值应该存储在哪个字节中
int bitIndex = i % 8; // 计算当前布尔值应该存储在字节的哪个位上
if (bools[i])
{
// 设置对应位为 1
bytes[byteIndex] |= (byte)(1 << bitIndex);
}
else
{
// 对应位保持为 0,无需额外操作
}
}
return bytes;
}
/// <summary>
/// 读 读写线圈
/// </summary>
/// <param name="startIndex"></param>
/// <param name="length"></param>
/// <returns></returns>
public async Task<bool[]> Request_01(ushort startIndex, ushort length)
{
var request = CreateMsg();
request.Length = 0x06;
request.FunctionCode= 0x01;
request.Data = new byte[4];
BinaryWriter writer = new BigEndianBinaryWriter(new MemoryStream(request.Data));
writer.Write(startIndex);
writer.Write(length);
var response = await SendWithResponse(request);
BinaryReader reader = new BinaryReader(new MemoryStream(response.Data));
byte byteLength=reader.ReadByte();
byte[] bytes = reader.ReadBytes(byteLength);
bool[] bools= BytesToBools(bytes,length);
return bools;
}
/// <summary>
/// 读 只读线圈
/// </summary>
/// <param name="startIndex"></param>
/// <param name="length"></param>
/// <returns></returns>
public async Task<bool[]> Request_02(ushort startIndex, ushort length)
{
var request = CreateMsg();
request.Length = 0x06;
request.FunctionCode = 0x02;
request.Data = new byte[4];
BinaryWriter writer = new BigEndianBinaryWriter(new MemoryStream(request.Data));
writer.Write(startIndex);
writer.Write(length);
var response = await SendWithResponse(request);
BinaryReader reader = new BinaryReader(new MemoryStream(response.Data));
byte byteLength = reader.ReadByte();
byte[] bytes = reader.ReadBytes(byteLength);
bool[] bools = BytesToBools(bytes, length);
return bools;
}
/// <summary>
/// 读 读写寄存器
/// </summary>
/// <param name="startIndex"></param>
/// <param name="length"></param>
/// <returns></returns>
public async Task<ushort[]> Request_03(ushort startIndex, ushort length)
{
var request = CreateMsg();
request.Length = 0x06;
request.FunctionCode = 0x03;
request.Data = new byte[4];
BinaryWriter writer = new BigEndianBinaryWriter(new MemoryStream(request.Data));
writer.Write(startIndex);
writer.Write(length);
var response = await SendWithResponse(request);
BinaryReader reader = new BigEndianBinaryReader(new MemoryStream(response.Data));
byte byteLength = reader.ReadByte();
ushort[] registers = new ushort[length];
for (int i = 0; i < length; i++)
{
registers[i] = reader.ReadUInt16();
}
return registers;
}
/// <summary>
/// 读 只读寄存器
/// </summary>
/// <param name="startIndex"></param>
/// <param name="length"></param>
/// <returns></returns>
public async Task<ushort[]> Request_04(ushort startIndex, ushort length)
{
var request = CreateMsg();
request.Length = 0x06;
request.FunctionCode = 0x04;
request.Data = new byte[4];
BinaryWriter writer = new BigEndianBinaryWriter(new MemoryStream(request.Data));
writer.Write(startIndex);
writer.Write(length);
var response = await SendWithResponse(request);
BinaryReader reader = new BigEndianBinaryReader(new MemoryStream(response.Data));
byte byteLength = reader.ReadByte();
ushort[] registers = new ushort[length];
for (int i = 0; i < registers.Length; i++)
{
registers[i] = reader.ReadUInt16();
}
return registers;
}
/// <summary>
/// 写 读写一个线圈
/// </summary>
/// <param name="startIndex"></param>
/// <param name="coil"></param>
/// <returns></returns>
public async Task<ADUMessage> Request_05(ushort startIndex, bool coil)
{
var request = CreateMsg();
request.Length = 0x06;
request.FunctionCode = 0x05;
request.Data = new byte[4];
BinaryWriter writer = new BigEndianBinaryWriter(new MemoryStream(request.Data));
writer.Write(startIndex);
if (coil)
{
writer.Write((ushort)0xff00);
}
else
{
writer.Write((ushort)0x0000);
}
await SendNoResponse(request);
return request;
}
/// <summary>
/// 写 读写一个寄存器
/// </summary>
/// <param name="startIndex"></param>
/// <param name="register"></param>
/// <returns></returns>
public async Task<ADUMessage> Request_06(ushort startIndex, ushort register)
{
var request = CreateMsg();
request.Length = 0x06;
request.FunctionCode = 0x06;
request.Data = new byte[4];
BinaryWriter writer = new BigEndianBinaryWriter(new MemoryStream(request.Data));
writer.Write(startIndex);
writer.Write(register);
await SendNoResponse(request);
return request;
}
/// <summary>
/// 写 读写多个线圈
/// </summary>
/// <param name="startIndex"></param>
/// <param name="coils"></param>
/// <returns></returns>
public async Task<ADUMessage> Request_0f(ushort startIndex, bool[] coils)
{
var request = CreateMsg();
request.FunctionCode = 0x0f;
request.Data = new byte[4+1+(coils.Length+7)/8];
BinaryWriter writer = new BigEndianBinaryWriter(new MemoryStream(request.Data));
writer.Write((ushort)startIndex);
var coilBytes = BoolToBytes(coils);
request.Length = (ushort)(7 + coilBytes.Length);
writer.Write((ushort)coils.Length);
writer.Write((byte)coilBytes.Length);
writer.Write(coilBytes);
await SendNoResponse(request);
return request;
}
/// <summary>
/// 写 读写多个寄存器
/// </summary>
/// <param name="startIndex"></param>
/// <param name="registers"></param>
/// <returns></returns>
public async Task<ADUMessage> Request_10(ushort startIndex, ushort[] registers)
{
var request = CreateMsg();
request.Length = (ushort)(7+ registers.Length * 2);
request.FunctionCode = 0x10;
request.Data = new byte[4+1+registers.Length*2];
BinaryWriter writer = new BigEndianBinaryWriter(new MemoryStream(request.Data));
writer.Write((ushort)startIndex);
writer.Write((ushort)registers.Length);
writer.Write((byte)(registers.Length * 2));
for (int i = 0; i < registers.Length; i++)
{
writer.Write(registers[i]);
}
await SendNoResponse(request);
return request;
}
}
Program.cs
internal class Program
{
static WebModbusServer webModbusServer;
static void Main(string[] args)
{
webModbusServer = new WebModbusServer();
//服务器
if (args.Length == 2)
{
if (args[0]=="tcp")
{
StartTcpServer(args[1]);
}
else if(args[0] == "websocket")
{
StartWebSocketServer(args[1]);
}
else if (args[0] == "comm")
{
StartCommServer(args[1]);
}
}
//客户端
else
{
if (args[0] == "tcp")
{
Task.Run(async () =>
{
await StartClient(args);
}).Wait();
}
else if (args[0] == "websocket")
{
Task.Run(async () =>
{
await StartWebsocketClient(args);
}).Wait();
}
else if (args[0] == "comm" && args[2]=="client")
{
Task.Run(async () =>
{
await StartCommClient(args);
}).Wait();
}
}
}
private static void StartTcpServer(string args)
{
int serverPort = Convert.ToInt32(args);
var server = new TcpListener(IPAddress.Parse("127.0.0.1"), serverPort);
Console.WriteLine($"TCP服务器 127.0.0.1:{serverPort}");
server.Start();
int cnt = 0;
Task.Run(async () =>
{
List<TcpClient> clients = new List<TcpClient>();
while (true)
{
TcpClient client = await server.AcceptTcpClientAsync();
clients.Add(client);
cnt++;
var ep = client.Client.RemoteEndPoint as IPEndPoint;
Console.WriteLine($"TCP客户端_{cnt} {ep.Address}:{ep.Port}");
//给这个客户端开一个聊天线程
//操作系统将会根据游客端口对应表将控制权交给对应游客线程
//StartChat(client);
StartModbus(client);
}
}).Wait();
}
private static void StartWebSocketServer(string args)
{
int serverPort = Convert.ToInt32(args);
WebsocketLisener websocketLisener = new WebsocketLisener(IPAddress.Parse("127.0.0.1"), serverPort);
Console.WriteLine($"Websocket服务器 127.0.0.1:{serverPort}");
Task.Run(async () =>
{
while (true)
{
WebSocket websocketServer = await websocketLisener.AcceptWebsocketConnectionAsync();
StartWebsocketModbus(websocketServer);
}
}).Wait();
}
private static void StartCommServer(string args)
{
SerialCommunication serialComm = new SerialCommunication(args, 9600);
Console.WriteLine($"串口服务器 {args}");
Task.Run(async () =>
{
await StartCommModbus(serialComm);
}).Wait();
}
private static async Task StartClient(string[] args)
{
int clientPort = Convert.ToInt32(args[1]);
int serverPort = Convert.ToInt32(args[2]);
var client = new TcpClient(new IPEndPoint(IPAddress.Parse("127.0.0.1"), clientPort));
Console.WriteLine($"TCP客户端 127.0.0.1:{clientPort}");
await client.ConnectAsync(new IPEndPoint(IPAddress.Parse("127.0.0.1"), serverPort));
Console.WriteLine($"连接到 127.0.0.1:{serverPort}");
WebModbusClient webModbusClient = new WebModbusClient(client);
Console.WriteLine("【功能码】 【地址】 【数量|数据】");
while (true)
{
Console.WriteLine("请输入指令");
string? msg = Console.ReadLine();
while (msg == null)
{
//功能码 数据
msg = Console.ReadLine();
}
try
{
string[] data = msg.Split(' ');
ushort funCode = ushort.Parse(data[0],NumberStyles.HexNumber);
ushort startIndex;
ushort length;
switch (funCode)
{
//读 读写线圈
case 0x01:
startIndex = ushort.Parse(data[1]);
length= ushort.Parse(data[2]);
var rs_01 = await webModbusClient.Request_01(startIndex, length);
PrintBools(rs_01);
break;
//读 只读线圈
case 0x02:
startIndex = ushort.Parse(data[1]);
length = ushort.Parse(data[2]);
var rs_02 = await webModbusClient.Request_02(startIndex, length);
PrintBools(rs_02);
break;
//读 读写寄存器
case 0x03:
startIndex = ushort.Parse(data[1]);
length = ushort.Parse(data[2]);
var rs_03 = await webModbusClient.Request_03(startIndex, length);
for (global::System.Int32 i = 0; i < length; i++)
{
Console.Write(rs_03[i]+" ");
}
Console.WriteLine();
break;
//读 只读寄存器
case 0x04:
startIndex = ushort.Parse(data[1]);
length = ushort.Parse(data[2]);
var rs_04 = await webModbusClient.Request_04(startIndex, length);
for (global::System.Int32 i = 0; i < length; i++)
{
Console.Write(rs_04[i] + " ");
}
Console.WriteLine();
break;
//写 读写一个线圈
case 0x05:
startIndex = ushort.Parse(data[1]);
var coil = bool.Parse(data[2]);
var rs_05 = await webModbusClient.Request_05(startIndex, coil);
break;
//写 读写一个寄存器
case 0x06:
startIndex = ushort.Parse(data[1]);
var register = ushort.Parse(data[2]);
var rs_06 = await webModbusClient.Request_06(startIndex, register);
break;
//写 读写多个线圈
case 0x0f:
startIndex = ushort.Parse(data[1]);
bool[] coils = new bool[data.Length - 2];
for (global::System.Int32 i = 2; i < data.Length; i++)
{
coils[i - 2] = bool.Parse(data[i]);
}
var rs_0f = await webModbusClient.Request_0f(startIndex, coils);
break;
//写 读写多个寄存器
case 0x10:
startIndex = ushort.Parse(data[1]);
ushort[] registers = new ushort[data.Length - 2];
for (global::System.Int32 i = 2; i < data.Length; i++)
{
registers[i - 2] = ushort.Parse(data[i]);
}
var rs_10 = await webModbusClient.Request_10(startIndex, registers);
break;
default:
//return Response_01(request);
break;
}
}
catch (Exception e)
{
}
}
}
private static async Task StartWebsocketClient(string[] args)
{
int clientPort = Convert.ToInt32(args[1]);
int serverPort = Convert.ToInt32(args[2]);
Uri uri = new($"ws://127.0.0.1:{serverPort}");
ClientWebSocket ws = new();
Console.WriteLine($"Websocket客户端");
await ws.ConnectAsync(uri, default);
Console.WriteLine($"连接到 127.0.0.1:{serverPort}");
WebModbusClient webModbusClient = new WebModbusClient(ws);
Console.WriteLine("【功能码】 【地址】 【数量|数据】");
while (true)
{
Console.WriteLine("请输入指令");
string? msg = Console.ReadLine();
while (msg == null)
{
//功能码 数据
msg = Console.ReadLine();
}
try
{
string[] data = msg.Split(' ');
ushort funCode = ushort.Parse(data[0], NumberStyles.HexNumber);
ushort startIndex;
ushort length;
switch (funCode)
{
//读 读写线圈
case 0x01:
startIndex = ushort.Parse(data[1]);
length = ushort.Parse(data[2]);
var rs_01 = await webModbusClient.Request_01(startIndex, length);
PrintBools(rs_01);
break;
//读 只读线圈
case 0x02:
startIndex = ushort.Parse(data[1]);
length = ushort.Parse(data[2]);
var rs_02 = await webModbusClient.Request_02(startIndex, length);
PrintBools(rs_02);
break;
//读 读写寄存器
case 0x03:
startIndex = ushort.Parse(data[1]);
length = ushort.Parse(data[2]);
var rs_03 = await webModbusClient.Request_03(startIndex, length);
for (global::System.Int32 i = 0; i < length; i++)
{
Console.Write(rs_03[i] + " ");
}
Console.WriteLine();
break;
//读 只读寄存器
case 0x04:
startIndex = ushort.Parse(data[1]);
length = ushort.Parse(data[2]);
var rs_04 = await webModbusClient.Request_04(startIndex, length);
for (global::System.Int32 i = 0; i < length; i++)
{
Console.Write(rs_04[i] + " ");
}
Console.WriteLine();
break;
//写 读写一个线圈
case 0x05:
startIndex = ushort.Parse(data[1]);
var coil = bool.Parse(data[2]);
var rs_05 = await webModbusClient.Request_05(startIndex, coil);
break;
//写 读写一个寄存器
case 0x06:
startIndex = ushort.Parse(data[1]);
var register = ushort.Parse(data[2]);
var rs_06 = await webModbusClient.Request_06(startIndex, register);
break;
//写 读写多个线圈
case 0x0f:
startIndex = ushort.Parse(data[1]);
bool[] coils = new bool[data.Length - 2];
for (global::System.Int32 i = 2; i < data.Length; i++)
{
coils[i - 2] = bool.Parse(data[i]);
}
var rs_0f = await webModbusClient.Request_0f(startIndex, coils);
break;
//写 读写多个寄存器
case 0x10:
startIndex = ushort.Parse(data[1]);
ushort[] registers = new ushort[data.Length - 2];
for (global::System.Int32 i = 2; i < data.Length; i++)
{
registers[i - 2] = ushort.Parse(data[i]);
}
var rs_10 = await webModbusClient.Request_10(startIndex, registers);
break;
default:
//return Response_01(request);
break;
}
}
catch (Exception e)
{
}
}
}
private static async Task StartCommClient(string[] args)
{
string clientPort = args[1];
SerialCommunication serialComm = new SerialCommunication(clientPort, 9600);
Console.WriteLine($"串口客户端 :{clientPort}");
WebModbusClient webModbusClient = new WebModbusClient(serialComm);
Console.WriteLine("【功能码】 【地址】 【数量|数据】");
while (true)
{
Console.WriteLine("请输入指令");
string? msg = Console.ReadLine();
while (msg == null)
{
//功能码 数据
msg = Console.ReadLine();
}
try
{
string[] data = msg.Split(' ');
ushort funCode = ushort.Parse(data[0], NumberStyles.HexNumber);
ushort startIndex;
ushort length;
switch (funCode)
{
//读 读写线圈
case 0x01:
startIndex = ushort.Parse(data[1]);
length = ushort.Parse(data[2]);
var rs_01 = await webModbusClient.Request_01(startIndex, length);
PrintBools(rs_01);
break;
//读 只读线圈
case 0x02:
startIndex = ushort.Parse(data[1]);
length = ushort.Parse(data[2]);
var rs_02 = await webModbusClient.Request_02(startIndex, length);
PrintBools(rs_02);
break;
//读 读写寄存器
case 0x03:
startIndex = ushort.Parse(data[1]);
length = ushort.Parse(data[2]);
var rs_03 = await webModbusClient.Request_03(startIndex, length);
for (global::System.Int32 i = 0; i < length; i++)
{
Console.Write(rs_03[i] + " ");
}
Console.WriteLine();
break;
//读 只读寄存器
case 0x04:
startIndex = ushort.Parse(data[1]);
length = ushort.Parse(data[2]);
var rs_04 = await webModbusClient.Request_04(startIndex, length);
for (global::System.Int32 i = 0; i < length; i++)
{
Console.Write(rs_04[i] + " ");
}
Console.WriteLine();
break;
//写 读写一个线圈
case 0x05:
startIndex = ushort.Parse(data[1]);
var coil = bool.Parse(data[2]);
var rs_05 = await webModbusClient.Request_05(startIndex, coil);
break;
//写 读写一个寄存器
case 0x06:
startIndex = ushort.Parse(data[1]);
var register = ushort.Parse(data[2]);
var rs_06 = await webModbusClient.Request_06(startIndex, register);
break;
//写 读写多个线圈
case 0x0f:
startIndex = ushort.Parse(data[1]);
bool[] coils = new bool[data.Length - 2];
for (global::System.Int32 i = 2; i < data.Length; i++)
{
coils[i - 2] = bool.Parse(data[i]);
}
var rs_0f = await webModbusClient.Request_0f(startIndex, coils);
break;
//写 读写多个寄存器
case 0x10: