1. 程式人生 > >TCP非阻塞socket程式設計

TCP非阻塞socket程式設計

網路通訊程式的同步方式指的是傳送方不等接收方響應,便接著發下個數據包的通訊方式;而非同步指傳送方發出資料後,等收到接收方發回的響應,才發下一個數據包的通訊方式。 阻塞套接字是指執行此套接字的網路呼叫時,直到成功才返回,否則一直阻塞在此網路呼叫上,比如呼叫recv()函式讀取網路緩衝區中的資料,如果沒有資料到達,將一直掛在recv()這個函式呼叫上,直到讀到一些資料,此函式呼叫才返回;而非阻塞套接字是指執行此套接字的網路呼叫時,不管是否執行成功,都立即返回。比如呼叫recv()函式讀取網路緩衝區中資料,不管是否讀到資料都立即返回,而不會一直掛在此函式呼叫上。在實際Windows網路通訊軟體開發中,非同步非阻塞套接字是用的最多的。平常所說的C/S(客戶端/伺服器)結構的軟體就是非同步非阻塞模式的。

用一個最簡單的例子說明非同步非阻塞Socket的基本原理和工作機制。目的是讓初學者不僅對Socket非同步非阻塞的概念有個非常透徹的理解,而且也給他們提供一個用Socket開發網路通訊應用程式的快速入門方法。作業系統是Windows 98(或NT4.0),開發工具是Visual C++6.0。 MFC提供了一個非同步類CAsyncSocket,它封裝了非同步、非阻塞Socket的基本功能,用它做常用的網路通訊軟體很方便。但它遮蔽了Socket的非同步、非阻塞等概念,開發人員無需瞭解非同步、非阻塞Socket的原理和工作機制。因此,建議初學者學習編網路通訊程式時,暫且不要用MFC提供的類,而先用Winsock2 API,這樣有助於對非同步、非阻塞Socket程式設計機制的理解。 為了簡單起見,伺服器端和客戶端的應用程式均是基於MFC的標準對話方塊,網路通訊部分基於Winsock2 API實現。 先做伺服器端應用程式。 用MFC嚮導做一個基於對話方塊的應用程式SocketSever,注意第三步中不要選上Windwos Sockets選項。在做好工程後,建立一個SeverSock,將它設定為非同步非阻塞模式,併為它註冊各種網路非同步事件,然後與自定義的網路非同步事件聯絡上,最後還要將它設定為監聽模式。在自定義的網路非同步事件的回撥函式中,你可以得到各種網路非同步事件,根據它們的型別,做不同的處理。下面將詳細介紹如何編寫相關程式碼。

在SocketSeverDlg.h檔案的類定義之前增加如下定義:

#define NETWORK_EVENT WM_USER+166 file://定義網路事件

SOCKET ServerSock; file://伺服器端Socket

 在類定義中增加如下定義:

class CSocketSeverDlg : CDialog

{ …

public: SOCKET ClientSock[CLNT_MAX_NUM]; file://儲存與客戶端通訊的Socket的陣列

/*各種網路非同步事件的處理函式*/

void OnClose(SOCKET CurSock); file://對端Socket斷開

void OnSend(SOCKET CurSock); file://傳送網路資料包

void OnReceive(SOCKET CurSock); file://網路資料包到達

void OnAccept(SOCKET CurSock); file://客戶端連線請求

BOOL InitNetwork(); file://初始化網路函式

void OnNetEvent(WPARAM wParam, LPARAM lParam); file://非同步事件回撥函式

 …

};

在SocketSeverDlg.cpp檔案中增加訊息對映,其中OnNetEvent是非同步事件回撥函式名:

ON_MESSAGE(NETWORK_EVENT,OnNetEvent)

 定義初始化網路函式,在SocketSeverDlg.cpp檔案的OnInitDialog()中調此函式即可。

BOOL CSocketSeverDlg::InitNetwork()

{

WSADATA wsaData; file://初始化TCP協議

BOOL ret = WSAStartup(MAKEWORD(2,2), &wsaData);

if(ret != 0)

{

MessageBox("初始化網路協議失敗!");

return FALSE;

}

//建立伺服器端套接字

ServerSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

if(ServerSock == INVALID_SOCKET)

{

MessageBox("建立套接字失敗!");

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設定為非同步非阻塞模式,併為它註冊各種網路非同步事件,其 中 m_hWnd file://為應用程式的主對話方塊或主視窗的控制代碼

if(WSAAsyncSelect(ServerSock, m_hWnd, NETWORK_EVENT, FD_ACCEPT | FD_CLOSE | FD_READ | FD_WRITE) == SOCKET_ERROR)

{

MessageBox("註冊網路非同步事件失敗!");

WSACleanup();

return FALSE;

 }

 listen(ServerSock, 5);

://設定偵聽模式 return TRUE;

}

下面定義網路非同步事件的回撥函式

void CSocketSeverDlg::OnNetEvent(WPARAM wParam, LPARAM lParam)

{://呼叫Winsock API函式,得到網路事件型別

int iEvent = WSAGETSELECTEVENT(lParam);

://呼叫Winsock API函式,得到發生此事件的客戶端套接字

SOCKET CurSock= (SOCKET)wParam;

 switch(iEvent)

{

case FD_ACCEPT:

://客戶端連線請求事件

OnAccept(CurSock);

 break;

case FD_CLOSE:

://客戶端斷開事件:

OnClose(CurSock);

break;

case FD_READ:

://網路資料包到達事件

OnReceive(CurSock);

 break;

case FD_WRITE:

://傳送網路資料事件

OnSend(CurSock);

break;

default:

break;

}

}

以下是發生在相應Socket上的各種網路非同步事件的處理函式,其中OnAccept傳進來的引數是伺服器端建立的套接字,OnClose()、OnReceive()和OnSend()傳進來的引數均是伺服器端在接受客戶端連線時新建立的用與此客戶端通訊的Socket。

void CSocketSeverDlg::OnAccept(SOCKET CurSock)

{ ://接受連線請求,並儲存與發起連線請求的客戶端進行通訊Socket file://為新的socket註冊非同步事件,注意沒有Accept事件 }

void CSocketSeverDlg::OnClose(SOCET CurSock) { file://結束與相應的客戶端的通訊,釋放相應資源 }

void CSocketSeverDlg::OnSend(SOCET CurSock) { file://在給客戶端發資料時做相關預處理 }

void CSocketSeverDlg::OnReceive(SOCET CurSock) { file://讀出網路緩衝區中的資料包 }

用同樣的方法建立一個客戶端應用程式。初始化網路部分,不需要將套接字設定為監聽模式。

註冊非同步事件時,沒有FD_ACCEPT,但增加了FD_CONNECT事件,因此沒有OnAccept()函式,但增加了OnConnect()函式。向伺服器發出連線請求時,使用connect()函式,連線成功後,會響應到OnConnect()函式中。下面是OnConnect()函式的定義,傳進來的引數是客戶端Socket和伺服器端發回來的連線是否成功的標誌。

void CSocketClntDlg::OnConnect(SOCKET CurSock, int error)

{

if(0 = = error)

{

if(CurSock = = ClntSock)

MessageBox("連線伺服器成功!");

 }

}

 定義OnReceive()函式,處理網路資料到達事件; 定義OnSend()函式,處理髮送網路資料事件; 定義OnClose()函式,處理伺服器的關閉事件。

以上就是用基於Windows訊息機制的非同步I/O模型做伺服器和客戶端應用程式的基本方法。另外還可以用事件模型、重疊模型或完成埠模型,讀者可以參考有關書籍。