網(wǎng)絡(luò)安全編程:非阻塞模式開發(fā)
Winsock套接字的工作模式有兩種,分別是阻塞模式(同步模式)和非阻塞模式(異步模式)。阻塞模式下的Winsock函數(shù)會(huì)將程序的某個(gè)線程(如果程序中只有一個(gè)主線程,那么會(huì)導(dǎo)致整個(gè)程序處于“等待”狀態(tài))處于“等待”狀態(tài)。非阻塞模式的Winsock函數(shù)不會(huì)發(fā)生需要等待的情況。在異步模式下,當(dāng)一個(gè)函數(shù)執(zhí)行后會(huì)立刻返回,即使是操作沒(méi)有完成也會(huì)返回;當(dāng)函數(shù)執(zhí)行完成時(shí),會(huì)以某種方式通知應(yīng)用程序。顯然,異步模式更適合于Windows下的開發(fā)。本文介紹異步模式的Winsock編程。
當(dāng)一個(gè)套接字通過(guò)socket()函數(shù)創(chuàng)建后,默認(rèn)工作在阻塞模式下。為了使得套接字工作在非阻塞模式狀態(tài)下,就需要對(duì)套接字進(jìn)行設(shè)置,將其改編為非阻塞模式。改變套接字工作模式的方法有多種,為了基于Windows應(yīng)用程序的消息驅(qū)動(dòng)機(jī)制,這里只介紹常用的改變套接字的函數(shù)。該函數(shù)是WSAAsyncSelect()函數(shù),其定義如下:
- int WSAAsyncSelect(
- SOCKET s,
- HWND hWnd,
- unsigned int wMsg,
- long lEvent
- );
WSAAsyncSelect()函數(shù)會(huì)把套接字設(shè)置為非阻塞模式,該函數(shù)會(huì)綁定指定套接字到一個(gè)窗口。當(dāng)該套接字有網(wǎng)絡(luò)事件發(fā)生時(shí),會(huì)向綁定窗口發(fā)送相應(yīng)的消息。該函數(shù)的參數(shù)含義說(shuō)明如下。
S:指定要改變工作模式為非阻塞模式的套接字。
hWnd:指定當(dāng)發(fā)生網(wǎng)絡(luò)事件時(shí)接收消息的窗口。
wMsg:指定當(dāng)網(wǎng)絡(luò)事件發(fā)生時(shí)向窗口發(fā)送的消息。該消息是一個(gè)自定義消息,定義自定義消息的方法是在 WM_USER 的基礎(chǔ)上加一個(gè)數(shù)值,比如(WM_USER + 1)。
lEvent:指定應(yīng)用程序感興趣的通知碼。它可以被指定為多個(gè)通知碼的組合。常用的通知碼有 FD_READ(套接字收到對(duì)端發(fā)來(lái)的數(shù)據(jù)包)、FD_ACCEPT(監(jiān)聽中的套接字有連接請(qǐng)求)、FD_CONNECT(套接字成功連接到對(duì)方)和 FD_CLOSE(套接字對(duì)應(yīng)的連接被關(guān)閉)。在指定通知碼時(shí)不需要全部將其指定。對(duì)于基于 TCP 協(xié)議的客戶端來(lái)說(shuō),F(xiàn)D_ACCEPT 是沒(méi)有意義的;對(duì)于基于 TCP 的服務(wù)端來(lái)說(shuō),F(xiàn)D_CONNECT 是沒(méi)有意義的;對(duì)于基于 UDP 協(xié)議的客戶端和服務(wù)器端來(lái)說(shuō),F(xiàn)D_ACCEPT、FD_CONNECT 和 FD_CLOSE 都是沒(méi)有意義的。
在了解如何將套接字設(shè)置為非阻塞模式以后,這里完成一個(gè)簡(jiǎn)單的遠(yuǎn)程控制工具。這里要編寫的遠(yuǎn)程控制工具是基于C/S模式的,即客戶端/服務(wù)器端模式的架構(gòu)??蛻舳送ㄟ^(guò)發(fā)送控制命令,操作服務(wù)器端接收到控制命令后響應(yīng)相應(yīng)的事件,完成特定的功能。
這個(gè)遠(yuǎn)程控制的服務(wù)器端只簡(jiǎn)單實(shí)現(xiàn)以下幾個(gè)功能。
- 向客戶端發(fā)送幫助信息。
- 將服務(wù)器信息發(fā)送給客戶端。
- 交換鼠標(biāo)的左右鍵和恢復(fù)鼠標(biāo)的左右鍵。
- 打開光驅(qū)和關(guān)閉光驅(qū)。
1. 遠(yuǎn)程控制軟件框架設(shè)計(jì)
遠(yuǎn)程控制分為控制端和被控制端,控制端通常為客戶端,而被控制端通常為服務(wù)器端。對(duì)于客戶端來(lái)說(shuō),它需要3種通知碼,即FD_CONNECT、FD_CLOSE和FD_READ。對(duì)于服務(wù)器端來(lái)說(shuō),它需要3種通知碼,即FD_ACCEPT、FD_CLOSE和FD_READ,如圖1所示。
圖1 服務(wù)器端和客戶端通信
這里解釋一下圖1,并對(duì)它的框架設(shè)計(jì)進(jìn)行補(bǔ)充。對(duì)于服務(wù)器端(Server端)來(lái)說(shuō),它需要處于監(jiān)聽狀態(tài)等待客戶端(Client端)發(fā)起的連接(FD_ACCEPT),在連接后會(huì)等待接收客戶端發(fā)來(lái)的控制命令(FD_READ),當(dāng)客戶端斷開連接后就可以結(jié)束此次通信了(FD_CLOSE)。對(duì)于客戶端來(lái)說(shuō),它需要等待確認(rèn)連接是否成功(FD_CONNET);當(dāng)連接成功后就可以向服務(wù)器端發(fā)送控制命令,并等待接收命令響應(yīng)結(jié)果(FD_READ);當(dāng)服務(wù)器端被關(guān)閉后,通信則強(qiáng)制被結(jié)束了(FD_CLOSE)。因此,服務(wù)器端需要的通知碼有FD_ACCEPT、FD_READ和FD_CLOSE,客戶端需要的通知碼有FD_CONNECT、FD_READ和FD_CLOSE。
客戶端向服務(wù)器端發(fā)送的命令為“字符串”類型的數(shù)據(jù)。當(dāng)服務(wù)器接收到客戶端發(fā)來(lái)的命令后,需要判斷命令,然后執(zhí)行相應(yīng)的功能。
服務(wù)器向客戶端反饋的執(zhí)行結(jié)果可能為字符串,也可能為其他的數(shù)據(jù)結(jié)構(gòu)類型的內(nèi)容。由于反饋數(shù)據(jù)的格式無(wú)法確定,那么對(duì)于服務(wù)器向客戶端反饋的信息必須做特殊的標(biāo)記,通過(guò)標(biāo)記判斷發(fā)送的數(shù)據(jù)格式。而客戶端接收到服務(wù)器端發(fā)來(lái)的數(shù)據(jù)后,必須對(duì)格式進(jìn)行解析,以便正確讀取服務(wù)器端返回的命令反饋結(jié)果。服務(wù)器端的反饋數(shù)據(jù)協(xié)議格式如圖2所示。
圖2 服務(wù)器端反饋數(shù)據(jù)協(xié)議格式
從圖2可以看出,服務(wù)器對(duì)于客戶端的反饋數(shù)據(jù)協(xié)議格式有3部分內(nèi)容,第1部分bType用于區(qū)分是文本數(shù)據(jù)和特定數(shù)據(jù)結(jié)構(gòu)的數(shù)據(jù),第2部分bClass用于區(qū)分不同的特定數(shù)據(jù)結(jié)構(gòu),第3部分szValue是真正的數(shù)據(jù)部分。對(duì)于服務(wù)器反饋的數(shù)據(jù),如果是文本數(shù)據(jù),那么客戶端直接將szValue中的字符串顯示輸出;如果反饋的是特定的數(shù)據(jù)結(jié)構(gòu),則必須區(qū)分是何種數(shù)據(jù)結(jié)構(gòu),最后按照直接的數(shù)據(jù)結(jié)構(gòu)解析szValue中的數(shù)據(jù)。將該協(xié)議格式定義為數(shù)據(jù)結(jié)構(gòu)體,如下:
- #define TEXTMSG 't' // 表示文本信息
- #define BINARYMSG 'b' // 表示特定的數(shù)據(jù)結(jié)構(gòu)
- typedef struct _DATA_MSG
- {
- BYTE bType; // 數(shù)據(jù)的類型
- BYTE bClass; // 數(shù)據(jù)類型的補(bǔ)充
- char szValue[0x200]; // 數(shù)據(jù)的信息
- }DATA_MSG, *PDATA_MSG;
2. 遠(yuǎn)程控制軟件代碼要點(diǎn)
前面介紹了WSAAsyncSelect()函數(shù)原型和參數(shù)的含義,下面具體介紹WSAAsyncSelect()函數(shù)的使用。WSAAsyncSelect()函數(shù)在使用時(shí)會(huì)將指定的套接字、窗口句柄、自定義消息和通知碼關(guān)聯(lián)在一起,使用如下:
- // 初始化 Winsock 庫(kù)
- WSADATA wsaData;
- WSAStartup(MAKEWORD(2, 2), &wsaData);
- // 創(chuàng)建套接字并將其設(shè)置為非阻塞模式
- m_ListenSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
- WSAAsyncSelect(m_ListenSock, GetSafeHwnd(), UM_SERVER, FD_ACCEPT);
在代碼的WSAAsyncSelect()函數(shù)中,第1個(gè)參數(shù)是新創(chuàng)建的用于監(jiān)聽的套接字m_ListenSock,第2個(gè)參數(shù)使用MFC的成員函數(shù)GetSafeHwnd()來(lái)得到當(dāng)前窗體的句柄,第3個(gè)參數(shù)UM_SERVER是一個(gè)自定義的類型,最后一個(gè)參數(shù)FD_ACCEPT是該套接字要接收的通知碼。函數(shù)中的第3個(gè)參數(shù)是一個(gè)自定義的消息。在服務(wù)器端,該消息的定義如下:
- #define UM_SERVER (WM_USER + 200)
當(dāng)有客戶端與服務(wù)器端連接時(shí),系統(tǒng)會(huì)發(fā)送UM_SERVER消息到與監(jiān)聽套接字關(guān)聯(lián)的句柄指定的窗口。當(dāng)窗口收到該消息后,需要對(duì)該消息進(jìn)行處理。該處理函數(shù)也需要手動(dòng)進(jìn)行添加,添加有3處地方。
第1處是在類定義中添加,代碼如下:
- // 生成的消息映射函數(shù)
- //{{AFX_MSG(CServerDlg)
- virtual BOOL OnInitDialog();
- afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
- afx_msg void OnPaint();
- afx_msg HCURSOR OnQueryDragIcon();
- afx_msg VOID OnSock(WPARAM wParam, LPARAM lParam);
- afx_msg void OnClose();
- //}}AFX_MSG
- DECLARE_MESSAGE_MAP()
在這里添加afx_msg VOID OnSock(WPARAM wParam, LPARAM lParam);
第2處在類實(shí)現(xiàn)中添加對(duì)應(yīng)的函數(shù)實(shí)現(xiàn)代碼,如下:
- VOID CServerDlg::OnSock(WPARAM wParam, LPARAM lParam)
- {
- }
第3處是要添加消息映射,代碼如下:
- BEGIN_MESSAGE_MAP(CServerDlg, CDialog)
- //{{AFX_MSG_MAP(CServerDlg)
- ON_WM_SYSCOMMAND()
- ON_WM_PAINT()
- ON_WM_QUERYDRAGICON()
- ON_MESSAGE(UM_SERVER, OnSock)
- ON_WM_CLOSE()
- //}}AFX_MSG_MAP
- END_MESSAGE_MAP()
在這里添加ON_MESSAGE(UM_SERVER, OnSock)。
通過(guò)以上3步,在程序中就可以接收并響應(yīng)對(duì)UM_SERVER消息的處理。
3. 遠(yuǎn)程控制界面布局
首先來(lái)看遠(yuǎn)程控制客戶端與服務(wù)器端的窗口界面,如圖3所示。
圖3 遠(yuǎn)程控制端與服務(wù)器端界面布局
在圖3中,SERVER表示服務(wù)器端,Client表示客戶端。服務(wù)器端(Server)運(yùn)行在虛擬機(jī)中,客戶端(Client)運(yùn)行在物理機(jī)中。通過(guò)圖3可以看出,物理機(jī)中客戶端與服務(wù)器端是可以正常進(jìn)行通信的。
服務(wù)器端的軟件只有一個(gè)用于顯示多行文本的編輯框。該界面比較簡(jiǎn)單。
客戶端軟件在IP地址后的編輯框中輸入服務(wù)器端的IP地址,然后單擊“連接”按鈕,客戶端會(huì)與遠(yuǎn)端的服務(wù)器進(jìn)行連接。當(dāng)連接成功后,輸入IP地址的編輯框會(huì)處于只讀狀態(tài),“連接”按鈕變?yōu)?ldquo;斷開連接”按鈕。對(duì)于發(fā)送命令后的編輯框變?yōu)榭捎脿顟B(tài),“發(fā)送”按鈕也變?yōu)榭捎脿顟B(tài)。
對(duì)于軟件界面的布局,大家可以自行調(diào)整。
4. 服務(wù)器端代碼的實(shí)現(xiàn)
當(dāng)服務(wù)器啟動(dòng)時(shí),需要?jiǎng)?chuàng)建套接字,并將套接字設(shè)置為異步模式,綁定IP地址和端口號(hào)并使其處于監(jiān)聽狀態(tài),代碼如下:
- BOOL CServerDlg::OnInitDialog()
- {
- ……
- // 添加其他初始化代碼
- // 初始化 Winsock 庫(kù)
- WSADATA wsaData;
- WSAStartup(MAKEWORD(2, 2), &wsaData);
- // 創(chuàng)建套接字并將其設(shè)置為非阻塞模式
- m_ListenSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
- WSAAsyncSelect(m_ListenSock, GetSafeHwnd(), UM_SERVER, FD_ACCEPT);
- sockaddr_in addr;
- addr.sin_family = AF_INET;
- addr.sin_addr.S_un.S_addr = ADDR_ANY;
- addr.sin_port = htons(5555);
- // 綁定 IP 地址及 5555 端口,并處于監(jiān)聽狀態(tài)
- bind(m_ListenSock, (SOCKADDR*)&addr, sizeof(addr));
- listen(m_ListenSock, 1);
- return TRUE; // return TRUE unless you set the focus to a control
- }
當(dāng)客戶端與服務(wù)器端進(jìn)行連接時(shí),需要處理通知碼FD_ACCEPT,并且創(chuàng)建與客戶端進(jìn)行通信的新的套接字。對(duì)于新的套接字也需要設(shè)置為異步模式,并且需要設(shè)置FD_READ和FD_CLOSE兩個(gè)通知碼。代碼如下:
- VOID CServerDlg::OnSock(WPARAM wParam, LPARAM lParam)
- {
- if ( WSAGETSELECTERROR(lParam) )
- {
- return ;
- }
- switch ( WSAGETSELECTEVENT(lParam))
- {
- // 處理 FD_ACCEPT
- case FD_ACCEPT:
- {
- sockaddr_in ClientAddr;
- int nSize = sizeof(ClientAddr);
- m_ClientSock = accept(m_ListenSock, (SOCKADDR*)&ClientAddr, &nSize);
- WSAAsyncSelect(m_ClientSock, GetSafeHwnd(), UM_SERVER, FD_READ | FD_CLOSE);
- m_StrMsg.Format("請(qǐng)求地址是%s:%d",
- inet_ntoa(ClientAddr.sin_addr), ntohs(ClientAddr.sin_port));
- DATA_MSG DataMsg;
- DataMsg.bType = TEXTMSG;
- DataMsg.bClass = 0;
- lstrcpy(DataMsg.szValue, HELPMSG);
- send(m_ClientSock, (const char *)&DataMsg, sizeof(DataMsg), 0);
- break;
- }
- // 處理 FD_READ
- case FD_READ:
- {
- char szBuf[MAXBYTE] = { 0 };
- recv(m_ClientSock, szBuf, MAXBYTE, 0);
- DispatchMsg(szBuf);
- m_StrMsg = "對(duì)方發(fā)來(lái)命令: ";
- m_StrMsg += szBuf;
- break;
- }
- // 處理 FD_CLOSE
- case FD_CLOSE:
- {
- closesocket(m_ClientSock);
- m_StrMsg = "對(duì)方關(guān)閉連接";
- break;
- }
- }
- InsertMsg();
- }
在代碼中,當(dāng)響應(yīng)FD_READ通知碼時(shí)會(huì)接收客戶端發(fā)來(lái)的命令,并通過(guò)DispatchMsg()函數(shù)處理客戶端發(fā)來(lái)的命令。在OnSock()函數(shù)的最后有一個(gè)InsertMsg()函數(shù),該函數(shù)用于將接收的命令顯示到界面上對(duì)應(yīng)的消息編輯框中。
DispatchMsg()函數(shù)用于處理客戶端發(fā)來(lái)的命令,該代碼如下:
- VOID CServerDlg::DispatchMsg(char *szBuf)
- {
- DATA_MSG DataMsg;
- ZeroMemory((void*)&DataMsg, sizeof(DataMsg));
- if ( !strcmp(szBuf, "help") )
- {
- DataMsg.bType = TEXTMSG;
- DataMsg.bClass = 0;
- lstrcpy(DataMsg.szValue, HELPMSG);
- }
- else if ( !strcmp(szBuf, "getsysinfo"))
- {
- SYS_INFO SysInfo;
- GetSysInfo(&SysInfo);
- DataMsg.bType = BINARYMSG;
- DataMsg.bClass = SYSINFO;
- memcpy((void *)DataMsg.szValue, (const char *)&SysInfo, sizeof(DataMsg));
- }
- else if ( !strcmp(szBuf, "open") )
- {
- SetCdaudio(TRUE);
- DataMsg.bType = TEXTMSG;
- DataMsg.bClass = 0;
- lstrcpy(DataMsg.szValue, "open 命令執(zhí)行完成");
- }
- else if ( !strcmp(szBuf, "close") )
- {
- SetCdaudio(FALSE);
- DataMsg.bType = TEXTMSG;
- DataMsg.bClass = 0;
- lstrcpy(DataMsg.szValue, "close 命令執(zhí)行完成");
- }
- else if ( !strcmp(szBuf, "swap") )
- {
- SetMouseButton(TRUE);
- DataMsg.bType = TEXTMSG;
- DataMsg.bClass = 0;
- lstrcpy(DataMsg.szValue, "swap 命令執(zhí)行完成");
- }
- else if ( !strcmp(szBuf, "restore") )
- {
- SetMouseButton(FALSE);
- DataMsg.bType = TEXTMSG;
- DataMsg.bClass = 0;
- lstrcpy(DataMsg.szValue, "restore 命令執(zhí)行完成");
- }
- else
- {
- DataMsg.bType = TEXTMSG;
- DataMsg.bClass = 0;
- lstrcpy(DataMsg.szValue, "無(wú)效的指令");
- }
- // 發(fā)送命令執(zhí)行情況給客戶端
- send(m_ClientSock, (const char *)&DataMsg, sizeof(DataMsg), 0);
- }
在DispatchMsg()函數(shù)中,通過(guò)if()…else if()…else()比較客戶端發(fā)來(lái)的命令執(zhí)行相應(yīng)的功能,并將執(zhí)行的結(jié)果發(fā)送給客戶端。
命令功能的實(shí)現(xiàn)函數(shù)如下:
- VOID CServerDlg::GetSysInfo(PSYS_INFO SysInfo)
- {
- unsigned long nSize = 0;
- SysInfo->OsVer.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
- GetVersionEx(&SysInfo->OsVer);
- nSize = NAME_LEN;
- GetComputerName(SysInfo->szComputerName, &nSize);
- nSize = NAME_LEN;
- GetUserName(SysInfo->szUserName, &nSize);
- }
- VOID CServerDlg::SetCdaudio(BOOL bOpen)
- {
- if ( bOpen )
- {
- // 打開光驅(qū)
- mciSendString("set cdaudio door open", NULL, NULL, NULL);
- }
- else
- {
- // 關(guān)閉光驅(qū)
- mciSendString("set cdaudio door closed", NULL, NULL, NULL);
- }
- }
- VOID CServerDlg::SetMouseButton(BOOL bSwap)
- {
- if ( bSwap)
- {
- // 交換
- SwapMouseButton(TRUE);
- }
- else
- {
- // 恢復(fù)
- SwapMouseButton(FALSE);
- }
- }
這里面對(duì)于getsysinfo命令,需要定義一個(gè)結(jié)構(gòu)體,具體如下:
- #define HELPMSG "幫助信息: \r\n" \
- "\t help : 顯示幫助菜單 \r\n" \
- "\t getsysinfo : 獲得對(duì)方主機(jī)信息\r\n" \
- "\t open : 打開光驅(qū) \r\n" \
- "\t close : 關(guān)閉光驅(qū) \r\n" \
- "\t swap : 交換鼠標(biāo)左右鍵 \r\n" \
- "\t restore : 恢復(fù)鼠標(biāo)左右鍵" \
- #define NAME_LEN 20
- typedef struct _SYS_INFO
- {
- OSVERSIONINFO OsVer; // 保存操作系統(tǒng)信息
- char szComputerName[NAME_LEN]; // 保存計(jì)算機(jī)名
- char szUserName[NAME_LEN]; // 保存當(dāng)前登錄名
- }SYS_INFO, *PSYS_INFO;
該結(jié)構(gòu)體不是文本類型的數(shù)據(jù),需要在反饋協(xié)議中填充bClass字段。對(duì)于getsysinfo命令,該bClass字段填充的內(nèi)容為“SYSINFO”。SYSINFO的定義如下:
- #define SYSINFO 0x01L
調(diào)用mciSendString()函數(shù)需要添加頭文件和庫(kù)文件,具體如下:
- #include <mmsystem.h>
- #pragma comment (lib, "Winmm")
至此,服務(wù)器端的主要功能就介紹完了,最后還有兩個(gè)函數(shù)沒(méi)有列出,分別是InsertMsg()函數(shù)和釋放Winsock庫(kù)的部分,代碼如下:
- void CServerDlg::OnClose()
- {
- // 添加處理程序代碼或調(diào)用默認(rèn)方法
- // 關(guān)閉監(jiān)聽套接字,并釋放 Winsock 庫(kù)
- closesocket(m_ClientSock);
- closesocket(m_ListenSock);
- WSACleanup();
- CDialog::OnClose();
- }
- VOID CServerDlg::InsertMsg()
- {
- CString strMsg;
- GetDlgItemText(IDC_MSG, strMsg);
- m_StrMsg += "\r\n";
- m_StrMsg += "----------------------------------------\r\n";
- m_StrMsg += strMsg;
- SetDlgItemText(IDC_MSG, m_StrMsg);
- m_StrMsg = "";
- }
5. 客戶端代碼的實(shí)現(xiàn)
客戶端的代碼基本與服務(wù)端的代碼類似,這里就不再說(shuō)明。
連接遠(yuǎn)程服務(wù)器的代碼如下:
- void CClientDlg::OnBtnConnect()
- {
- // 添加處理程序代碼
- char szBtnName[10] = { 0 };
- GetDlgItemText(IDC_BTN_CONNECT, szBtnName, 10);
- // 斷開連接
- if ( !lstrcmp(szBtnName, "斷開連接") )
- {
- SetDlgItemText(IDC_BTN_CONNECT, "連接");
- (GetDlgItem(IDC_SZCMD))->EnableWindow(FALSE);
- (GetDlgItem(IDC_BTN_SEND))->EnableWindow(FALSE);
- (GetDlgItem(IDC_IPADDR))->EnableWindow(TRUE);
- closesocket(m_Socket);
- m_StrMsg = "主動(dòng)斷開連接";
- InsertMsg();
- return ;
- }
- // 連接遠(yuǎn)程服務(wù)器端
- char szIpAddr[MAXBYTE] = { 0 };
- GetDlgItemText(IDC_IPADDR, szIpAddr, MAXBYTE);
- m_Socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
- WSAAsyncSelect(m_Socket,GetSafeHwnd(),UM_CLIENT, FD_READ | FD_CONNECT | FD_CLOSE);
- sockaddr_in ServerAddr;
- ServerAddr.sin_family = AF_INET;
- ServerAddr.sin_addr.S_un.S_addr = inet_addr(szIpAddr);
- ServerAddr.sin_port = htons(5555);
- connect(m_Socket, (SOCKADDR*)&ServerAddr, sizeof(ServerAddr));
- }
響應(yīng)通知碼的函數(shù)如下:
- VOID CClientDlg::OnSock(WPARAM wParam, LPARAM lParam)
- {
- if ( WSAGETSELECTERROR(lParam) )
- {
- return ;
- }
- switch ( WSAGETSELECTEVENT(lParam))
- {
- // 處理 FD_ACCEPT
- case FD_CONNECT:
- {
- (GetDlgItem(IDC_SZCMD))->EnableWindow(TRUE);
- (GetDlgItem(IDC_BTN_SEND))->EnableWindow(TRUE);
- (GetDlgItem(IDC_IPADDR))->EnableWindow(FALSE);
- SetDlgItemText(IDC_BTN_CONNECT, "斷開連接");
- m_StrMsg = "連接成功";
- break;
- }
- // 處理 FD_READ
- case FD_READ:
- {
- DATA_MSG DataMsg;
- recv(m_Socket, (char *)&DataMsg, sizeof(DataMsg), 0);
- DispatchMsg((char *)&DataMsg);
- break;
- }
- // 處理 FD_CLOSE
- case FD_CLOSE:
- {
- (GetDlgItem(IDC_SZCMD))->EnableWindow(FALSE);
- (GetDlgItem(IDC_BTN_SEND))->EnableWindow(FALSE);
- (GetDlgItem(IDC_IPADDR))->EnableWindow(TRUE);
- closesocket(m_Socket);
- m_StrMsg = "對(duì)方關(guān)閉連接";
- break;
- }
- }
- InsertMsg();
- }
發(fā)送命令到遠(yuǎn)程服務(wù)器端的代碼如下:
- void CClientDlg::OnBtnSend()
- {
- // 添加處理程序代碼
- char szBuf[MAXBYTE] = { 0 };
- GetDlgItemText(IDC_SZCMD, szBuf, MAXBYTE);
- send(m_Socket, szBuf, MAXBYTE, 0);
- }
處理服務(wù)器端反饋結(jié)果的代碼如下:
- VOID CClientDlg::DispatchMsg(char *szBuf)
- {
- DATA_MSG DataMsg;
- memcpy((void*)&DataMsg, (const void *)szBuf, sizeof(DATA_MSG));
- if ( DataMsg.bType == TEXTMSG )
- {
- m_StrMsg = DataMsg.szValue;
- }
- else
- {
- if ( DataMsg.bClass == SYSTEMINFO )
- {
- ParseSysInfo((PSYS_INFO)&DataMsg.szValue);
- }
- }
- }
解析服務(wù)器端信息的代碼如下:
- VOID CClientDlg::ParseSysInfo(PSYS_INFO SysInfo)
- {
- if ( SysInfo->OsVer.dwPlatformId == VER_PLATFORM_WIN32_NT )
- {
- if ( SysInfo->OsVer.dwMajorVersion == 5 && SysInfo->OsVer.dwMinorVersion == 1 )
- {
- m_StrMsg.Format("對(duì)方系統(tǒng)信息:\r\n\t Windows XP %s", SysInfo->OsVer. szCSDVersion);
- }
- else if ( SysInfo->OsVer.dwMajorVersion == 5 && SysInfo->OsVer.dwMinorVersion== 0)
- {
- m_StrMsg.Format("對(duì)方系統(tǒng)信息:\r\n\t Windows 2K");
- }
- }
- else
- {
- m_StrMsg.Format("對(duì)方系統(tǒng)信息:\r\n\t Other System \r\n");
- }
- m_StrMsg += "\r\n";
- m_StrMsg += "\t Computer Name is ";
- m_StrMsg += SysInfo->szComputerName;
- m_StrMsg += "\r\n";
- m_StrMsg += "\t User Name is";
- m_StrMsg += SysInfo->szUserName;
- }
到這里,遠(yuǎn)程控制的代碼就完成了。如果要實(shí)現(xiàn)更多的功能,可能該框架無(wú)法進(jìn)行更好的擴(kuò)充。該實(shí)例主要為了演示非阻塞模式的Winsock應(yīng)用的開發(fā)。如果該實(shí)例中的套接字使用阻塞模式的話,那么就必須配合多線程來(lái)完成,將接收的部分單獨(dú)放在一個(gè)線程中,否則接收數(shù)據(jù)的函數(shù)recv()在等待接收數(shù)據(jù)的到來(lái)時(shí)會(huì)將整個(gè)程序“卡死”。