上一篇已经实现了ModbusTcp服务器和8个主要的功能码,只是还没有实现错误处理功能。

但是在测试客户端时却发现了上一篇的一个错误,那就是写数据成功,服务器不需要响应。

接下来要做的就是实现ModbusTcp客户端。有了清晰的协议,代码循规蹈矩的写就行了。

效果

  • 原始数据

    其中只读寄存器和线圈都有可分辨的值

  • 交互

    改变线圈和寄存器的值

    • 向线圈写入4个1
    • 向寄存器写入4个11
    • 将每个栈的值查询出来

  • 结果

    可以看到数据已变成我们设定的值

客户端解析

  • 工作流程

    • 命令行输入指令
    • 解析指令
    • 根据功能码跳转到相应分支
    • 构造、发送请求
    • 解析响应
  • 根据协议,每次发请求,事务标识符都会自增。
  • 客户端需要实现8种功能码,因此每个功能码都需要一个方法去实现。
      //WebModbus.cs
    
      // 读 读写线圈
    public async Task<bool[]> Request_01(ushort startIndex, ushort length){}
    // 读 只读线圈
    public async Task<bool[]> Request_02(ushort startIndex, ushort length){}
    // 读 读写寄存器
    public async Task<ushort[]> Request_03(ushort startIndex, ushort length){}
    // 读 只读寄存器
    public async Task<ushort[]> Request_04(ushort startIndex, ushort length){}
    // 写 读写一个线圈
    public async Task<ADUMessage> Request_05(ushort startIndex, bool coil){}
    // 写 读写一个寄存器
    public async Task<ADUMessage> Request_06(ushort startIndex, ushort register){}
    // 写 读写多个线圈
    public async Task<ADUMessage> Request_0f(ushort startIndex, bool[] coils){}
    // 写 读写多个寄存器
    public async Task<ADUMessage> Request_10(ushort startIndex, ushort[] registers){}
  • 为了便于观察消息,我在请求发出后和接到响应后都打印了出来。
      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));
    PrintBytes(bytes.Take(msgLength).ToArray(), "响应");
    }
  • 线圈存储时使用bool值,传输时使用bit,而且还是按位的,这需要用到位运算符。所以需要一对转换方法
      public bool[] BytesToBools(byte[] bytes,ushort dataNumber){}
    public byte[] BoolToBytes(bool[] bools){}

测试类

我们还需要一个界面区使用这个协议,所以还需要一个测试类。

命令行程序的话,就是使用while循环了,在循环中接收指令

private static async Task StartClient(string[] args)
{
//其他代码... 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)
{ }
}
}

完整代码

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 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;
} 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
{
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)));
}
else if (WebSocket != null)
{
await WebSocket.SendAsync(new ArraySegment<byte>(ADUMessage.Serialze(request)), WebSocketMessageType.Binary, true, CancellationToken.None);
}
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 == 1)
{
//webModbusServer.store.WriteCoilDiscretes(0, new bool[] { true, true });
//webModbusServer.store.CoilInputs[0] = true;
//webModbusServer.store.CoilInputs[1] = true;
StartServer(args[0]);
}
//客户端
else
{
Task.Run(async () =>
{
await StartClient(args);
}).Wait();
}
} 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}");
//给这个客户端开一个聊天线程
//操作系统将会根据游客端口对应表将控制权交给对应游客线程
//StartChat(client);
StartModbus(client);
}
}).Wait();
} private static async Task StartClient(string[] args)
{
int clientPort = Convert.ToInt32(args[0]);
int serverPort = Convert.ToInt32(args[1]);
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)
{ }
}
} 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();
}
public static void PrintBools(bool[] bools)
{
for (int i = 0; i < bools.Length; i++)
{
Console.Write(bools[i] + " ");
}
Console.WriteLine();
}
}
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);
} // 可以继续添加其他方法来支持更多数据类型的大端写入
}

程序所需命令行参数的一种方式是在项目文件种指定,这在调试时比较方便

<PropertyGroup>
<StartArguments>5234</StartArguments>
</PropertyGroup>

可以注意到ModbusTcp消息的解析和Tcp没有什么关系。因此,验证了服务器和客户端的正确性之后,就可以把Tcp连接改为WebSocket连接了。

基于WebSocket的modbus通信(一)- 客户端的更多相关文章

  1. 【转】iOS基于WebSocket的聊天机制

    原文网址:http://www.jianshu.com/p/21d9b3b94cfc WebSocket 的使得浏览器提供对 Socket 的支持成为可能,从而在浏览器和服务器之间提供了一个基于 TC ...

  2. 分享基于 websocket 网页端聊天室

    博客地址:https://ainyi.com/67 有一个月没有写博客了,也是因为年前需求多.回家过春节的原因,现在返回北京的第二天,想想,应该也要分享技术专题的博客了!! 主题 基于 websock ...

  3. 基于 WebSocket 的 MQTT 移动推送方案

    WebSphere MQ Telemetry Transport 简介 WebSphere MQ Telemetry Transport (MQTT) 是一项异步消息传输协议,是 IBM 在分析了他们 ...

  4. SpringBoot基于websocket的网页聊天

    一.入门简介正常聊天程序需要使用消息组件ActiveMQ或者Kafka等,这里是一个Websocket入门程序. 有人有疑问这个技术有什么作用,为什么要有它?其实我们虽然有http协议,但是它有一个缺 ...

  5. 基于 websocket 的多端桥接平台

    我们现在的业务是基于新闻客户端实现的,都要经过新闻客户端的环境,进行前后端数据上的交互.但是我们在调试过程中,非常的不方便. 通常使用的工具有:modheader, postman, fiddler ...

  6. java 从零开始手写 RPC (01) 基于 websocket 实现

    RPC 解决的问题 RPC 主要是为了解决的两个问题: 解决分布式系统中,服务之间的调用问题. 远程调用时,要能够像本地调用一样方便,让调用者感知不到远程调用的逻辑. 这一节我们来学习下如何基于 we ...

  7. 网络编程-基于Websocket聊天室(IM)系统

    目录 一.HTML5 - Websocket协议 二.聊天室(IM)系统的设计 2.1.使用者眼中的聊天系统 2.2.开发者眼中的聊天系统 2.3.IM系统的特性 2.4.心跳机制:解决网络的不确定性 ...

  8. 为美多商城(Django2.0.4)添加基于websocket的实时通信,主动推送,聊天室及客服系统

    原文转载自「刘悦的技术博客」https://v3u.cn/a_id_67 websocket是个啥? webSocket是一种在单个TCP连接上进行全双工通信的协议 webSocket使得客户端和服务 ...

  9. 基于 WebSocket 实现 WebGL 3D 拓扑图实时数据通讯同步(二)

    我们上一篇<基于 WebSocket 实现 WebGL 3D 拓扑图实时数据通讯同步(一)>主要讲解了如何搭建一个实时数据通讯服务器,客户端与服务端是如何通讯的,相信通过上一篇的讲解,再配 ...

  10. Socket.IO – 基于 WebSocket 构建跨浏览器的实时应用

     Socket.IO 是一个功能非常强大的框架,能够帮助你构建基于 WebSocket 的跨浏览器的实时应用.支持主流浏览器,多种平台,多种传输模式,还可以集合 Exppress 框架构建各种功能复杂 ...

随机推荐

  1. js 按照字母进行分组

    前言 js 按照字母进行分组的一个实例. 正文 var list = [ { 'name' : '张三', 'py' : 'zhnagsan' }, { 'name' : '李四', 'py' : ' ...

  2. 一种关于低代码平台(LCDP)建设实践与设计思路

    简介: 作者在负责菜鸟商业中心CRM系统开发过程中发现有一个痛点:业务线很多,每个业务线对同一个页面都有个性化布局和不同的字段需求,而他所在的团队就3个人,那么在资源有限的情况下该如何支撑呢?本文就降 ...

  3. 从图森未来的数据处理平台,看Serverless工作流应用场景

    4月,阿里云Serverless工作流正式商业化,这是一款用于协调多个分布式任务执行的全托管 Serverless 云服务.产品致力于简化开发和运行业务流程所需要的任务协调.状态管理以及错误处理等繁琐 ...

  4. 从“预见”到“遇见” | SAE 引领应用步入 Serverless 全托管新时代

    简介: 阿里云 Serverless 应用引擎(简称 SAE)初衷是让客户不改任何代码,不改变应用部署方式,就可以享受到微服务+K8s+Serverless 的完整体验,开箱即用免运维.作为业界首款面 ...

  5. 技术解读 | 智能开放搜索CTR预估模型

    ​简介:本文介绍开放搜索CTR预估模型在个性化排序中的应用与实践 如何评价搜索排序效果? 搜索是用户触达信息最简单直接的方式,是APP.网页必备的功能.如何评价并提升搜索效果一直是搜索领域的常见问题. ...

  6. [Go] Golang并发控制: WaitGroup 含义和常规用法

    一个 WaitGroup 等待一个 goroutine 集合的结束. 主 goroutine 调用 Add 设置需要等待的 goroutine 数量. 接下来每个 goroutine 运行并在结束时调 ...

  7. dotnet 如何将 Microsoft.Maui.Graphics 对接到 UNO 框架

    本文将和大家介绍如何将 Microsoft.Maui.Graphics 对接到 UNO 框架里面.一旦完成 Microsoft.Maui.Graphics 对接,即可让 UNO 框架复用现有的许多绘制 ...

  8. pnpm的基本原理及快速使用

    基本原理 前置知识:软件链接与硬链接 软链接(符号链接Symbolic link):是一类特殊的文件, 其包含有一条以绝对路径或者相对路径的形式指向其它文件或者目录的引用.在window快捷方式上和其 ...

  9. 普冉PY32系列(十五) PY32F0系列的低功耗模式

    目录 普冉PY32系列(一) PY32F0系列32位Cortex M0+ MCU简介 普冉PY32系列(二) Ubuntu GCC Toolchain和VSCode开发环境 普冉PY32系列(三) P ...

  10. dotnet build error CS5001: Program does not contain a static 'Main' method suitable for an entry point

    前言 Docker环境编译.Net6项目,出现诡异的CS5001 Program does not contain a static 'Main' method suitable for an ent ...