天天看點

伺服器端WSAAsyncSelect模型程式設計

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網絡程式設計第二版》,裡面有比較詳細的介紹。