网络服务器编程——异步选择模型

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。

猜你喜欢

转载自blog.csdn.net/Bing_bing_bing_/article/details/83655977