Winsock開發(fā)網(wǎng)絡通信程序的經(jīng)典入門
對于許多初學者來說,網(wǎng)絡通信程序的開發(fā),普遍的一個現(xiàn)象就是覺得難以入手。許多概念,諸如:同步(Sync)/異步(Async),阻塞(Block)/非阻塞(Unblock)等,初學者往往迷惑不清,只知其所以而不知起所以然。
同步方式指的是發(fā)送方不等接收方響應,便接著發(fā)下個數(shù)據(jù)包的通信方式;而異步指發(fā)送方發(fā)出數(shù)據(jù)后,等收到接收方發(fā)回的響應,才發(fā)下一個數(shù)據(jù)包的通信方式。
阻塞套接字是指執(zhí)行此套接字的網(wǎng)絡調用時,直到成功才返回,否則一直阻塞在此網(wǎng)絡調用上,比如調用recv()函數(shù)讀取網(wǎng)絡緩沖區(qū)中的數(shù)據(jù),如果沒有數(shù)據(jù)到達,將一直掛在recv()這個函數(shù)調用上,直到讀到一些數(shù)據(jù),此函數(shù)調用才返回;而非阻塞套接字是指執(zhí)行此套接字的網(wǎng)絡調用時,不管是否執(zhí)行成功,都立即返回。比如調用recv()函數(shù)讀取網(wǎng)絡緩沖區(qū)中數(shù)據(jù),不管是否讀到數(shù)據(jù)都立即返回,而不會一直掛在此函數(shù)調用上。在實際Windows網(wǎng)絡通信軟件開發(fā)中,異步非阻塞套接字是用的最多的。平常所說的C/S(客戶端/服務器)結構的軟件就是異步非阻塞模式的。
對于這些概念,初學者的理解也許只能似是而非,我將用一個最簡單的例子說明異步非阻塞Socket的基本原理和工作機制。目的是讓初學者不僅對Socket異步非阻塞的概念有個非常透徹的理解,而且也給他們提供一個用Socket開發(fā)網(wǎng)絡通信應用程序的快速入門方法。操作系統(tǒng)是Windows 98(或NT4.0),開發(fā)工具是Visual C++6.0。
MFC提供了一個異步類CAsyncSocket,它封裝了異步、非阻塞Socket的基本功能,用它做常用的網(wǎng)絡通信軟件很方便。但它屏蔽了Socket的異步、非阻塞等概念,開發(fā)人員無需了解異步、非阻塞Socket的原理和工作機制。因此,建議初學者學習編網(wǎng)絡通信程序時,暫且不要用MFC提供的類,而先用Winsock2 API,這樣有助于對異步、非阻塞Socket編程機制的理解。
為了簡單起見,服務器端和客戶端的應用程序均是基于MFC的標準對話框,網(wǎng)絡通信部分基于Winsock2 API實現(xiàn)。
先做服務器端應用程序。
用MFC向導做一個基于對話框的應用程序SocketSever,注意第三步中不要選上Windwos Sockets選項。在做好工程后,創(chuàng)建一個SeverSock,將它設置為異步非阻塞模式,并為它注冊各種網(wǎng)絡異步事件,然后與自定義的網(wǎng)絡異步事件聯(lián)系上,最后還要將它設置為監(jiān)聽模式。在自定義的網(wǎng)絡異步事件的回調函數(shù)中,你可以得到各種網(wǎng)絡異步事件,根據(jù)它們的類型,做不同的處理。下面將詳細介紹如何編寫相關代碼。
在SocketSeverDlg.h文件的類定義之前增加如下定義: #define NETWORK_EVENT WM_USER+166 file://定義網(wǎng)絡事件
在類定義中增加如下定義:
在SocketSeverDlg.cpp文件中增加消息映射,其中OnNetEvent是異步事件回調函數(shù)名:
定義初始化網(wǎng)絡函數(shù),在SocketSeverDlg.cpp文件的OnInitDialog()中調此函數(shù)即可。
下面定義網(wǎng)絡異步事件的回調函數(shù)
以下是發(fā)生在相應Socket上的各種網(wǎng)絡異步事件的處理函數(shù),其中OnAccept傳進來的參數(shù)是服務器端創(chuàng)建的套接字,OnClose()、OnReceive()和OnSend()傳進來的參數(shù)均是服務器端在接受客戶端連接時新創(chuàng)建的用與此客戶端通信的Socket。
用同樣的方法建立一個客戶端應用程序。初始化網(wǎng)絡部分,不需要將套接字設置為監(jiān)聽模式。注冊異步事件時,沒有FD_ACCEPT,但增加了FD_CONNECT事件,因此沒有OnAccept()函數(shù),但增加了OnConnect()函數(shù)。向服務器發(fā)出連接請求時,使用connect()函數(shù),連接成功后,會響應到OnConnect()函數(shù)中。下面是OnConnect()函數(shù)的定義,傳進來的參數(shù)是客戶端Socket和服務器端發(fā)回來的連接是否成功的標志。
·定義OnReceive()函數(shù),處理網(wǎng)絡數(shù)據(jù)到達事件;
·定義OnSend()函數(shù),處理發(fā)送網(wǎng)絡數(shù)據(jù)事件;
·定義OnClose()函數(shù),處理服務器的關閉事件。
以上就是用基于Windows消息機制的異步I/O模型做服務器和客戶端應用程序的基本方法。另外還可以用事件模型、重疊模型或完成端口模型,讀者可以參考有關書籍。
在實現(xiàn)了上面的例子后,你將對Winsock編網(wǎng)絡通信程序的機制有了一定的了解。接下來你可以進行更精彩的編程, 不僅可以在網(wǎng)上傳輸普通數(shù)據(jù),而且還以傳輸語音、視頻數(shù)據(jù),你還可以自己做一個網(wǎng)絡資源共享的服務器軟件,和你的同學在實驗室的局域網(wǎng)里可以共同分享你的成果。
from: http://soft.yesky.com/165/2284165.shtml
同步方式指的是發(fā)送方不等接收方響應,便接著發(fā)下個數(shù)據(jù)包的通信方式;而異步指發(fā)送方發(fā)出數(shù)據(jù)后,等收到接收方發(fā)回的響應,才發(fā)下一個數(shù)據(jù)包的通信方式。
阻塞套接字是指執(zhí)行此套接字的網(wǎng)絡調用時,直到成功才返回,否則一直阻塞在此網(wǎng)絡調用上,比如調用recv()函數(shù)讀取網(wǎng)絡緩沖區(qū)中的數(shù)據(jù),如果沒有數(shù)據(jù)到達,將一直掛在recv()這個函數(shù)調用上,直到讀到一些數(shù)據(jù),此函數(shù)調用才返回;而非阻塞套接字是指執(zhí)行此套接字的網(wǎng)絡調用時,不管是否執(zhí)行成功,都立即返回。比如調用recv()函數(shù)讀取網(wǎng)絡緩沖區(qū)中數(shù)據(jù),不管是否讀到數(shù)據(jù)都立即返回,而不會一直掛在此函數(shù)調用上。在實際Windows網(wǎng)絡通信軟件開發(fā)中,異步非阻塞套接字是用的最多的。平常所說的C/S(客戶端/服務器)結構的軟件就是異步非阻塞模式的。
對于這些概念,初學者的理解也許只能似是而非,我將用一個最簡單的例子說明異步非阻塞Socket的基本原理和工作機制。目的是讓初學者不僅對Socket異步非阻塞的概念有個非常透徹的理解,而且也給他們提供一個用Socket開發(fā)網(wǎng)絡通信應用程序的快速入門方法。操作系統(tǒng)是Windows 98(或NT4.0),開發(fā)工具是Visual C++6.0。
MFC提供了一個異步類CAsyncSocket,它封裝了異步、非阻塞Socket的基本功能,用它做常用的網(wǎng)絡通信軟件很方便。但它屏蔽了Socket的異步、非阻塞等概念,開發(fā)人員無需了解異步、非阻塞Socket的原理和工作機制。因此,建議初學者學習編網(wǎng)絡通信程序時,暫且不要用MFC提供的類,而先用Winsock2 API,這樣有助于對異步、非阻塞Socket編程機制的理解。
為了簡單起見,服務器端和客戶端的應用程序均是基于MFC的標準對話框,網(wǎng)絡通信部分基于Winsock2 API實現(xiàn)。
先做服務器端應用程序。
用MFC向導做一個基于對話框的應用程序SocketSever,注意第三步中不要選上Windwos Sockets選項。在做好工程后,創(chuàng)建一個SeverSock,將它設置為異步非阻塞模式,并為它注冊各種網(wǎng)絡異步事件,然后與自定義的網(wǎng)絡異步事件聯(lián)系上,最后還要將它設置為監(jiān)聽模式。在自定義的網(wǎng)絡異步事件的回調函數(shù)中,你可以得到各種網(wǎng)絡異步事件,根據(jù)它們的類型,做不同的處理。下面將詳細介紹如何編寫相關代碼。
在SocketSeverDlg.h文件的類定義之前增加如下定義: #define NETWORK_EVENT WM_USER+166 file://定義網(wǎng)絡事件
SOCKET ServerSock; file://服務器端Socket |
在類定義中增加如下定義:
class CSocketSeverDlg : CDialog { public: SOCKET ClientSock[CLNT_MAX_NUM]; file://存儲與客戶端通信的Socket的數(shù)組 /*各種網(wǎng)絡異步事件的處理函數(shù)*/ void OnClose(SOCKET CurSock); file://對端Socket斷開 void OnSend(SOCKET CurSock); file://發(fā)送網(wǎng)絡數(shù)據(jù)包 void OnReceive(SOCKET CurSock); file://網(wǎng)絡數(shù)據(jù)包到達 void OnAccept(SOCKET CurSock); file://客戶端連接請求 BOOL InitNetwork(); file://初始化網(wǎng)絡函數(shù) void OnNetEvent(WPARAM wParam, LPARAM lParam); file://異步事件回調函數(shù) … }; |
在SocketSeverDlg.cpp文件中增加消息映射,其中OnNetEvent是異步事件回調函數(shù)名:
ON_MESSAGE(NETWORK_EVENT,OnNetEvent) |
定義初始化網(wǎng)絡函數(shù),在SocketSeverDlg.cpp文件的OnInitDialog()中調此函數(shù)即可。
BOOL CSocketSeverDlg::InitNetwork() { WSADATA wsaData; //初始化TCP協(xié)議 BOOL ret = WSAStartup(MAKEWORD(2,2), &wsaData); if(ret != 0) { MessageBox("初始化網(wǎng)絡協(xié)議失敗!"); return FALSE; } //創(chuàng)建服務器端套接字 ServerSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if(ServerSock == INVALID_SOCKET) { MessageBox("創(chuàng)建套接字失敗!"); closesocket(ServerSock); WSACleanup(); return FALSE; } //綁定到本地一個端口上 sockaddr_in localaddr; localaddr.sin_family = AF_INET; localaddr.sin_port = htons(8888); //端口號不要與其他應用程序沖突 localaddr.sin_addr.s_addr = 0; if(bind(ServerSock ,(struct sockaddr*)&localaddr,sizeof(sockaddr))= = SOCKET_ERROR) { MessageBox("綁定地址失敗!"); closesocket(ServerSock); WSACleanup(); return FALSE; } //將SeverSock設置為異步非阻塞模式,并為它注冊各種網(wǎng)絡異步事件,其中m_hWnd //為應用程序的主對話框或主窗口的句柄 if(WSAAsyncSelect(ServerSock, m_hWnd, NETWORK_EVENT, FD_ACCEPT | FD_CLOSE | FD_READ | FD_WRITE) == SOCKET_ERROR) { MessageBox("注冊網(wǎng)絡異步事件失敗!"); WSACleanup(); return FALSE; } listen(ServerSock, 5); file://設置偵聽模式 return TRUE; } |
下面定義網(wǎng)絡異步事件的回調函數(shù)
void CSocketSeverDlg::OnNetEvent(WPARAM wParam, LPARAM lParam) { //調用Winsock API函數(shù),得到網(wǎng)絡事件類型 int iEvent = WSAGETSELECTEVENT(lParam); //調用Winsock API函數(shù),得到發(fā)生此事件的客戶端套接字 SOCKET CurSock= (SOCKET)wParam; switch(iEvent) { case FD_ACCEPT: //客戶端連接請求事件 OnAccept(CurSock); break; case FD_CLOSE: //客戶端斷開事件: OnClose(CurSock); break; case FD_READ: //網(wǎng)絡數(shù)據(jù)包到達事件 OnReceive(CurSock); break; case FD_WRITE: //發(fā)送網(wǎng)絡數(shù)據(jù)事件 OnSend(CurSock); break; default: break; } } |
以下是發(fā)生在相應Socket上的各種網(wǎng)絡異步事件的處理函數(shù),其中OnAccept傳進來的參數(shù)是服務器端創(chuàng)建的套接字,OnClose()、OnReceive()和OnSend()傳進來的參數(shù)均是服務器端在接受客戶端連接時新創(chuàng)建的用與此客戶端通信的Socket。
void CSocketSeverDlg::OnAccept(SOCKET CurSock) { //接受連接請求,并保存與發(fā)起連接請求的客戶端進行通信Socket //為新的socket注冊異步事件,注意沒有Accept事件 } void CSocketSeverDlg::OnClose(SOCET CurSock) { //結束與相應的客戶端的通信,釋放相應資源 } void CSocketSeverDlg::OnSend(SOCET CurSock) { //在給客戶端發(fā)數(shù)據(jù)時做相關預處理 } void CSocketSeverDlg::OnReceive(SOCET CurSock) { //讀出網(wǎng)絡緩沖區(qū)中的數(shù)據(jù)包 } |
用同樣的方法建立一個客戶端應用程序。初始化網(wǎng)絡部分,不需要將套接字設置為監(jiān)聽模式。注冊異步事件時,沒有FD_ACCEPT,但增加了FD_CONNECT事件,因此沒有OnAccept()函數(shù),但增加了OnConnect()函數(shù)。向服務器發(fā)出連接請求時,使用connect()函數(shù),連接成功后,會響應到OnConnect()函數(shù)中。下面是OnConnect()函數(shù)的定義,傳進來的參數(shù)是客戶端Socket和服務器端發(fā)回來的連接是否成功的標志。
void CSocketClntDlg::OnConnect(SOCKET CurSock, int error) { if(0 = = error) { if(CurSock = = ClntSock) MessageBox("連接服務器成功!"); } } |
·定義OnReceive()函數(shù),處理網(wǎng)絡數(shù)據(jù)到達事件;
·定義OnSend()函數(shù),處理發(fā)送網(wǎng)絡數(shù)據(jù)事件;
·定義OnClose()函數(shù),處理服務器的關閉事件。
以上就是用基于Windows消息機制的異步I/O模型做服務器和客戶端應用程序的基本方法。另外還可以用事件模型、重疊模型或完成端口模型,讀者可以參考有關書籍。
在實現(xiàn)了上面的例子后,你將對Winsock編網(wǎng)絡通信程序的機制有了一定的了解。接下來你可以進行更精彩的編程, 不僅可以在網(wǎng)上傳輸普通數(shù)據(jù),而且還以傳輸語音、視頻數(shù)據(jù),你還可以自己做一個網(wǎng)絡資源共享的服務器軟件,和你的同學在實驗室的局域網(wǎng)里可以共同分享你的成果。
from: http://soft.yesky.com/165/2284165.shtml
posted on 2006-09-28 13:17 weidagang2046 閱讀(268) 評論(0) 編輯 收藏 所屬分類: Windows