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: 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("rn")[0]; var lines = startLine.Split("rn"); httpMethod = lines[0].Split(' ')[0]; path = lines[0].Split(' ')[1]; //头部 var headerslines = str.Split("rnrn")[0].Split("rn"); 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连接"); } }