C# 開發ModBus的服務器程序 實現ModBus數據總站 搭建自定義的Modbus服務器 同時支持tcp和rtu


前言


 本文將使用一個NuGet公開的組件技術來實現一個ModBus TCP的服務器端數據引擎,方便的實現接收來自各種設備的數據。並且該服務器模擬真實的設備,包含了數據池功能,可以接受來自任何支持Modbus tcp的客戶端進行讀寫數據。C#實現的客戶端類請參考下面這篇文章:http://www.cnblogs.com/dathlin/p/7885368.html  可以進行一些客戶端服務器的聯合調試。

nuget地址:https://www.nuget.org/packages/HslCommunication/       nuget     下載

github地址:https://github.com/dathlin/HslCommunication      fork      star                     如果喜歡可以star或是fork,還可以打賞支持。

聯系作者及加群方式(激活碼在群里發放):http://www.hslcommunication.cn/Cooperation

 

在Visual Studio 中的NuGet管理器中可以下載安裝,也可以直接在NuGet控制台輸入下面的指令安裝:

Install-Package HslCommunication

NuGet安裝教程  http://www.cnblogs.com/dathlin/p/7705014.html

 

下載地址


此處提供一個服務器的Demo軟件,下載解壓就可以直接運行,這個Demo的源代碼也在上面的示例,界面如下:后續新的版本可能會有點小區別

https://github.com/dathlin/HslCommunication/raw/master/Download/ModbusTcpServer.zip   這個地址的服務器軟件永遠都是最新的,會不停的更新。

如果您需要一個測試的客戶端,包括了tcp和rtu的都可以,下載下面的Demo即可,支持各種配置信息

HslCommunicationDemo.zip

 

 

隨便聊聊


使用本組件可以快速搭建一個高性能的MODBUS TCP總站,當我們一個上位機需要讀取100台西門子PLC設備(此處只是舉個例子,凡是都是使用Modbus tcp的都是一樣的)的時候,你采用服務器主動去請求100台設備的機制對性能來說是個極大的考驗,如果開100個線程去輪詢100台設備,那么性能損失將是非常大的,更不用說再增加設備,如果搭建Modbus tcp服務器,就可以完美的解決性能問題,因為連接的壓力將會平均分攤給每一台PLC,服務器端只要新增一個時間戳就可以知道客戶端有沒有連接上。

我們在100台PLC里都增加發送Modbus tcp方法,將數據發送到服務器的ip和端口上去,服務器根據站號來區分設備。這樣就可以搭建一個高性能總站。 

 

關於數據池


本服務器端擁有兩個數據池,線圈數據池和寄存器數據池,任何客戶端包括服務器本身都可以對數據池進行讀寫數據,比如PLC1將所有的數據發送到寄存器地址0-99上,PLC2將數據發送到100-199上。

  • 線圈數據池         模擬了真實的數據讀寫,自動解析指令並返回數據
  • 離散輸入數據池    服務器端允許讀寫,對客戶端來說僅僅支持讀,功能碼02
  • 寄存器數據池     模擬了真實的數據讀寫
  • 輸入寄存器數據池   服務器端允許讀寫,對客戶端來說僅僅支持讀,功能碼04

四個數據池的地址范圍都是0-65535,起始地址是從0開始

 

Reference


 

ModBus組件所有的功能類都在 HslCommunication.ModBus命名空間,所以再使用之前先添加

using HslCommunication.ModBus;

How to Use


 如果想快速的搭建一個Modbus-Tcp的服務器,只要2行代碼即可,實例化,啟動,而下面的例子稍微復雜了一點,額外配置了日志記錄器,綁定了一個數據接收的方法,每當客戶端進行數據交互,就會觸發,可用於實現自定義功能的Modbus-Tcp服務器

 

        private HslCommunication.ModBus.ModbusTcpServer busTcpServer;

        private void button1_Click( object sender, EventArgs e )
        {
            if(!int.TryParse(textBox2.Text,out int port))
            {
                MessageBox.Show( "端口輸入不正確!" );
                return;
            }



            try
            {

                busTcpServer = new HslCommunication.ModBus.ModbusTcpServer( );
                busTcpServer.LogNet = new HslCommunication.LogNet.LogNetSingle( "logs.txt" );
                busTcpServer.LogNet.BeforeSaveToFile += LogNet_BeforeSaveToFile;
                busTcpServer.OnDataReceived += BusTcpServer_OnDataReceived;
                busTcpServer.ServerStart( port );

                button1.Enabled = false;
                panel2.Enabled = true;
            }
            catch (Exception ex)
            {
                MessageBox.Show( ex.Message );
            }
        }

        private void BusTcpServer_OnDataReceived( byte[] modbus )
        {
            if (InvokeRequired)
            {
                BeginInvoke( new Action<byte[]>( BusTcpServer_OnDataReceived ), modbus );
                return;
            }
            textBox1.AppendText( "接收數據:" + HslCommunication.BasicFramework.SoftBasic.ByteToHexString(modbus) + Environment.NewLine );
        }

        /// <summary>
        /// 當有日志記錄的時候,觸發,將日志信息也在主界面進行輸出
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void LogNet_BeforeSaveToFile( object sender, HslCommunication.LogNet.HslEventArgs e )
        {
            if(InvokeRequired)
            {
                BeginInvoke( new Action<object, HslCommunication.LogNet.HslEventArgs>( LogNet_BeforeSaveToFile ), sender, e );
                return;
            }

            textBox1.AppendText( e.HslMessage.ToString( ) + Environment.NewLine );
        }

 

        

RTU支持


 當客戶端進行連接了一個串口線后,也可以方便的讓服務器的支持同時支持串口訪問,

busTcpServer.StartSerialPort( "Com3" );

 默認的串口參數是9600波特率。8位數據位,無奇偶校驗,1位停止位,當然也可以自行指定波特率

busTcpServer.StartSerialPort( "Com3",9600 );

 還可以傳入一個初始化的委托方法,可以實現任意的支持。

 

創建數據訂閱


 當客戶端進行發送讀寫指定時,如果你使用了OnDataReceived事件,都會觸發,當然可能這並不是你需要的,如果我們相對一個地址上的數據進行監視,也就當有客戶端寫入的時候觸發,或者是當這個數據更改了的時候觸發,那么我們可以創建自己的數據訂閱器:

        private void button2_Click( object sender, EventArgs e )
        {
            // 點擊數據監視
            ModBusMonitorAddress monitorAddress = new ModBusMonitorAddress( );
            monitorAddress.Address = ushort.Parse( textBox6.Text );
            monitorAddress.OnChange += MonitorAddress_OnChange;
            monitorAddress.OnWrite += MonitorAddress_OnWrite;
            busTcpServer.AddSubcription( monitorAddress );
            button2.Enabled = false;
        }

        private void MonitorAddress_OnWrite( ModBusMonitorAddress monitor, short value )
        {
            // 當有客戶端寫入時就觸發
        }

        private void MonitorAddress_OnChange( ModBusMonitorAddress monitor, short befor, short after )
        {
            // 當該地址的值更改的時候觸發
            if(InvokeRequired)
            {
                BeginInvoke( new Action<ModBusMonitorAddress, short, short>( MonitorAddress_OnChange ), monitor, befor, after );
                return;
            }

            textBox9.Text = after.ToString( );

            label11.Text = "寫入時間:" + DateTime.Now.ToString( ) + " 修改前:" + befor + " 修改后:" + after;
        }

 

 

當然,你可以只使用其中的一種數據訂閱,比如上述的操作,一旦有客戶端寫入地址0x01的數據(無論是寫入一個寄存器還是批量),那么馬上會觸發如下的方法。注意,從服務器自身寫入的數據不會觸發,所有的訂閱都可以關聯同一個方法,根據地址的不同來區分。

 

 

 

特別說明:

服務器只負責接受Modbus TCP協議的數據,無論客戶端發了讀寫指令,都會觸發接收事件。在服務器端有一個數據池,存儲了線圈數據和寄存器數據,模擬了一個真實的設備,允許客戶端使用Modbus tcp協議對服務器進行數據讀寫,並返回真實的數據,如果設備往這個服務器的寄存器地址寫了100,那么服務器端或者其他客戶端去讀取寄存器100地址的值的時候,那么也是100.

 

下面演示了一些簡單的數據讀寫,用於服務器端進行操作的。

 

            bool Coil100 = busTcpServer.ReadCoil( "100" );                  // 讀線圈100的值
            bool[] Coil100_109 = busTcpServer.ReadCoil( "100", 10 );        // 讀線圈數組
            short Short100 = busTcpServer.ReadInt16( "100" );       // 讀取寄存器值
            ushort UShort100 = busTcpServer.ReadUInt16( "100" );    // 讀取寄存器ushort值
            int Int100 = busTcpServer.ReadInt32( "100" );             // 讀取寄存器int值
            uint UInt100 = busTcpServer.ReadUInt32( "100" );          // 讀取寄存器uint值
            float Float100 = busTcpServer.ReadFloat( "100" );       // 讀取寄存器Float值
            long Long100 = busTcpServer.ReadInt64( "100" );          // 讀取寄存器long值
            ulong ULong100 = busTcpServer.ReadUInt64( "100" );       // 讀取寄存器ulong值
            double Double100 = busTcpServer.ReadDouble( "100" );    // 讀取寄存器double值

            busTcpServer.WriteCoil( "100", true );              // 寫線圈的通斷
            busTcpServer.Write( "100", (short)5 );      // 寫入short值
            busTcpServer.Write( "100", (ushort)45678 ); // 寫入ushort值
            busTcpServer.Write( "100", 12345667 );      // 寫入int值
            busTcpServer.Write( "100", (uint)12312312 );// 寫入uint值
            busTcpServer.Write( "100", 123.456f );      // 寫入float值
            busTcpServer.Write( "100", 1231231231233L );// 寫入long值
            busTcpServer.Write( "100", 1212312313UL );  // 寫入ulong值
            busTcpServer.Write( "100", 123.456d );      // 寫入double值

 

讀寫離散量:

 bool value_100 = busTcpServer.ReadDiscrete( "100" ); // 讀取地址100的離散量
 bool[] value_100_109 =  busTcpServer.ReadDiscrete( "100", 10  ); // 讀取數據



busTcpServer.WriteDiscrete( "100", true);    // 地址100為true
busTcpServer.WriteDiscrete( "100", new bool[]{true,true}); // 地址100-101為true

  

讀寫輸入寄存器:

輸入寄存器的讀寫方式和寄存器的是一致的,只是地址改一下就好了,也即使用富地址的方式,舉個例子,寫輸入寄存器地址100為123

 busTcpServer.Write( "x=4;100", (short)123 );

之前寫100,現在改成"x=4;123"   x=4也即是使用功能碼04,那么之前寄存器的地址100等效於"x=3;100"

其他格式的數據參照這個即可。

 

過濾客戶端:

支持對客戶端的IP地址過濾,禁止不信任的Ip登錄

modbusTcpServer.SetTrustedIpAddress( new List<string>( "192.168.0.100", "192.168.0.101") );

 

 如果要移除限制,重新恢復所有的客戶端登錄,那么按照如下操作

modbusTcpServer.SetTrustedIpAddress( null );

 

 

主動連接客戶端:

本服務器支持主動連接客戶端,然后再進行數據交互,這種情況在有些工業上應用還是很廣泛的

OperateResult connect = busTcpServer.ConnectHslAlientClient( "192.168.0.111", "10000", "12345678901" );
                    if (connect.IsSuccess)
                    {
                        MessageBox.Show( "連接成功!" );
                    }
                    else
                    {
                        MessageBox.Show( "連接失敗!原因:" + connect.Message );
                    }
                }

需要制定對方的IP地址,端口號,以及唯一識別碼,目前使用的注冊包協議為HslAlien協議,過幾天專門寫一篇這個協議的博文,如果需要連接其他定制的客戶端,請聯系作者進行定制。 

 

 

注意:

數據串的第7個字節為Modbus的站號信息,如下界面是FF,也即255,可以以此來區分不同的設備發來的數據信息,所以此處一個服務器實例掛的最大客戶端數為256台設備,前兩個字節為消息的頭序列,如果設備可以固定消息頭,用這個來標識設備的話,就可以區分65536台設備。

 

創作不易,感謝打賞:

 

參考鏈接:

如果你對MODBUS TCP不熟悉,那么請參照如下地址,我就是參照該地址的博客開發的代碼:

http://blog.csdn.net/thebestleo/article/details/52269999


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM