协议实战 · 2026-03-26

C#上位机实现ModbusTCP服务器

大家好!我是付工。

上位机在开发完成后,如果需要提供数据接口,供第三方平台进行获取。

常用的方式有以下几种:

1、数据库方式

2、WebApi方式

3、ModbusTCP方式

今天这篇文章给大家分享一下,如何基于EasyModbusTCP来实现ModbusTCP服务器搭建。

PLC采集

为了更好展示数据,我们写个简单的PLC采集程序,如果没有PLC,可以使用随机数。

1、PLC采集代码如下:

private void FrmMain_Load(object sender, EventArgs e){
    //实例化PLC对象
    Plc plc = new Plc(CpuType.S71200, "192.168.2.151", 0, 0);
    try
    {
        //建立PLC连接
        plc.Open();
        Task.Run(() =>
        {
           while (true)
            {
                //读取PLC数据
                PlcData plcData = plc.ReadClass<PlcData>(1, 0);
                //更新界面
                this.Invoke(new Action(() =>
                {
                    this.lbl_PressureIn.Text = plcData.PressureIn.ToString("f2");
                    this.lbl_PressureOut.Text = plcData.PressureOut.ToString("f2");
                    this.lbl_TempIn.Text = plcData.TempIn1.ToString("f2");
                    this.lbl_TempOut.Text = plcData.TempOut.ToString("f2");
                }));
                //加点延时,不然太快了
                Thread.Sleep(500);
            }
        });
    }
    catch (Exception ex)
    {
        MessageBox.Show("PLC连接失败:" + ex.Message);
    }
}

2、运行结果如下:

原文配图 1

ModbusTCP服务器

接下来我们实现ModbusTCP服务器部分。

1、首先通过Nuget安装EasyModbusTCP库。

原文配图 2

2、我们创建一个ModbusTCPServer类,这个类主要是对库进行封装,代码如下:

public class ModbusTCPServer{
    private ModbusServer modbusServer;
    private bool isRunning = false;
    public event EventHandler<string> OnLogMessage;
    public event EventHandler<string> OnDataChanged;
    public bool IsRunning { get { return isRunning; } }
    public ModbusTCPServer(string ip, int port = 502)
    {
        modbusServer = new ModbusServer();
        modbusServer.LocalIPAddress = IPAddress.Parse(ip);
        modbusServer.Port = port;
    }
    public void StartServer()
    {
        if (!isRunning)
        {
            modbusServer.Listen();
            modbusServer.HoldingRegistersChanged += ModbusServer_HoldingRegistersChanged;
            isRunning = true;
            OnLogMessage?.Invoke(this, "Modbus服务器启动成功");
        }
        else
        {
            OnLogMessage?.Invoke(this, "Modbus服务器已经运行");
        }
    }
    public void StopServer()
    {
        if (isRunning)
        {
            modbusServer.HoldingRegistersChanged -= ModbusServer_HoldingRegistersChanged;
            isRunning = false;
            modbusServer.StopListening();
            OnLogMessage?.Invoke(this, "Modbus服务器停止成功");
        }
        else
        {
            OnLogMessage?.Invoke(this, "Modbus服务器未运行");
        }
    }
    private void ModbusServer_HoldingRegistersChanged(int registerAddress, int numberOfRegisters)
    {
        //这里可以监测数据变化
        OnDataChanged(this, "寄存器地址:" + registerAddress + ",寄存器数量:" + numberOfRegisters);
    }
}

3、我们最常用的是保持型寄存器,因此,我们封装一些读写保持型寄存器的方法,代码如下:

public short[] GetHoldingRegisters(int startAddress, int quantity){
    if (startAddress + quantity <= modbusServer.holdingRegisters.localArray.Length)
    {
        short[] result = new short[quantity];
        Array.Copy(modbusServer.holdingRegisters.localArray, startAddress + 1, result, 0, quantity);
        return result;
    }
    return null;
}
public short GetHoldingRegisterShort(int startAddress){
    return GetHoldingRegisters(startAddress, 1).FirstOrDefault();
}
public float GetHoldingRegisterFloat(int startAddress){
    short[] values = GetHoldingRegisters(startAddress, 2);
    if (values != null)
    {
        List<byte> bytes = new List<byte>();
        bytes.AddRange(BitConverter.GetBytes(values[1]));
        bytes.AddRange(BitConverter.GetBytes(values[0]));
        return BitConverter.ToSingle(bytes.ToArray(), 0);
    }
    return 0.0f;
}
public void SetHoldingRegisters(int startAddress, float value){
    byte[] buffer = BitConverter.GetBytes(value);
    short[] values = new short[2]
    {
        BitConverter.ToInt16(buffer, 2),
        BitConverter.ToInt16(buffer, 0)
    };
    SetHoldingRegisters(startAddress, values);
}
public void SetHoldingRegisters(int startAddress, short value){
    SetHoldingRegisters(startAddress, new short[] { value });
}
public void SetHoldingRegisters(int startAddress, short[] values){
    if (startAddress + values.Length <= modbusServer.holdingRegisters.localArray.Length)
    {
        Array.Copy(values, 0, modbusServer.holdingRegisters.localArray, startAddress + 1, values.Length);
    }
}

4、实现启动服务器和关闭服务器,代码如下:

private ModbusTCPServer server = null;
private void btn_StartServer_Click(object sender, EventArgs e){
    try
    {
        server = new ModbusTCPServer(this.txt_ServerIp.Text, Convert.ToInt32(this.txt_ServerPort.Text));
        server.StartServer();
    }
    catch (Exception)
    {
        MessageBox.Show("服务器开启失败");
    }
}
private void btn_StopServer_Click(object sender, EventArgs e){
    this.server?.StopServer();
}

5、将PLC数据写入到寄存器中,我们需要实时将数据写入PLC寄存器中,可以单独创建一个线程或定时器来实现,我们这里直接就在读取到PLC数据之后写入,在读取完成加一段代码即可,代码如下:

if (server!=null&&server.IsRunning){
    server.SetHoldingRegisters(0, plcData.PressureIn);
    server.SetHoldingRegisters(2, plcData.PressureOut);
    server.SetHoldingRegisters(4, plcData.TempIn1);
    server.SetHoldingRegisters(6, plcData.TempOut);
}

ModbusPoll测试

1、程序运行后,开启服务,运行如下:

原文配图 3

正常应该使用502端口,因为我电脑502端口被占用了,所以我这里使用503端口。

2、我们使用ModbusPoll连接测试结果如下:

原文配图 4

整体总结

1、ModbusTCP服务器的本质就是TCP服务器。

2、在通信库里开辟了四个数组,分别对应4个存储区,然后实现了报文的处理。

3、往服务器里写数据的本质就是对数据处理后,根据索引给数组赋值。

4、如果需要检测对方写入数据,可以在HoldingRegistersChanged中进行处理。

本文为原文文字快照,已收录 4 张技术配图;二维码与推广图未收录。
标签:#ModbusTCP #S7-1200