基于WebSocket的modbus通信(一)- 服务器
ModbusTcp协议是基于tcp的,但不是说一定要通过tcp协议才能传输,只要能传输二进制的地方都可以。比如WebSocket协议。
但由于目前我只有tcp上面的modbus服务器实现,所以我必须先用tcp连接借助已有工具来验证我的服务器是否写正确。
效果


ModBusTCP协议报文
ModBusTCP协议报文比较复杂,主要是区分了3组类型
- 请求和响应
- 读和写
- 多个数据和单个数据
理论上将报文格式的种类从1种变成了8种。
但是在响应读数据时,没有区分数据量,而是到请求中去查找,听说是为了减少传输量。因此实际格式种类是3组6种。
请求读数据报文
事务标识符 协议标识符 报文长度 单元标识符 功能码 起始地址 读取数量 字段长度 2 byte 2 byte 2 byte 1 byte 1 byte 2 byte 2 byte 数据类型 ushort ushort ushort byte byte ushort ushort 自增 一般为0 6,从单元标识符开始算 一般为0 链接 响应读数据报文
事务标识符 协议标识符 报文长度 单元标识符 功能码 数据字节数 数据 2 byte 2 byte 2 byte 1 byte 1 byte 1 byte n byte 值得注意的是,响应报文中没有包含请求的数据数量,只能从请求中获得。比如是7个线圈还是8个线圈是看不出来的。
这其实就限制了程序结构,应该在同一个上下文中声明请求和响应变量。请求写单个数据报文
事务标识符 协议标识符 报文长度 单元标识符 功能码 起始地址 数据 2 byte 2 byte 2 byte 1 byte 1 byte 2 byte 2 byte 响应写单个数据报文
事务标识符 协议标识符 报文长度 单元标识符 功能码 起始地址 数据 2 byte 2 byte 2 byte 1 byte 1 byte 2 byte 2 byte 请求写单个数据的响应实际上是原样返回请求。
写1位线圈和写16位寄存器都传输了2个字节,这很奇怪。
原来是ModBusTCP定义写单个线圈用0xff00表示1,0x0000表示0。请求写多个数据报文
事务标识符 协议标识符 报文长度 单元标识符 功能码 起始地址 数量 字节数 数据 2 byte 2 byte 2 byte 1 byte 1 byte 2 byte 2 byte 1 byte n byte 请求写多个数据时,如果是线圈,现在又变成了一位代表一个数据,而不是原来的2字节代表单个数据
还需要注意的是modbus采用大端传输,意味着一个字节中如果写了四个线圈,要从右向左数。响应写多个数据报文
事务标识符 协议标识符 报文长度 单元标识符 功能码 起始地址 数量 2 byte 2 byte 2 byte 1 byte 1 byte 2 byte 2 byte
报文类定义
从功能码字段往后,字段不确定,所以统一用Data表示。
在各个响应方法里根据不同功能码对数据进行处理。
//WebModbus.cs
// Modbus报文
public class ADUMessage
{
// 事务标识符
public ushort Transaction { get; set; }
// 协议标识符
public ushort Protocol { get; set; }
// 报文长度
public ushort Length { get; set; }
// 单元标识符
public byte Unit { get; set; }
// 功能码
public byte FunctionCode { get; set; }
// 数据
public byte[] Data { get; set; }
public static ADUMessage Deserialize(byte[] buffer){}
public static byte[] Serialze(ADUMessage message){}
}
数据模型
modbusTCP协议规定了服务器实现线圈、寄存器2种数据模型,每种又分为只读、可读可写。于是服务器一共要提供4个存储栈。
因为报文起始地址字段有2个字节,所以寻址范围是0-65535
| 栈地址 | 线圈栈 | 离散量输入栈 | 只读寄存器栈 | 读写寄存器栈 |
|---|---|---|---|---|
| 65535 | 0 | 0 | 0xFF C1 | 0x00 00 |
| 65534 | 0 | 1 | 0x00 00 | 0x00 00 |
| ... | 1 | 0 | 0x00 00 | 0x00 00 |
| 1 | 0 | 0 | 0x00 00 | 0x08 00 |
| 0 | 0 | 0 | 0x00 00 | 0x00 1A |
其中线圈栈和离散量输入栈是bool类型的,实际实现的时候可以是数字0、1,也可以是布尔true,false,还可以是字符串"on","off"
如何存储,存储的数据类型是什么?这不重要。重要的是传输的时候采用规定格式就行。
- 传输格式
采用第3种报文写单线圈时,如果要传输true,要使用0xFF 00。传输false,要使用0x00 00
其它时候都用一个bit位表示true和false
比如线圈栈,从地址0开始读9个数据。就是小端字节序0x0f 00。0 1 2 3 4 5 6 7 8 ... 1 1 1 1 0 0 0 0 0 0 当然,要从请求报文中知道截取取响应数据的哪几位。
- 模型类定义
//WebModbus.cs
// 数据仓库,144KB
public class DataStore
{
// 读写16位寄存器,64KB
public ushort[] HoldingRegisters;
// 只读16位寄存器,64KB
public ushort[] InputRegisters;
// 读写1位线圈,8KB
public bool[] CoilDiscretes;
// 只读1位线圈,8KB
public bool[] CoilInputs;
public DataStore()
{
HoldingRegisters = new ushort[65536];
InputRegisters = new ushort[65536];
CoilDiscretes = new bool[65536];
CoilInputs = new bool[65536];
}
// 读 读写16位寄存器
public ushort[] ReadHoldingRegisters(ushort startIndex, ushort length){}
// 读 只读16位寄存器
public ushort[] ReadInputRegisters(ushort startIndex, ushort length){}
// 读 读写1位线圈
public bool[] ReadCoilDiscretes(ushort startIndex, ushort length){}
// 读 只读1位线圈
public bool[] ReadCoilInputs(ushort startIndex, ushort length){}
// 写 读写16位寄存器
public void WriteHoldingRegisters(ushort startIndex, ushort[] data){}
//写 读写1位线圈
public void WriteCoilDiscretes(ushort startIndex, bool[] data){}
}
功能码
功能码主要用来操作这4个栈,因为栈分读写和只读,写入还分为多个数据和单个数据。所以主要有8种功能码
- 0x01 读线圈栈
- 0x02 读离散量输入栈
- 0x03 读只读寄存器栈
- 0x04 读读写寄存器栈
- 0x05 写一个数据到线圈栈
- 0x06 写一个数据到读写寄存器栈
- 0x0F 写多个数据到线圈栈
- 0x10 写多个数据到读写寄存器栈
- 当然还有其它的,但不影响主要功能,这里就不说明了
相应的
字节序大小端
字节序这个东西还是当初上操作系统课时老师讲过,以后再没接触。不过要自己处理字节流时又面临这个问题了。
modbusTcp主要采用大端字节序方式传输数据。
为什么说主要呢,因为唯独线圈数据是采用小端的方式传输的,其它数据都是大端传输。这要特别注意。
- C#实现时可以使用
BinaryReader和BinaryWriter来处理接收到的和要传输的字节流。
但这两个类默认都是按照小端字节序来处理数据。比如reader.ReadUInt16(),如果我们传的字节流是0x00 01,它会以为是ushort 256,而不是ushort 1。
所以需要我们重载override这个方法,翻转字节数组,按照大端方式读取。
//BigEndianBinaryReader.cs
public override short ReadInt16()
{
var data = base.ReadBytes(2);
Array.Reverse(data);
return BitConverter.ToInt16(data, 0);
}
服务器处理流程
modbus服务器要做的就是
接收消息,反序列化为消息对象
然后判断怎么响应
读取数据栈,构造消息
序列化后响应
核心方法
//WebModbus.cs
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);
}
}
完整代码
程序所需命令行参数的一种方式是在项目文件种指定,这在调试时比较方便
<PropertyGroup>
<StartArguments>5234</StartArguments>
</PropertyGroup>
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();
}
}
}
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;
}
}
BigEndianBinaryReader.cs
public class BigEndianBinaryReader : BinaryReader
{
public BigEndianBinaryReader(Stream input) : base(input)
{
}
public override short ReadInt16()
{
var data = base.ReadBytes(2);
Array.Reverse(data);
return BitConverter.ToInt16(data, 0);
}
public override ushort ReadUInt16()
{
var data = base.ReadBytes(2);
Array.Reverse(data);
return BitConverter.ToUInt16(data, 0);
}
public override int ReadInt32()
{
var data = base.ReadBytes(4);
Array.Reverse(data);
return BitConverter.ToInt32(data, 0);
}
public override uint ReadUInt32()
{
var data = base.ReadBytes(4);
Array.Reverse(data);
return BitConverter.ToUInt32(data, 0);
}
public override long ReadInt64()
{
var data = base.ReadBytes(8);
Array.Reverse(data);
return BitConverter.ToInt64(data, 0);
}
public override float ReadSingle()
{
var data = base.ReadBytes(4);
Array.Reverse(data);
return BitConverter.ToSingle(data, 0);
}
public override double ReadDouble()
{
var data = base.ReadBytes(8);
Array.Reverse(data);
return BitConverter.ToDouble(data, 0);
}
// 可以继续添加其他方法来支持更多数据类型的大端读取
}
public class BigEndianBinaryWriter : BinaryWriter
{
public BigEndianBinaryWriter(Stream input) : base(input)
{
}
public override void Write(ushort value)
{
var bytes = BitConverter.GetBytes(value);
Array.Reverse(bytes);
base.Write(bytes);
}
public override void Write(short value)
{
var bytes = BitConverter.GetBytes(value);
Array.Reverse(bytes);
base.Write(bytes);
}
public override void Write(uint value)
{
var bytes = BitConverter.GetBytes(value);
Array.Reverse(bytes);
base.Write(bytes);
}
public override void Write(int value)
{
var bytes = BitConverter.GetBytes(value);
Array.Reverse(bytes);
base.Write(bytes);
}
public override void Write(ulong value)
{
var bytes = BitConverter.GetBytes(value);
Array.Reverse(bytes);
base.Write(bytes);
}
public override void Write(long value)
{
var bytes = BitConverter.GetBytes(value);
Array.Reverse(bytes);
base.Write(bytes);
}
// 可以继续添加其他方法来支持更多数据类型的大端写入
}
Program.cs
internal class Program
{
static WebModbusServer webModbusServer;
static void Main(string[] args)
{
webModbusServer = new WebModbusServer();
//服务器
if (args.Length == 1)
{
//webModbusServer.store.WriteCoilDiscretes(0, new bool[] { true, true });
//webModbusServer.store.CoilInputs[0] = true;
//webModbusServer.store.CoilInputs[1] = true;
StartServer(args[0]);
}
}
private static void StartServer(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}");
//给这个客户端开一个聊天线程
//操作系统将会根据游客端口对应表将控制权交给对应游客线程
StartModbus(client);
}
}).Wait();
}
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 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();
}
}
基于WebSocket的modbus通信(一)- 服务器的更多相关文章
- 分享基于 websocket 网页端聊天室
博客地址:https://ainyi.com/67 有一个月没有写博客了,也是因为年前需求多.回家过春节的原因,现在返回北京的第二天,想想,应该也要分享技术专题的博客了!! 主题 基于 websock ...
- 基于 WebSocket 的 MQTT 移动推送方案
WebSphere MQ Telemetry Transport 简介 WebSphere MQ Telemetry Transport (MQTT) 是一项异步消息传输协议,是 IBM 在分析了他们 ...
- SpringBoot基于websocket的网页聊天
一.入门简介正常聊天程序需要使用消息组件ActiveMQ或者Kafka等,这里是一个Websocket入门程序. 有人有疑问这个技术有什么作用,为什么要有它?其实我们虽然有http协议,但是它有一个缺 ...
- 基于 websocket 的多端桥接平台
我们现在的业务是基于新闻客户端实现的,都要经过新闻客户端的环境,进行前后端数据上的交互.但是我们在调试过程中,非常的不方便. 通常使用的工具有:modheader, postman, fiddler ...
- 网络编程-基于Websocket聊天室(IM)系统
目录 一.HTML5 - Websocket协议 二.聊天室(IM)系统的设计 2.1.使用者眼中的聊天系统 2.2.开发者眼中的聊天系统 2.3.IM系统的特性 2.4.心跳机制:解决网络的不确定性 ...
- 为美多商城(Django2.0.4)添加基于websocket的实时通信,主动推送,聊天室及客服系统
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_67 websocket是个啥? webSocket是一种在单个TCP连接上进行全双工通信的协议 webSocket使得客户端和服务 ...
- 基于 WebSocket 实现 WebGL 3D 拓扑图实时数据通讯同步(二)
我们上一篇<基于 WebSocket 实现 WebGL 3D 拓扑图实时数据通讯同步(一)>主要讲解了如何搭建一个实时数据通讯服务器,客户端与服务端是如何通讯的,相信通过上一篇的讲解,再配 ...
- Socket.IO – 基于 WebSocket 构建跨浏览器的实时应用
Socket.IO 是一个功能非常强大的框架,能够帮助你构建基于 WebSocket 的跨浏览器的实时应用.支持主流浏览器,多种平台,多种传输模式,还可以集合 Exppress 框架构建各种功能复杂 ...
- QQ 腾讯QQ(简称“QQ”)是腾讯公司开发的一款基于Internet的即时通信(IM)软件
QQ 编辑 腾讯QQ(简称“QQ”)是腾讯公司开发的一款基于Internet的即时通信(IM)软件.腾讯QQ支持在线聊天.视频通话.点对点断点续传文件.共享文件.网络硬盘.自定义面板.QQ邮箱等多种功 ...
- 基于 WebSocket 构建跨浏览器的实时应用
Socket.IO – 基于 WebSocket 构建跨浏览器的实时应用 Socket.IO 是一个功能非常强大的框架,能够帮助你构建基于 WebSocket 的跨浏览器的实时应用.支持主流浏览器,多 ...
随机推荐
- tracer 原理
前言 准备整理网络章节,先整理概念. tracer 是一个可以获取我们的主机到访问端中间经过了哪些路由,这个对于我们非常重要,看下原理吧. 正文 tracer 在unix中是tracerRout. 它 ...
- MVC过滤器简单刨析
前言 简单介绍一下过滤器. 正文 看下过滤器类型: action: 在动作方法之前及之后执行 result: 在action结果被执行前或者之后执行,比如说返回一个视图,再执行视图前执行一个方法,再执 ...
- Django框架——路由分发、名称空间、虚拟环境、视图层三板斧、JsonResponse对象、request获取文件、FBV与CBV、CBV源码剖析、模版层
路由分发 # Django支持每个应用都可以有自己独立的路由层.静态文件.模版层.基于该特性多人开发项目就可以完全解耦合,之后利用路由分发还可以整合到一起 多个应用都有很多路由与视图函数的对应关系 这 ...
- 见鬼了!我家的 WiFi 只有下雨天才能正常使用...
这是作者大学时期在家里遇到的一个非常奇怪的网络问题,作者的父亲是一名经验丰富的网络工程师,他们家里使用了一个复杂的网络设置,通过 Wi-Fi 桥接的方式,将父亲公司的高速商业网络连接到家中.但是有一天 ...
- oracle表名、字段名等对象的命名长度限制(报错:ORA-00972: 标识符过长)
oracle表名.字段名等对象的命名长度限制(报错:ORA-00972: 标识符过长) 简单来说,出现了ORA-00972: 标识符过长的错误 找来找去发现是自己的中间表名太长导致的 Oracle数据 ...
- 力扣540(java&python)-有序数组中的单一元素(中等)
题目: 给你一个仅由整数组成的有序数组,其中每个元素都会出现两次,唯有一个数只会出现一次. 请你找出并返回只出现一次的那个数. 你设计的解决方案必须满足 O(log n) 时间复杂度和 O(1) 空间 ...
- .NET周刊【4月第2期 2024-04-21】
国内文章 他来了他来了,.net开源智能家居之苹果HomeKit的c#原生sdk[Homekit.Net]1.0.0发布,快来打造你的私人智能家居吧 https://www.cnblogs.com/h ...
- dotnet 读 WPF 源代码笔记 聊聊 HwndWrapper.GetGCMemMessage 调试消息
我在阅读 WPF 源代码,在 HwndWrapper 的静态构造函数看到了申请了 HwndWrapper.GetGCMemMessage 这个 Windows 消息,好奇这个消息是什么功能的.通过阅读 ...
- Codeforces 题解集
Codeforces Round 940 (Div. 2) and CodeCraft-23 Codeforces Round 940 (Div. 2) and CodeCraft-23 (A-E)
- 老外为了在MacBook上玩原神,让M1支持了所有iOS应用 | Github每周精彩分享第一期
大家好,这里是每周更新的Github有趣项目分享,我是每周都在吃瓜的蛮三刀酱. 我会从Github热门榜里选出 高质量.有趣,牛B 的开源项目进行分享. 废话不多说,看看最近有什么有意思的Github ...