網路伺服器程式設計——非同步選擇模型
4.3.2非同步選擇模型
非同步選擇WSAAsyncSelect是Select模型的非同步版本。在Select模型中,呼叫select()函式會發生阻塞;而WSAAsyncSelect模型在呼叫WSAAsyncSelect()函式時,它會通知系統感興趣的網路事件,然後立即返回。
在前面,我們在windows下建立的都是控制檯程式;本小節的程式碼則是windows應用程式。使用WSAAsyncSelect模型,必須在應用程式中建立一個視窗,併為視窗提供回撥函式(視窗處理函式)。
//非同步選擇:TCP伺服器端程式碼
#include <iostream>
#include
#include <tchar.h>
using namespace std;
#pragma comment(lib, "ws2_32.lib")
#define WM_SOCKET WM_USER+1
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
//windows應用程式的入口函式:WinMain,其引數必須和宣告保持一致;返回0表示正常退出
//引數1:當前例項的控制代碼;引數2:前一個例項的控制代碼;引數3:命令列引數;引數4:窗體顯示形式(最大化、最小化)
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = _T("AsyncSelect Model");
//步驟1:視窗類定義
WNDCLASS wndclass;
wndclass.style = CS_HREDRAW | CS_VREDRAW;//視窗的樣式
wndclass.lpfnWndProc = WndProc;//定義視窗處理函式
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;//當前例項控制代碼,由windows自動分發
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);//視窗的最小化圖示
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); // 視窗游標:採用箭頭
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); //視窗背景:白色
wndclass.lpszMenuName = NULL; //視窗無選單
wndclass.lpszClassName = szAppName; //視窗類名
//步驟2:註冊視窗
if (!RegisterClass(&wndclass))
{
MessageBox(NULL, TEXT("Registration Window Failed!"), szAppName, MB_ICONERROR);
return 0;
}
//步驟3:建立視窗
HWND hwnd;
hwnd = CreateWindow(szAppName, //視窗類名稱
TEXT("AsyncSelect"), //視窗標題
WS_OVERLAPPEDWINDOW, //視窗風格,或稱視窗格式
CW_USEDEFAULT, //視窗相對於父級的X座標
CW_USEDEFAULT, //視窗相對於父級的Y座標
CW_USEDEFAULT, //視窗的寬度
CW_USEDEFAULT, //視窗的高度
NULL, //沒有父視窗,為NULL
NULL, //沒有選單,為NULL
hInstance, //當前應用程式的例項控制代碼
NULL); //沒有附加資料,為NULL
//步驟4:顯示視窗
ShowWindow(hwnd, iCmdShow);
//步驟5:更新視窗
UpdateWindow(hwnd);
//步驟6:從訊息佇列中,取出系統嚮應用程式發出的訊息
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))//訊息迴圈
{
TranslateMessage(&msg);//將訊息轉換為WM_CHAR訊息
DispatchMessage(&msg);//把訊息傳到WindowProc
}
return 0;
}
//引數1:視窗控制代碼;引數2:訊息ID;引數3/4:訊息引數
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static SOCKET sockListen;
SOCKET sockClient;
SOCKADDR_IN addrServer, addrClient;
int len = sizeof(addrClient);
char Buf[1024] = "\0";
int ret;
//步驟7:訊息處理
switch (message)
{
//步驟7.1
case WM_CREATE://建立視窗時,傳送WM_CREATE訊息
WSADATA wsaData;
WSAStartup(0x0202, &wsaData);
sockListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
addrServer.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
addrServer.sin_family = AF_INET;
addrServer.sin_port = htons(6000);
bind(sockListen, (SOCKADDR *)&addrServer, sizeof(addrServer));
listen(sockListen, 3);
WSAAsyncSelect(sockListen, hwnd, WM_SOCKET, FD_ACCEPT);
return 0;
case WM_DESTROY://關閉應用程式
closesocket(sockListen);
WSACleanup();
PostQuitMessage(0);//提交WM_QUIT訊息,GetMessage得到後返回0,因此退出訊息迴圈
return 0;
case WM_SOCKET:
if (WSAGETSELECTERROR(lParam))
{
closesocket(wParam);
break;
}
switch (WSAGETSELECTEVENT(lParam))
{
//步驟7.2
case FD_ACCEPT://伺服器接收連線的通知
sockClient = accept(wParam, (struct sockaddr *)&addrClient, &len);
WSAAsyncSelect(sockClient, hwnd, WM_SOCKET, FD_READ | FD_CLOSE);
break;
//步驟7.3
case FD_READ://套接字可讀通知
ret = recv(wParam, Buf, 1024, 0);
//客戶端和伺服器端斷開連線:ret == 0
if (ret == 0 || ret == SOCKET_ERROR)
{
closesocket(wParam);
}
else
{
//二次開發
cout << Buf << endl;
strcat(Buf, ":Server Received");
send(wParam, Buf, strlen(Buf)+1, 0);
}
break;
//步驟7.4
case FD_CLOSE://套接字關閉通知
closesocket(wParam);
break;
}
return 0;
}
//步驟8:預設訊息處理函式
return DefWindowProc(hwnd, message, wParam, lParam);
}
//TCP客戶端
#include <iostream>
#include <winsock2.h>
#include <windows.h>
#pragma comment(lib,"ws2_32.lib")//引用庫檔案
using namespace std;
int main(int argc, char ** argv)
{
//步驟1:當前應用程式和相應的socket庫繫結
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
{
cout << "WSAStartup Failed!" << endl;
return -1;
}
//步驟2:建立TCP網路套接字
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
//步驟3:設定伺服器端地址結構SOCKADDR_IN
SOCKADDR_IN addr_server;
//3.1 初始化SOCKADDR_IN
memset(&addr_server, 0, sizeof(addr_server));
//3.2 設定地址協議族
addr_server.sin_family = AF_INET;
//3.3 設定伺服器端的IP
addr_server.sin_addr.s_addr = inet_addr("192.168.1.101");
//3.4 設定伺服器端的埠號
addr_server.sin_port = htons(6000);//不能使用公認埠,即埠>= 1024
//步驟4:客戶端連線伺服器
int result = connect(sock, (SOCKADDR*)&addr_server, sizeof(addr_server));
if (result == -1)
return -1;
cout << "Connect Seccessed!" << endl;
//步驟5:向伺服器端傳送資料
char Buf[1024] = "\0";
cin.getline(Buf, 1024);
send(sock, Buf, 1024, 0);
recv(sock, Buf, 1024, 0);
printf("%s\n", Buf);
//步驟6:關閉套接字
closesocket(sock);
//步驟7:將應用程式和socket庫解除繫結
WSACleanup();
return 0;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam):
引數hwnd表示視窗的控制代碼;對於此函式的呼叫,正是由那個視窗發出的。
引數message表示需要對哪些訊息進行處理。
引數wParam表示一個網路事件的套接字,若客戶端發出連線請求,那它就表示伺服器端的監聽套接字;若客戶端傳輸資料,它就表示客戶端的套接字。即相當於選擇模式中的FD_SET。
引數lParam包含兩方面資訊,高位元組包含程式碼的錯誤資訊,可以使用WSAGETSELECTERROR來獲取;低位元組表示已經發生的網路事件,可以用WSAGETSELECTEVENT來獲取。
int PASCAL FAR WSAAsyncSelect(_In_ SOCKET s,_In_ HWND hWnd,_In_ u_int wMsg,_In_ long lEvent):
引數s表示是否有資料傳輸的套接字,立即返回。
引數hWnd指定一個視窗控制代碼,它對應於網路事件發生之後,想要收到通知訊息的那個視窗。
引數wMsg指定在發生網路事件時,打算接收的訊息,該訊息會投遞到由hWnd視窗控制代碼指定的那個視窗。
引數lEvent指定一個位掩碼,對應於一系列網路事件的組合,大多數應用程式通常感興趣的網路事件型別包括:FD_READ、FD_WRITE、FD_ACCEPT、FD_CONNECT、FD_CLOSE。