首页 > 其他分享 >基于WebSocket的modbus通信(一)- 服务器

基于WebSocket的modbus通信(一)- 服务器

时间:2024-05-31 19:34:39浏览次数:29  
标签:WebSocket ushort request modbus new 服务器 byte data public

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

效果

image

image

ModBusTCP协议报文

ModBusTCP协议报文比较复杂,主要是区分了3组类型

  • 请求和响应
  • 读和写
  • 多个数据和单个数据

理论上将报文格式的种类从1种变成了8种。
但是在响应读数据时,没有区分数据量,而是到请求中去查找,听说是为了减少传输量。因此实际格式种类是3组6种。

  1. 请求读数据报文

    事务标识符 协议标识符 报文长度 单元标识符 功能码 起始地址 读取数量
    字段长度 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. 响应读数据报文

    事务标识符 协议标识符 报文长度 单元标识符 功能码 数据字节数 数据
    2 byte 2 byte 2 byte 1 byte 1 byte 1 byte n byte

    值得注意的是,响应报文中没有包含请求的数据数量,只能从请求中获得。比如是7个线圈还是8个线圈是看不出来的。
    这其实就限制了程序结构,应该在同一个上下文中声明请求和响应变量。

  3. 请求写单个数据报文

    事务标识符 协议标识符 报文长度 单元标识符 功能码 起始地址 数据
    2 byte 2 byte 2 byte 1 byte 1 byte 2 byte 2 byte
  4. 响应写单个数据报文

    事务标识符 协议标识符 报文长度 单元标识符 功能码 起始地址 数据
    2 byte 2 byte 2 byte 1 byte 1 byte 2 byte 2 byte

    请求写单个数据的响应实际上是原样返回请求。
    写1位线圈和写16位寄存器都传输了2个字节,这很奇怪。
    原来是ModBusTCP定义写单个线圈用0xff00表示1,0x0000表示0。

  5. 请求写多个数据报文

    事务标识符 协议标识符 报文长度 单元标识符 功能码 起始地址 数量 字节数 数据
    2 byte 2 byte 2 byte 1 byte 1 byte 2 byte 2 byte 1 byte n byte

    请求写多个数据时,如果是线圈,现在又变成了一位代表一个数据,而不是原来的2字节代表单个数据
    还需要注意的是modbus采用大端传输,意味着一个字节中如果写了四个线圈,要从右向左数。

  6. 响应写多个数据报文

    事务标识符 协议标识符 报文长度 单元标识符 功能码 起始地址 数量
    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#实现时可以使用BinaryReaderBinaryWriter来处理接收到的和要传输的字节流。
    但这两个类默认都是按照小端字节序来处理数据。比如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,ushort,request,modbus,new,服务器,byte,data,public
From: https://www.cnblogs.com/ggtc/p/18211583

相关文章

  • WiFi串口服务器与工业路由器:局域网应用的协同之力
    在工业物联网(IIoT)迅猛发展的当下,局域网(LAN)作为连接工业设备与数据中心的桥梁,其重要性日益凸显。WiFi串口服务器与工业路由器作为局域网中的关键组件,以其独特的性能和功能,为传统行业的数字化转型提供了强有力的支持。本文将深入探讨WiFi串口服务器与工业路由器如何协同工作,以实现......
  • django项目部署到服务器
     1.安装python[root@dsc1nginx]#python-VPython3.6.5 2.安装数据库yuminstallmysqlmysql-develyuminstallsqlite-devel 3.安装django[root@dsc1mydjango]#pip3installdjangopipisconfiguredwithlocationsthatrequireTLS/SSL,howeverthesslmodu......
  • 两台windowserver服务器配置Redis哨兵集群
    十年河东,十年河西,莫欺少年穷学无止境,精益求精redis下载地址:https://github.com/tporadowski/redis/releases 这里选择压缩版,不选择安装版1、集群环境 主机master:局域网IP  172.27.40.27从机slave:局域网IP  172.27.40.29 2、修改主从相关配置-两台服务器均需修改......
  • 服务器设置
    3090查看anaconda位置安装anaconda回车下一步依旧回车长按输入yes回车键等待下载输入yes安装成功!!系统环境配置查看安装位置编辑环境变量文件执行环境变量脚本文件出现base解决每次登陆时都需要source~/.bashrc才能使得进入conda的问题vi~/.bash_p......
  • 工控现场什么情况下会采用Modbus转Profinet网关
    工控现场什么情况下会采用Modbus转Profinet网关一、原因:工控现场需要将Modbus转换为Profinet协议,这是由于在工业控制领域中,不同设备之间的通信协议多样,而Modbus和Profinet分别代表着两种不同的通信协议。Modbus是一种最常见的工业通信协议,但其速度较慢,数据量有限,在一些需要快速......
  • 在windows服务器上搭建FileZilla服务端
    最近接到个需求,需要搭建图片文件服务器给后端程序读取目录和操作图片这个需求分为几个步骤:1.扩展windwos虚拟机磁盘空间考虑到图片操作的便利性,这里选用windows系统做图片服务器,但是由于图片比较大,原有windows主机磁盘不够,所以第一步就是扩展windows虚拟机磁盘空间2.在windows......
  • 【测评】雨云香港三区云服务器,2核2G 5兆,仅需38元/月(文末有福利)
    雨云香港三区云服务器,高性能的AMD®EPYC处理器+企业级NVMESSD高性能云服务器。2核2G10兆400G防御,仅需38元/月,年付7折仅319.2元/年。官网:https://www.rainyun.com(优惠码:lz932使用优惠码注册后绑定微信可获得多个优惠券)本次测评服务器配置如下:CPU:2核内存:2G硬盘......
  • 基恩士PLC与ModbusTCP转Profibus网关实现与激光设备的高效连接
    本文将探讨如何通过使用基恩士PLC以及无锡耐特森ModbusTCP转Profibus网关来实现与激光设备的高效连接。在当今工业自动化领域,不同厂商的硬件设备和软件系统之间的互联互通性成为了提高生产效率、实现智能制造的关键因素。其中,可编程逻辑控制器(PLC)作为工业控制的核心,与其他设备的通......
  • RFS_Server_05 云服务器配置及域名解析
    操作描述:在阿里云购买两项服务【云服务器ECS】和【域名】后,配置云服务器,注册域名,最终实现通过域名访问服务器,需要做以下事情:域名:1.ICP域名备案。此服务由云服务商代理,按照指引填写主体信息、域名用途等信息,等待客服联系通过审核即可。(此次遇到的阿里云客服态度非常不耐烦......
  • golang开发 gorilla websocket的使用
    很多APP都需要主动向用户推送消息,这就需要用到长连接的服务,即我们通常提到的websocket,同样也是使用socket服务,通信协议是基本类似的,在go中用的最多的、也是最简单的socket服务就是gorilla/websocket,它有21.1K的star,足以说明它的受欢迎程度,它的github地址是https://github.com/g......