用C程式碼簡要模擬實現一下RPC(遠端過程呼叫)並談談它在程式碼調測中的重要應用
阿新 • • 發佈:2019-01-05
說明: 本文僅僅是一種模擬的RPC實現, 真正的RPC實現還是稍微有點複雜的。
我們來看看下面這個常見的場景: 在某系統中,我們要對某一函式進行調測, 但是, 很難很難構造出這個函式被呼叫的實際場景, 怎麼辦?
雖然很難構造出這個函式被呼叫的實際場景, 但我們完全可以在程式碼中主動呼叫這個函式啊。多想方法(直接方法和間接方法), 少找藉口, 並且堅信方法總是存在的。我們可以搞一個觸發的操作, 每觸發一次, 就呼叫到該系統中的該函式。 可是, 如果這個系統比較封閉, 比如是某嵌入式系統, 也不好觸發。 沒關係, 我們借用RPC的思路來實現: 讓這個系統做服務端, 然後在客戶端上觸發。
什麼是RPC(遠端過程呼叫)呢?度娘介紹了很多, 我不想搞那麼複雜, 所以用一句白話來解釋RPC: 程序A向程序B傳送訊息, 觸發程序B的函式被執行,這樣, 從形式上看, 好像就是程序A遠端呼叫了程序B的函式, 這就是所謂的RPC(實際上, 程序A僅僅是觸發而已, 真正執行的仍然是程序B, 但理解為程序A遠端呼叫了程序B的函式, 也是很爽的)
下面, 基於上面介紹的程式碼調測場景, 我來簡要實現一下RPC:
服務端程式為(程序B):
啟動服務端。#include <stdio.h> #include <winsock2.h> // winsock介面 #pragma comment(lib, "ws2_32.lib") // winsock實現 SOCKET sockConn; // 全域性的通訊socket // RPC函式(Remote Procedure Calling) void readIP() { printf("ip is 192.168.1.100\n"); } // RPC函式(Remote Procedure Calling) void readMask() { printf("mask is 255.255.255.0\n"); } // RPC函式(Remote Procedure Calling) void readGateway() { printf("gateway is 192.168.1.1\n"); } // 訊息處理執行緒 DWORD WINAPI handleThread(LPVOID pM) { while(1) { char szMsg[100] = {0}; int nRet = recv(sockConn, szMsg, sizeof(szMsg) - 1, 0); if(nRet <= 0) { printf("recv error\n"); closesocket(sockConn); break; } // 僅僅考慮讀操作, 預期的形式為: read xxx char szOperType[20] = {0}; char szParaName[50] = {0}; nRet = sscanf(szMsg, "%s %s", szOperType, szParaName); if(2 != nRet) { printf("command error\n"); continue; } if(0 != strcmp(szOperType, "read")) { printf("type error\n"); continue; } // 其實, 下面的部分最好用C++ STL的map來做, 為了簡便示意, 我就沒用map搞了 if(0 == strcmp(szParaName, "ip")) { readIP(); } else if(0 == strcmp(szParaName, "mask")) { readMask(); } else if(0 == strcmp(szParaName, "gateway")) { readGateway(); } else { printf("parameter error\n"); continue; } Sleep(200); } return 0; } int main() { WORD wVersionRequested; // 雙位元組,winsock庫的版本 WSADATA wsaData; // winsock庫版本的相關資訊 wVersionRequested = MAKEWORD(1, 1); // 0x0101 即:257 // 載入winsock庫並確定winsock版本,系統會把資料填入wsaData中 WSAStartup( wVersionRequested, &wsaData ); // AF_INET 表示採用TCP/IP協議族 // SOCK_STREAM 表示採用TCP協議 // 0是通常的預設情況 unsigned int sockSrv = socket(AF_INET, SOCK_STREAM, 0); SOCKADDR_IN addrSrv; addrSrv.sin_family = AF_INET; // TCP/IP協議族 addrSrv.sin_addr.S_un.S_addr = inet_addr("0.0.0.0"); // socket對應的IP地址 addrSrv.sin_port = htons(8888); // socket對應的埠 // 將socket繫結到某個IP和埠(IP標識主機,埠標識通訊程序) bind(sockSrv,(SOCKADDR*)&addrSrv, sizeof(SOCKADDR)); // 將socket設定為監聽模式,5表示等待連線佇列的最大長度 listen(sockSrv, 5); // sockSrv為監聽狀態下的socket // &addrClient是緩衝區地址,儲存了客戶端的IP和埠等資訊 // len是包含地址資訊的長度 // 如果客戶端沒有啟動,那麼程式一直停留在該函式處 SOCKADDR_IN addrClient; int len = sizeof(SOCKADDR); sockConn = accept(sockSrv,(SOCKADDR*)&addrClient, &len); // 開啟訊息處理執行緒 HANDLE handle = CreateThread(NULL, 0, handleThread, NULL, 0, NULL); while(1); // 卡住, 表示主執行緒去做自己的事情, 忙自己的東西 CloseHandle(handle); closesocket(sockConn); closesocket(sockSrv); WSACleanup(); return 0; }
然後看看客戶端(程序A):
好, 開啟客戶端。#include <winsock2.h> #include <stdio.h> #pragma comment(lib, "ws2_32.lib") int main() { WORD wVersionRequested; WSADATA wsaData; wVersionRequested = MAKEWORD(1, 1); SOCKET sockClient = 0; WSAStartup( wVersionRequested, &wsaData ); sockClient = socket(AF_INET, SOCK_STREAM, 0); SOCKADDR_IN addrSrv; addrSrv.sin_addr.S_un.S_addr = inet_addr("192.168.1.100"); // 請替換為合適的ip addrSrv.sin_family = AF_INET; addrSrv.sin_port = htons(8888); connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)); while(1) { char szOpenType[20] = {0}; char szParaName[50] = {0}; // 客戶端發訊息給服務端, 觸發服務端RPC函式執行, 這樣, 就感覺是客戶端程序在呼叫伺服器程序裡面的函式, 爽歪歪啊! scanf("%s", szOpenType); scanf("%s", szParaName); char szMsg[100] = {0}; sprintf(szMsg, "%s %s", szOpenType, szParaName); // 其實, sprintf不太安全哈 send(sockClient, szMsg, strlen(szMsg) + 1, 0); } closesocket(sockClient); WSACleanup(); return 0; }
下面是執行結果:
我們看到, 客戶端遠端呼叫到了服務端的函式, 這就是所謂的RPC. 在實際程式碼調測中, 我們經常需要主動觸發某一函式或某一部分程式碼的執行, 一般來說, 怎麼方便怎麼觸發, 本文介紹的RPC觸發方式是值得考慮的一種方法。 通過本文的學習, 我們也算初步瞭解了RPC吧。