1. 程式人生 > >網路伺服器程式設計——非同步選擇模型

網路伺服器程式設計——非同步選擇模型

4.3.2非同步選擇模型

非同步選擇WSAAsyncSelect是Select模型的非同步版本。在Select模型中,呼叫select()函式會發生阻塞;而WSAAsyncSelect模型在呼叫WSAAsyncSelect()函式時,它會通知系統感興趣的網路事件,然後立即返回。

在前面,我們在windows下建立的都是控制檯程式;本小節的程式碼則是windows應用程式。使用WSAAsyncSelect模型,必須在應用程式中建立一個視窗,併為視窗提供回撥函式(視窗處理函式)。

//非同步選擇TCP伺服器端程式碼

#include <iostream>

#include

 <winsock.h>

#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結構體用來儲存視窗資訊

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。