轉自:http://blog.csdn.net/thebestleo/article/details/52269999
首先我要說明一下,本人新手一枚,本文僅為同樣熱愛學習的同學提供參考,有不
對的地方還請大家熱心指出,本文只起到一個拋磚引玉的作用,希望看到本文的同學可
以從中學習到少許知識,也希望可以跟各位讀者成為朋友,多多交流,使學習不再孤單
寂寞。
由於本文太長,顧分為兩部分,第二部分連接
初識Modbus TCP/IP-------------C#編寫Modbus TCP客戶端程序(二)
http://blog.csdn.net/thebestleo/article/details/52331976
廢話少說,我們直接上干的,學習知識,第一個是收集和查閱資料,這個是必須的。
1、Modbus官方網站:http://www.modbus.org/
2、Modbus協議規范英文原版:
http://download.csdn.net/download/thebestleo/9609480
3、Modbus協議規范中文版:
http://download.csdn.net/download/thebestleo/9609620
4、Modbus通訊的TCP實現指南:
http://download.csdn.net/download/thebestleo/9609646
5、Modbu TCP服務器測試工具:
http://download.csdn.net/download/thebestleo/9609665
6、Modbu TCP客戶端測試工具:
http://download.csdn.net/download/thebestleo/9609676
7、網絡數據分析軟件Wireshark:
http://download.csdn.net/download/thebestleo/9613131
8、文章中Modbus Slave的設置文件,打包下載一下,便於你的測試
http://download.csdn.net/detail/thebestleo/9614679
9、本文最終所寫成的C#的Modbus TCP客戶端程序
http://download.csdn.net/download/thebestleo/9614682
下面傳一張modbus官網上的一張圖片,是一個Modbus TCP的工具包,跟我上面給出的類似
看見沒,上述資料價值500美元,好了,我的工作到此結束,剩下的不給錢不說了
開個玩笑,我們繼續。說實話,上述的資料我也沒有特別仔細的看過,
(等找個時間好好看看)
這里我只是簡單的理解一下Modbus TCP/IP協議的內容,就是去掉了modbus協議
本身的CRC
校驗,增加
了MBAP 報文頭。(這里只是簡單的理解,深入之后可能會有更
多的東西需要學習,但
為了可以快速入門,我們先按照這個思路往下走)。
我們首先來看一下,MBAP 報文頭都包括了哪些信息和內容
事務元標識符(2個字節):
用於事務處理配對。在響應中,MODBUS服務器復制請求的事務處理標識符。
這里在以太網傳輸中存在一個問題,就是先發后至,我們可以利用這個事務處理
標識符做一個TCP序列號,
來防止這種情況所造成的數據收發錯亂(這里我們先不
討論這種情況,這個事務處理標識符我們統一使用0x00,0x01)
協議標識符(2個字節):modbus協議標識符為0x00,0x00
長度(2個字節):長度域是下一個域的字節數,包括單元標識符和數據域。
單元標識符(1個字節):這個好像是個站號,文檔中的說明沒怎么看懂,有明白的
可以留言告訴我。
根據上面的思路很容易理解,在modbus報文前,加上表的MBAP報文頭,再去掉modbus
報文中
的CRC校驗
就可以形成modbus TCP的報文,那么modbus報文格式是什么樣的呢?
modbus報文時
根據不同的功能碼,
報文格式的形式是不同的,下面我們具體用一個C#
的例程來說明一下
Modbus TCP報文的數據組成和傳輸
方法。(這里很多同學會說,我
對modbus不了解,對C#更
是知道的更少了,不要緊,只要你有一點C語
言和串口通信的
基礎,其他的你盡管抄襲過來,
日后慢慢的消化理解,很多老師在教育學生的時候總是
鼓勵什么獨立思考,嚴禁抄襲什么的,
再我看來抄別人的並沒有什么錯,學習嗎,就是
站在前人的肩膀
上看世界,很多東西你沒有
那個時間去研究,還有很多東西即使你有那
個時間你也研究不出來,老師上
課教的是啥,不
都是抄襲前人的科研成果嗎,要是什么
都需要自己研究,還用老師教什么。)
所以我在這里以一種開放的態度,撰寫了本文,希望大家能相互學習進步。
言歸正傳,我們來用C#寫一個Modbus TCP的客戶端程序,並使用Modbus Slave
這個軟件對程序的功能進行測試
1、首先,作為客戶端程序,我們要先針對服務器IP和端口建立一個連接,IP地址根據
Modbus Slave,所在電腦的IP來確定,Modbus TCP的端口號是眾所周知的502
(為了保持程序的完整性,我把第一步的整個程序都貼出來,避免造成歧義。)
- using System;
- using System.Windows.Forms;
- using System.Net.Sockets;
- using System.Threading;
- using System.Net;
-
- namespace Modbus_TCP_Client
- {
- public partial class Form1 : Form
- {
- public Socket newclient;
- public bool Connected;
- public Thread myThread;
- public delegate void MyInvoke(string str);
- public Form1()
- {
- InitializeComponent();
- }
-
- private void exit_Click(object sender, EventArgs e)
- {
- Application.Exit();
- }
-
- public void Connect()
- {
- byte[] data = new byte[1024];
-
- string ipadd = serverIP.Text.Trim();
- int port = Convert.ToInt32(serverPort.Text.Trim());
-
-
-
- IPEndPoint ie = new IPEndPoint(IPAddress.Parse(ipadd), port);
- newclient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
-
-
-
- try
- {
- newclient.Connect(ie);
- connect.Enabled = false;
- Connected = true;
-
- }
- catch (SocketException e)
- {
- MessageBox.Show("連接服務器失敗 " + e.Message);
- return;
- }
-
- ThreadStart myThreaddelegate = new ThreadStart(ReceiveMsg);
- myThread = new Thread(myThreaddelegate);
- myThread.Start();
- tmSend.Enabled = true;
-
- }
-
- private void connect_Click_1(object sender, EventArgs e)
- {
- Connect();
- }
- }
- }
2、為了避免連接服務器發生超時掉線,我們這里做一個定時發送的函數,保證
在掉線時間范圍內連續向服務器發送數據,注意,需要在連接函數中增加
timersend.Enabled = true;,在連接服務器的同時來觸發定時發送。
- private void timersend_Tick(object sender, EventArgs e)
- {
- int isecond = 5000;
- timersend.Interval = isecond;
- byte[] data = new byte[] { 0x00, 0x0f, 0x00, 0x00, 0x00, 0x06, 0x01, 0x04, 0x00, 0x00, 0x00, 0x01 };
- newclient.Send(data);
- }
通過上面的兩步,一個Modbus TCP的客戶端連接已經建立起來了,下面我們就來分析
Modbus TCP協議的具體內容與實現方式了。
3、我們根據Modbus協議規范中文版中的內容,來寫幾個功能碼的程序。
我們直接用實例來說明:
1)、01(0x01)功能碼--------讀線圈
請求與響應格式
這是一個請求讀離散量輸出20-38 的實例:
由上圖可以,我們來編程發送數據(這里需要注意一下,上述圖片是
從modbus協議文檔上截取的,起始地址要根據你的服務器來具體分析,
有的是從0開始的,有的是從1開始,所以起始地址應該是14)
0x00,0x01,0x00,0x00,0x00,0x06,0x01,0x01,0x00,0x14,0x00,0x13
- private void send01_Click(object sender, EventArgs e)
- {
- byte[] data = new byte[] { 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x01, 0x01, 0x00, 0x14, 0x00, 0x13;
- newclient.Send(data);
- }
接收數據為:
0x00,0x01,0x00,0x00,0x00,0x06,0x01,0x01,0x03,0xCD,0x6B,0x05
這里還需要做一個接收函數和現實接收數據的文本框
- public void ReceiveMsg()
- {
- while (true)
- {
- byte[] data = new byte[1024];
- newclient.Receive(data);
- int length = data[5];
- Byte[] datashow = new byte[length + 6];
- for (int i = 0; i <= length + 5; i++)
- datashow[i] = data[i];
- string stringdata = BitConverter.ToString(datashow);
- if (data[7] == 0x01) { showMsg01(stringdata + "\r\n"); };
- if (data[7] == 0x02) { showMsg02(stringdata + "\r\n"); };
- if (data[7] == 0x03) { showMsg03(stringdata + "\r\n"); };
- if (data[7] == 0x05) { showMsg05(stringdata + "\r\n"); };
- if (data[7] == 0x06) { showMsg06(stringdata + "\r\n"); };
- if (data[7] == 0x0F) { showMsg0F(stringdata + "\r\n"); };
- if (data[7] == 0x10) { showMsg10(stringdata + "\r\n"); };
- }
- }
- public void showMsg01(string msg)
- {
-
-
- if (receiveMsg01.InvokeRequired)
- {
- MyInvoke _myinvoke = new MyInvoke(showMsg01);
- receiveMsg01.Invoke(_myinvoke, new object[] { msg });
- }
- else
- {
- receiveMsg01.AppendText(msg);
- }
-
- }
下面我來介紹1個測試工具,modbus slave,打開軟件,點擊Connection來建立一個
modbus tcp服務器,如下圖所示
下面我們再來設置一下該軟件,這里我們首先測試的是0x01功能碼,所以我們點擊Setup,設置如下圖
我們0x01功能碼的例子是一個請求讀離散量輸出20-38,這里注意一下分析,
0xCD是27-20,0x6B是35-28,0x05是43-36,這里從39到43用0來補齊
下面我們在Modbus Slave中填寫數據,如圖所示
下面,我們運行我們用C#編寫的軟件,並打開Wireshark來截取封包進行分析,
截取封包如下圖
Modbus TCP請求
Modbus TCP相應:
再看一下我們軟件接收到的數據
2)、02(0x02)功能碼--------讀離散量輸入
請求與應答PDU
這是一個請求讀取離散量輸入197-218 的實例:
發送數據為:
0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x01, 0x02, 0x00, 0xC5, 0x00, 0x16
程序如下:
- private void send02_Click(object sender, EventArgs e)
- {
- byte[] data = new byte[] { 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x01, 0x02, 0x00, 0xC5, 0x00, 0x16 };
- newclient.Send(data);
- }
接收數據為:
0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x01,
0x02, 0x03, 0xAC, 0xDB, 0x35
程序如下:
- public void showMsg02(string msg)
- {
-
-
- if (receive0x01.InvokeRequired)
- {
- MyInvoke _myinvoke = new MyInvoke(showMsg02);
- receive0x02.Invoke(_myinvoke, new object[] { msg });
- }
- else
- {
- receive0x02.AppendText(msg);
- }
-
- }
我們再來看一下Modbus Slave設置
我們再看一下Wireshark截取封包
Modbus TCP請求
Modbus TCP響應
我們的軟件所收到的數據