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协议族为我们所熟悉。串口通信是如何进行的?也是分层的吗?串口通信与网络通信能融合吗?这个我们比较陌生。

串口通信和网络通信(例如通过以太网进行的网络通信)在其基本原理和工作方式上有一些显著的区别。

串口通信与网络通信比较

  1. 物理介质:

    • 串口通信:通常通过物理导线(例如串口线)直接连接两个设备进行通信,例如 RS-232、RS-485、USB 等串口标准。

      串口通信通常通过导线进行,但也可以通过其他媒介进行,如光纤或无线电波。就是插一个转换器到串口接口上,比如串口到光纤转换器。串口到无线电波转换器。我们平时经常用到的就有USB转WIFI、USB转4G。
    • 网络通信:通过各种不同的物理介质进行,如以太网使用的双绞线、光纤、无线电波等。
  2. 协议栈和分层:

    • 串口通信:虽然串口通信也可以分层,但通常它的分层结构较简单,主要包含物理层和数据链路层。常见的串口通信协议如

      物理层 传输介质 接口 数据链路层
      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协议栈,实现远程监控、远程控制等功能。
  3. 速率和带宽:

    • 串口通信:通常速率较低,受限于物理介质和串口协议的限制,不适合大量数据的高速传输。
    • 网络通信:具有较高的传输速率和带宽,能够支持大规模数据的传输。

在程序中使用串口

就像网络通讯我们不用管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:
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)
{ }
}
} public static async Task StartModbus(TcpClient client)
{
var buffer = new byte[1024 * 4];
while (client.Connected)
{
int msgLength = await client.Client.ReceiveAsync(new ArraySegment<byte>(buffer));
//关闭连接时会接收到一次空消息,不知道为什么
if (msgLength>0)
{
PrintBytes(buffer.Take(msgLength).ToArray(), "请求 ");
ADUMessage response = webModbusServer.HandleRequest(buffer.Take(msgLength).ToArray());
await client.Client.SendAsync(ADUMessage.Serialze(response));
PrintBytes(ADUMessage.Serialze(response), "响应 ");
}
}
} public static async Task StartWebsocketModbus(WebSocket websocketServer)
{
var buffer = new byte[1024 * 4];
while (!websocketServer.CloseStatus.HasValue)
{
var result = await websocketServer.ReceiveAsync(new ArraySegment<byte>(buffer),CancellationToken.None);
if (result.Count > 0)
{
PrintBytes(buffer.Take(result.Count).ToArray(), "请求 ");
ADUMessage response = webModbusServer.HandleRequest(buffer.Take(result.Count).ToArray());
await websocketServer.SendAsync(ADUMessage.Serialze(response),WebSocketMessageType.Binary,true,CancellationToken.None);
PrintBytes(ADUMessage.Serialze(response), "响应 ");
}
}
} 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), "响应 ");
}
}
} 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();
}
public static void PrintBools(bool[] bools)
{
for (int i = 0; i < bools.Length; i++)
{
Console.Write(bools[i] + " ");
}
Console.WriteLine();
}
} public class HttpRequet
{
/// <summary>
/// 解析HTTP消息
/// </summary>
public HttpRequet(string str)
{
Str = str;
//开始行
var startLine = str.Split("\r\n")[0];
var lines = startLine.Split("\r\n");
httpMethod = lines[0].Split(' ')[0];
path = lines[0].Split(' ')[1];
//头部
var headerslines = str.Split("\r\n\r\n")[0].Split("\r\n");
headers = new Dictionary<string, string>();
for (int i = 1; i < headerslines.Length; i++)
{
var header = headerslines[i].Split(": ");
headers.Add(header[0], header[1]);
}
} /// <summary>
/// 请求原始消息
/// </summary>
public string Str { get; }
/// <summary>
/// 请求方法
/// </summary>
public string httpMethod { get; internal set; }
/// <summary>
/// 请求路径
/// </summary>
public string path { get; set; }
/// <summary>
/// 头部字段
/// </summary>
public Dictionary<string, string> headers { get; set; } /// <summary>
/// 判断是否是转协议的请求
/// </summary>
/// <returns></returns>
public bool IsWebsocket()
{
if (this.headers.ContainsKey("Connection") && this.headers["Connection"] == "Upgrade" && this.headers.ContainsKey("Upgrade") && this.headers["Upgrade"] == "websocket")
return true;
else
return false;
} /// <summary>
/// 响应转协议请求并未用当前连接创建一个WebSocket对象
/// </summary>
/// <param name="client"></param>
/// <returns></returns>
public async Task<WebSocket> AcceptWebsocket(TcpClient client, string Sec_WebSocket_Key)
{
using (MemoryStream memoryStream = new MemoryStream())
{
string header = @$"HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: {GenerateResponseKey(Sec_WebSocket_Key)} ";
memoryStream.Write(new ArraySegment<byte>(ASCIIEncoding.ASCII.GetBytes(header)));
await client.Client.SendAsync(new ArraySegment<byte>(memoryStream.ToArray()));
Console.WriteLine(header); return WebSocket.CreateFromStream(client.GetStream(), true, null, TimeSpan.FromSeconds(10));
}
} public static string GenerateResponseKey(string requestKey)
{
const string guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
string concatenated = requestKey + guid;
byte[] hashed = System.Security.Cryptography.SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(concatenated));
return Convert.ToBase64String(hashed);
}
} public class WebsocketLisener
{
public TcpListener server { get; set; }
public int cnt;
public WebsocketLisener(IPAddress address,int port)
{
cnt = 0;
server = new TcpListener(address, port);
server.Start();
}
public async Task<WebSocket> AcceptWebsocketConnectionAsync()
{
TcpClient client = await server.AcceptTcpClientAsync();
var buffer = new byte[1024 * 4];
int msgLength = await client.Client.ReceiveAsync(new ArraySegment<byte>(buffer));
string str = UTF8Encoding.UTF8.GetString(buffer, 0, msgLength);
HttpRequet request = new HttpRequet(str);
if (request.IsWebsocket())
{
cnt++;
WebSocket webSocket = await request.AcceptWebsocket(client, request.headers["Sec-WebSocket-Key"]);
var ep = client.Client.RemoteEndPoint as IPEndPoint;
Console.WriteLine($"Websocket客户端_{cnt} {ep.Address}:{ep.Port}");
return webSocket;
}
throw new Exception("不是WebSocket连接");
}
}

基于WebSocket的modbus通信(三)- websocket和串口的更多相关文章

  1. 基于动态代理的WebAPI/RPC/webSocket框架,一套接口定义,多个通讯方式

    API/RPC/webSocket三个看起来好像没啥相同的地方,在开发时,服务端,客户端实现代码也大不一样 最近整理了一下,通过动态代理的形式,整合了这些开发,都通过统一的接口约束,服务端实现和客户端 ...

  2. .NET 即时通信,WebSocket服务端实例

    即时通信常用手段 1.第三方平台 谷歌.腾讯 环信等多如牛毛,其中谷歌即时通信是免费的,但免费就是免费的并不好用.其他的一些第三方一般收费的,使用要则限流(1s/限制x条消息)要么则限制用户数. 但稳 ...

  3. WebSocket原理与实践(三)--解析数据帧

    WebSocket原理与实践(三)--解析数据帧 1-1 理解数据帧的含义:   在WebSocket协议中,数据是通过帧序列来传输的.为了数据安全原因,客户端必须掩码(mask)它发送到服务器的所有 ...

  4. [Python]通过websocket与jsclient通信

    站点大多使用HTTP协议通信.而HTTP是无连接的协议.仅仅有client请求时,server端才干发出对应的应答.HTTP请求的包也比較大,假设仅仅是非常小的数据通信.开销过大.于是,我们能够使用w ...

  5. .NET 即时通信,WebSocket

    .NET 即时通信,WebSocket 即时通信常用手段 1.第三方平台 谷歌.腾讯 环信等多如牛毛,其中谷歌即时通信是免费的,但免费就是免费的并不好用.其他的一些第三方一般收费的,使用要则限流(1s ...

  6. WebSocket原理与实践(二)---WebSocket协议

    WebSocket原理与实践(二)---WebSocket协议 WebSocket协议是为了解决web即时应用中服务器与客户端浏览器全双工通信问题而设计的.协议定义ws和wss协议,分别为普通请求和基 ...

  7. C#实现WebSocket协议客户端和服务器websocket sharp组件实例解析

    看到这篇文章的题目,估计很多人都会问,这个组件是不是有些显的无聊了,说到web通信,很多人都会想到ASP.NET SignalR,或者Nodejs等等,实现web的网络实时通讯.有关于web实时通信的 ...

  8. QQ 腾讯QQ(简称“QQ”)是腾讯公司开发的一款基于Internet的即时通信(IM)软件

    QQ 编辑 腾讯QQ(简称“QQ”)是腾讯公司开发的一款基于Internet的即时通信(IM)软件.腾讯QQ支持在线聊天.视频通话.点对点断点续传文件.共享文件.网络硬盘.自定义面板.QQ邮箱等多种功 ...

  9. 基于FPGA的红外遥控解码与PC串口通信

    基于FPGA的红外遥控解码与PC串口通信 zouxy09@qq.com http://blog.csdn.net/zouxy09 这是我的<电子设计EDA>的课程设计作业(呵呵,这个月都拿 ...

  10. 基于51单片机IIC通信的PCF8591学习笔记

    引言 PCF8591 是单电源,低功耗8 位CMOS 数据采集器件,具有4 个模拟输入.一个输出和一个串行I2C 总线接口.3 个地址引脚A0.A1 和A2 用于编程硬件地址,允许将最多8 个器件连接 ...

随机推荐

  1. 重新整理.net core 计1400篇[九] (.net core 中的依赖注入的服务注入)

    前言 在该系列六中介绍了一个简单的依赖注入,该节介绍.net core 中的依赖注入的服务注入. ServiceDescriptor ServiceDescriptor 是服务描述的意思,这个是做什么 ...

  2. node require 运行步骤

    前言 准备整理node 系列,先把一些基础含义放出来. 在学习node 的时候我们一般加载模块都是require,那么require 是如何运行的呢? 正文 通常,在Node.js里导入是通过 req ...

  3. 国庆的一些blog 书写

    前言 国庆估计出不去了,所以吧,把文档准备下. 正文 1.docker 微服务,整理微软开源shop框架. 2.rpa 这个东西,我第一次接触是因为android测试的时候,每次修改代码,都需要全部测 ...

  4. web开发可不可以是这样的?

    service不外乎就是数据校验,调用其它service,调用第三方api,读写数据库,既然这样,那我认为Service也可以做成可配置化的样子,配置项大致有 所需参数配置:参数列表,参数类型,参数长 ...

  5. 红日安全vulnstack (一)

    网络拓扑图 靶机参考文章 CS/MSF派发shell 环境搭建 IP搭建教程 本机双网卡 65网段和83网段是自己本机电脑(虚拟机)中的网卡, 靶机外网的IP需要借助我们这两个网段之一出网 Kali ...

  6. stm32串口晶振不对输出乱码+汇承HC-14lora模块

    最近要用到一个lora无线透传模块,然后就先用两个32开发板(用的STM32F103C8T6)试试简单的收发数据.结果,第一步串口发送一句话就寄了,我串口打印了"hi",结果出现了 ...

  7. TiDB、OceanBase、PolarDB-X、CockroachDB二级索引写入性能测评

    简介: 二级索引是关系型数据库相较于NoSQL数据库的一个关键差异.二级索引必须是强一致的,因此索引的写入需要与主键的写入放在一个事务当中,事务的性能是二级索引性能的基础.本次测试将重点关注不同分布式 ...

  8. 测试环境不稳定&复杂的必然性及其对策

    简介: 为什么测试环境的不稳定是必然的,怎么让它尽量稳定一点?为什么测试环境比生产环境更复杂,怎么让它尽量简单一点?本文将就这两点进行分享.同时,还会谈一谈对测试环境和生产环境的区别的理解. 作者 | ...

  9. Apsara Stack 技术百科 | 数字化业务系统安全工程

    ​简介:数字化平台已经与我们生活紧密结合,其用户规模庞大,一旦系统出现故障,势必会造成一定生活的不便.比如疫情时代,健康码已经成为人们出门必备的条件,一旦提供健康码服务平台出现故障,出行将变得寸步难行 ...

  10. MaxCompute中如何通过logview诊断慢作业

    ​建模服务,在MaxCompute执行sql任务的时候有时候作业会很慢,本文通过查看logview排查具体任务慢的原因 在这里把任务跑的慢的问题划分为以下几类 资源不足导致的排队(一般是包年包月项目) ...