Winsock提供了一個很有用的非同步I/O模型,利用這個模型,應用程式可以在一個通訊端上接
收以Windows訊息為基礎的網路事件通知。這個模型最開始出現在Winsock
1.1版本中,是為
了協助開發人員面向一些早期的16位Windows平台而設計的。但是現在的應用程式仍然可以從
這種模型中得到好處,就連MFC中的CSocket類也採納了這種模型。
由於該模型是基於Windows訊息機制的,所以要想使用這種模型必須要Create一個視窗,這
個視窗將會被用來接收訊息。接下來建立通訊端,然後調用WSAAsyncSelect函數,開啟視窗
訊息通知,函數原型如下:
int WSAAsyncSelect(SOCKET s, HWND hWnd, unsigned int uMsg, long lEvent);
其中s就是我們想要的那個通訊端;hWnd是接收訊息通知那個視窗控制代碼;wMsg參數指定在
發生網路事件時要接受的訊息,通常設成比WM_USER大的一個值,以避免訊息衝突;
lEvent指定了一個位元遮罩,對應一系列網路事件的組合,見下表:
Event |
含義 |
FD_READ |
程式想要接收有關是否可讀的通知,以便讀入資料 |
FD_WRITE |
程式想要接收有關是否可寫的通知,以便寫入資料 |
FD_OOB |
程式想要接收是否有OOB資料到達的通知 |
FD_ACCEPT |
程式想要接收與進入串連有關的通知 |
FD_CONNECT |
程式想要接收與一次串連或多點接入有關的通知 |
FD_CLOSE |
程式想要接收與通訊端關閉有關的通知 |
FD_QOS |
程式想要接收通訊端“服務品質(QoS)”發生變化的通知 |
FD_GROUP_QOS |
暫時沒用,屬於保留事件 |
FD_ROUTING_INTERFACE_CHANGE |
程式想要接收有關到指定地址的路由介面發生變化的通知 |
FD_ADDRESS_LIST_CHANGE |
程式想要接收本地地址變化的通知 |
當程式在一個通訊端上調用WSAAsyncSelect成功後,這個程式就會在與hWnd視窗控制代碼對
應的視窗常式中以Windows訊息的形式接收網路事件通知。視窗常式通常定義成這個樣子:
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam,
LPARAM
lParam)
其中wParam參數指定在其上面發生了一個網路事件的通訊端,如果定義了多個通訊端,這
個參數就顯得很重要了。lParam參數則包含了兩方面的重要訊息,它的低位字指定了已經發
生的網路事件,而高位字包含了可能出現的錯誤碼。
簡單的來說,這個模型的具體使用流程就是:
當網路訊息抵達一個視窗常式後,程式要先檢測lParam的高位位元組,從而判斷是否在通訊端
上面發生了網路錯誤。現成的宏已經有在這裡了
-->
WSAGETSELECTERROR,可以用它返
回高位元組包含的錯誤資訊,如果沒有發現任何的錯誤,接下來就是確定究竟是什麼類型的網
絡事件觸發了這條Windows訊息,這個操作也有現成的宏
--> WSAGETSELECTEVENT
下面就是原始碼,其中部分很基本的代碼我就省略掉了,編譯平台為
Win2000 Server with SP2 + VC6.0 with
SP5
#include <windows.h>
#include <winsock2.h>
#define PORT 5150
#define DATA_BUFSIZE 8192
typedef struct _SOCKET_INFORMATION {
BOOL RecvPosted;
CHAR
Buffer[DATA_BUFSIZE];
WSABUF DataBuf;
SOCKET Socket;
DWORD
BytesSEND;
DWORD BytesRECV;
_SOCKET_INFORMATION *Next;
}
SOCKET_INFORMATION, * LPSOCKET_INFORMATION;
#define WM_SOCKET (WM_USER + 1)
void CreateSocketInformation(SOCKET s, HWND);
LPSOCKET_INFORMATION
GetSocketInformation(SOCKET s);
void FreeSocketInformation(SOCKET s);
LPSOCKET_INFORMATION SocketInfoList;
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE
hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
DWORD
Ret;
SOCKET Listen;
SOCKADDR_IN InternetAddr;
WSADATA
wsaData;
static TCHAR szAppName[] = TEXT ("HelloWin") ;
HWND hwnd
;
MSG msg ;
WNDCLASS wndclass ;
// Prepare echo server
wndclass.style = CS_HREDRAW | CS_VREDRAW
;
...
...
RegisterClass (&wndclass);
hwnd = CreateWindow
(...) ; // creation parameters
ShowWindow (hwnd, nCmdShow)
;
UpdateWindow (hwnd) ;
if ((Ret = WSAStartup(0x0202, &wsaData))
!= 0)
{
MessageBox(hwnd, TEXT("Start socket failed"), TEXT("error"),
MB_OK);
ExitProcess(1);
}
if ((Listen = socket (PF_INET,
SOCK_STREAM, 0)) == INVALID_SOCKET)
{
MessageBox(hwnd, TEXT("socket()
failed"), TEXT("error"),
MB_OK);
ExitProcess(1);
}
WSAAsyncSelect(Listen, hwnd, WM_SOCKET,
FD_ACCEPT|FD_CLOSE);
InternetAddr.sin_family =
AF_INET;
InternetAddr.sin_addr.s_addr =
htonl(INADDR_ANY);
InternetAddr.sin_port = htons(PORT);
if (bind(Listen, (PSOCKADDR) &InternetAddr,
sizeof(InternetAddr))
== SOCKET_ERROR)
{
MessageBox(hwnd,
TEXT("bind() failed"), TEXT("error"), MB_OK);
ExitProcess(1);
}
if
(listen(Listen, 5))
{
MessageBox(hwnd, TEXT("listen() failed"),
TEXT("error"), MB_OK);
ExitProcess(1);
}
// Translate and dispatch
window messages for the application thread
while (GetMessage (&msg,
NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage
(&msg) ;
}
return msg.wParam ;
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT message,
WPARAM wParam,
LPARAM lParam)
{
HDC hdc ;
PAINTSTRUCT ps ;
RECT rect
;
SOCKET Accept;
LPSOCKET_INFORMATION SocketInfo;
DWORD RecvBytes,
SendBytes;
DWORD Flags;
switch (message)
{
case
WM_CREATE:
return 0 ;
case WM_PAINT:
hdc = BeginPaint
(hwnd, &ps) ;
GetClientRect (hwnd, &rect) ;
DrawText (hdc,
TEXT ("Server Started!"), -1, &rect,
DT_SINGLELINE | DT_CENTER |
DT_VCENTER) ;
EndPaint (hwnd, &ps) ;
return 0 ;
case
WM_DESTROY:
PostQuitMessage (0) ;
return 0 ;
case WM_SOCKET:
if
(WSAGETSELECTERROR(lParam))
{
MessageBox(...);
FreeSocketInformation(wParam);
}
else
{
switch(WSAGETSELECTEVENT(lParam))
{
case
FD_ACCEPT:
if ((Accept = accept(wParam, NULL, NULL)) ==
INVALID_SOCKET)
{
MessageBox(...);
break;
}
// Create a
socket information structure to associate with the
// socket for
processing I/O.
CreateSocketInformation(Accept,
hwnd);
WSAAsyncSelect(Accept, hwnd, WM_SOCKET,
FD_READ|FD_WRITE|FD_CLOSE);
break;
case FD_READ:
SocketInfo =
GetSocketInformation(wParam);
// Read data only if the receive buffer
is empty.
if (SocketInfo->BytesRECV !=
0)
{
SocketInfo->RecvPosted = TRUE;
return
0;
}
else
{
SocketInfo->DataBuf.buf =
SocketInfo->Buffer;
SocketInfo->DataBuf.len =
DATA_BUFSIZE;
Flags = 0;
if (WSARecv(SocketInfo->Socket,
&(SocketInfo->DataBuf),
1, &RecvBytes, &Flags,
NULL, NULL) == SOCKET_ERROR)
{
if (WSAGetLastError() !=
WSAEWOULDBLOCK)
{
MessageBox(...);
FreeSocketInformation(wParam);
return
0;
}
}
else // No error so update the byte
count
SocketInfo->BytesRECV = RecvBytes;
}
// DO
NOT BREAK HERE SINCE WE GOT A SUCCESSFUL RECV.
// Go ahead and begin
writing data to the client.
case FD_WRITE:
SocketInfo =
GetSocketInformation(wParam);
if (SocketInfo->BytesRECV >
SocketInfo->BytesSEND)
{
SocketInfo->DataBuf.buf =
SocketInfo->Buffer +
SocketInfo->BytesSEND;
SocketInfo->DataBuf.len =
SocketInfo->BytesRECV - SocketInfo->BytesSEND;
if
(WSASend(SocketInfo->Socket, &(SocketInfo->DataBuf),
1, &SendBytes, 0,NULL, NULL) ==
SOCKET_ERROR)
{
if (WSAGetLastError() !=
WSAEWOULDBLOCK)
{
MessageBox(...);
FreeSocketInformation(wParam);
return
0;
}
}
else // No error so update the byte
count
SocketInfo->BytesSEND += SendBytes;
}
if (SocketInfo->BytesSEND ==
SocketInfo->BytesRECV)
{
SocketInfo->BytesSEND =
0;
SocketInfo->BytesRECV = 0;
// If a RECV occurred
during our SENDs then we need to post
// an FD_READ notification on
the socket.
if (SocketInfo->RecvPosted ==
TRUE)
{
SocketInfo->RecvPosted =
FALSE;
PostMessage(hwnd, WM_SOCKET, wParam,
FD_READ);
}
}
if(SocketInfo->DataBuf.buf !=
NULL)
MessageBox(hwnd, SocketInfo->DataBuf.buf,
TEXT("Received"), MB_OK);
break;
case
FD_CLOSE:
FreeSocketInformation(wParam);
break;
}
}
return
0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
void CreateSocketInformation(SOCKET s, HWND
hwnd)
{
LPSOCKET_INFORMATION SI;
if ((SI = (LPSOCKET_INFORMATION)
GlobalAlloc(GPTR,
sizeof(SOCKET_INFORMATION))) ==
NULL)
{
MessageBox(...);
return;
}
// Prepare SocketInfo
structure for use.
SI->Socket = s;
SI->RecvPosted =
FALSE;
SI->BytesSEND = 0;
SI->BytesRECV = 0;
SI->Next =
SocketInfoList;
SocketInfoList = SI;
}
LPSOCKET_INFORMATION GetSocketInformation(SOCKET
s)
{
SOCKET_INFORMATION *SI = SocketInfoList;
while(SI)
{
if
(SI->Socket == s)
return SI;
SI = SI->Next;
}
return
NULL;
}
void FreeSocketInformation(SOCKET s)
{
SOCKET_INFORMATION *SI =
SocketInfoList;
SOCKET_INFORMATION *PrevSI =
NULL;
while(SI)
{
if (SI->Socket == s)
{
if
(PrevSI)
PrevSI->Next = SI->Next;
else
SocketInfoList
=
SI->Next;
closesocket(SI->Socket);
GlobalFree(SI);
return;
}
PrevSI
= SI;
SI = SI->Next;
}
}
伺服器就這樣建好了,只需要一個客戶機就可以通訊了,具體的代碼我就不再貼了,各位可
以自己設計客戶機,或者去下載區下載來源程式。最後向大家推薦《Windows網路編程技術》,
真的不錯。
本文中部分內容翻譯自MSDN,客戶機程式使用的是《Windows網路編程技術》中的例子