WSAAsyncSelect是伺服器端的六種I/O模型之一,他的主要思想是運用了windows視窗的消息機制,用函數WSAAsyncSelect()将監聽端口感興趣的網絡消息注冊到視窗,然後在視窗的消息過程中處理,該模型隻提供異步通知,并不提供異步資料傳送,隻适用于系統開銷不大的情況。
使用該模型程式設計,需要基于視窗,以下CreateServerWindow提供了建立視窗的過程。
#include <cassert>
#include <iostream>
#include "ServerWindowCreator.h"
#include "SocketUtil.h"
HWND ServerWindowCreator::CreateServerWindow(HINSTANCE hInstance, LPCTSTR wndClass, WNDPROC wndProc)
{
assert(hInstance);
assert(wndClass);
assert(wndProc);
if(!hInstance || !wndClass || !wndProc)
return NULL;
HWND hWnd = NULL;
if (MyRegisterClass(hInstance, wndClass, wndProc))//注冊視窗類
hWnd = InitInstance(hInstance, wndClass);//建立視窗
return hWnd;
}
bool ServerWindowCreator::MyRegisterClass(HINSTANCE hInstance, LPCTSTR wndClass, WNDPROC wndProc)
{
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = 0;
wcex.lpfnWndProc = wndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = NULL;
wcex.hCursor = NULL;
wcex.hbrBackground = NULL;
wcex.lpszMenuName = NULL;
wcex.lpszClassName = wndClass;
wcex.hIconSm = NULL;
ATOM ret = RegisterClassEx(&wcex);
assert(ret!=NULL || ::GetLastError()==ERROR_CLASS_ALREADY_EXISTS);
return ret!=NULL || ::GetLastError()==ERROR_CLASS_ALREADY_EXISTS;
}
HWND ServerWindowCreator::InitInstance(HINSTANCE hInstance, LPCTSTR wndClass)
{
HWND hWnd = CreateWindow(wndClass, NULL, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, 0, 0, NULL, NULL, hInstance, NULL);
assert(hWnd);
if(!hWnd)
{
DWORD dwError = GetLastError();
std::cout<<"create server window failed"<<std::endl;
}
return hWnd;
}
伺服器端的通信過程
#include "SocketUtil.h"
#include "ServerWindowCreator.h"
#include "ServerSourceHolder.h"
#include "ServerParamDef.h"
#include <iostream>
#pragma comment(lib, "Ws2_32.lib")
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance, LPSTR lpCmdLine,
int nCmdShow)
{
ServerHolder skHoder;//該對象負責管理winsock庫的初始化和清理,監聽socket的釋放,視窗的資源和消息管理
if (!skHoder.Init())
return 0;
SOCKET skListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);//建立監聽socket
if (skListen == INVALID_SOCKET)
{
RecordSocketError("create listen socket failed");
return 0;
}
std::cout<<"listen socket = "<<skListen<<std::endl;
skHoder.HoldSocketFd(skListen);
sockaddr_in addr;
memset(&addr, NULL, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(SERVER_PORT);
if (bind(skListen, (sockaddr*)&addr, sizeof(addr)))//将socket與位址綁定
{
RecordSocketError("bind address failed");
return 0;
}
const TCHAR* WINDOW_CLASS = _T("ServerWindow");//建立視窗
HWND hWnd = ServerWindowCreator::CreateServerWindow(hInstance, WINDOW_CLASS,
ServerHolder::ServerWndProc);
if (!hWnd || !::IsWindow(hWnd))
return 0;
skHoder.HoldWindow(hWnd);
if (WSAAsyncSelect(skListen, hWnd, WM_SOCKET,
FD_ACCEPT | FD_CLOSE | FD_WRITE) != 0)//注冊網絡消息
{
RecordSocketError("set socket error");
return 0;
}
if (listen(skListen, MAX_PENDING_CONN) != 0) //開始監聽
{
RecordSocketError("listen error");
return 0;
}
std::cout<<"start server ..."<<std::endl;
ServerHolder::ServerMessageLoop();//進入視窗消息循環
return 0;
}
WSAAsyncSelect模型的基礎程式設計可以參考《windows網絡程式設計第二版》,裡面有比較詳細的介紹。