前言
本文配套代码:https://github.com/TTGuoying/WSAEventSelect-model
由于篇幅原因,本文假设你已经熟悉了利用Socket进行TCP/IP编程的基本原理,并且也熟练的掌握了多线程编程技术,太基本的概念我这里就略过不提了,网上的资料应该遍地都是。
上一篇文章介绍的IOCP模型主要用于服务器,客户端的话一般用WSAEventSelect模型,下面介绍 WSAEventSelect 模型。
由于网络编程中数据何时到来的不可预知,如果我们在线程中用recv()函数一直等待数据的到来会造成cpu的极大浪费,事件选择(WSAEventSelect)模型可以避免这个问题。事件选择(WSAEventSelect)模型原理是:
- 初始化网络环境,创建一个监听的socket,然后进行connect操作。接下来WSACreateEvent()创建一个网络事件对象,其声明如下:
WSAEVENT WSACreateEvent(void); //返回一个手工重置的事件对象句柄
- 再调用WSAEventSelect,来将监听的socket与该事件进行一个关联,其声明如下:
int WSAEventSelect( SOCKET s, //套接字 WSAEVENT hEventObject, //网络事件对象 long lNetworkEvents //需要关注的事件 );
我们客户端只关心FD_READ和FD_CLOSE操作,所以第三个参数传FD_READ | FD_CLOSE。
- 启动一个线程调用WSAWaitForMultipleEvents等待1中的event事件,其声明如下:
DWORD WSAWaitForMultipleEvents( DWORD cEvents, //指定了事件对象数组里边的个数,最大值为64 const WSAEVENT FAR *lphEvents, //事件对象数组 BOOL fWaitAll, //等待类型,TRUE表示要数组里全部有信号才返回,FALSE表示至少有一个就返回,这里必须为FALSE DWORD dwTimeout, //等待的超时时间 BOOL fAlertable //当系统的执行队列有I/O例程要执行时,是否返回,TRUE执行例程返回,FALSE不返回不执行,这里为FALSE );
由于我们是客户端,所以只等待一个事件。
- 当事件发生,我们需要调用WSAEnumNetworkEvents,来检测指定的socket上的网络事件。其声明如下:
int WSAEnumNetworkEvents ( SOCKET s, //指定的socket WSAEVENT hEventObject, //事件对象 LPWSANETWORKEVENTS lpNetworkEvents //WSANETWORKEVENTS<span style="font-family:Arial, Helvetica, sans-serif;">结构地址</span> );
当我们调用这个函数成功后,它会将我们指定的socket和事件对象所关联的网络事件的信息保存到WSANETWORKEVENTS这个结构体里边去,我们来看下这个结构体的声明:
typedef struct _WSANETWORKEVENTS { long lNetworkEvents;<span style="white-space:pre"> </span>//指定了哪个已经发生的网络事件 int iErrorCodes[FD_MAX_EVENTS];<span style="white-space:pre"> </span>//错误码 } WSANETWORKEVENTS, *LPWSANETWORKEVENTS;
根据这个结构体我们就可以判断是否是我们所关注的网络事件已经发生了。如果是我们的读的网络事件发生了,那么我们就调用recv函数进行操作。若是关闭的事件发生了,就调用closesocket将socket关掉,在数组里将其置零等操作。
整个模型的流程图如下:
实现(配合IOCP服务器类测试更佳)
1 #pragma once 2 #include "stdafx.h" 3 #include <WinSock2.h> 4 #include <Windows.h> 5 6 // 释放指针的宏 7 #define RELEASE(x) {if(x != NULL) {delete x; x = NULL;}} 8 // 释放句柄的宏 9 #define RELEASE_HANDLE(x) {if(x != NULL && x != INVALID_HANDLE_VALUE) { CloseHandle(x); x = INVALID_HANDLE_VALUE; }} 10 // 释放Socket的宏 11 #define RELEASE_SOCKET(x) {if(x != INVALID_SOCKET) { closesocket(x); x = INVALID_SOCKET; }} 12 13 class ClientBase 14 { 15 public: 16 ClientBase(); 17 ~ClientBase(); 18 19 // 启动通信 20 BOOL Start(const char *IPAddress, USHORT port); 21 // 关闭通信 22 BOOL Stop(); 23 // 发送数据 24 BOOL Send(const BYTE* buffer, int len); 25 // 是否已启动 26 BOOL HasStarted(); 27 28 // 事件通知函数(派生类重载此族函数) 29 // 连接关闭 30 virtual void OnConnectionClosed() = 0; 31 // 连接上发生错误 32 virtual void OnConnectionError() = 0; 33 // 读操作完成 34 virtual void OnRecvCompleted(BYTE* buffer, int len) = 0; 35 // 写操作完成 36 virtual void OnSendCompleted() = 0; 37 38 private: 39 // 接收线程函数 40 static DWORD WINAPI RecvThreadProc(LPVOID lpParam); 41 // socket是否存活 42 BOOL IsSocketAlive(SOCKET sock); 43 SOCKET clientSock; 44 WSAEVENT socketEvent; 45 HANDLE stopEvent; 46 HANDLE thread; 47 };
1 #include "ClientBase.h" 2 #include <WS2tcpip.h> 3 4 #pragma comment(lib, "WS2_32.lib") 5 6 ClientBase::ClientBase() 7 { 8 WSADATA wsaData; 9 WSAStartup(MAKEWORD(2, 2), &wsaData); 10 } 11 12 13 ClientBase::~ClientBase() 14 { 15 WSACleanup(); 16 } 17 18 BOOL ClientBase::Start(const char *IPAddress, USHORT port) 19 { 20 clientSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 21 if (clientSock == INVALID_SOCKET) 22 return false; 23 socketEvent = WSACreateEvent(); 24 stopEvent = CreateEvent(NULL, FALSE, FALSE, NULL); 25 26 sockaddr_in serAddr; 27 serAddr.sin_family = AF_INET; 28 serAddr.sin_port = htons(port); 29 inet_pton(AF_INET, IPAddress, &serAddr.sin_addr); 30 //serAddr.sin_addr.S_un.S_addr = inet_addr(IPAddress); 31 if (connect(clientSock, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR) 32 { //连接失败 33 closesocket(clientSock); 34 return false; 35 } 36 if (0 != WSAEventSelect(clientSock, socketEvent, FD_READ | FD_CLOSE)) 37 return false; 38 39 thread = CreateThread(0, 0, RecvThreadProc, (void *)this, 0, 0); 40 return true; 41 } 42 43 BOOL ClientBase::Stop() 44 { 45 SetEvent(stopEvent); 46 WaitForSingleObject(thread, INFINITE); 47 RELEASE_SOCKET(clientSock); 48 WSACloseEvent(socketEvent); 49 RELEASE_HANDLE(stopEvent); 50 return true; 51 } 52 53 BOOL ClientBase::Send(const BYTE * buffer, int len) 54 { 55 if (SOCKET_ERROR == send(clientSock, (char*)buffer, len, 0)) 56 { 57 return false; 58 } 59 return true; 60 } 61 62 BOOL ClientBase::HasStarted() 63 { 64 return 0; 65 } 66 67 DWORD ClientBase::RecvThreadProc(LPVOID lpParam) 68 { 69 if (lpParam == NULL) 70 return 0; 71 72 ClientBase *client = (ClientBase *)lpParam; 73 DWORD ret = 0; 74 int index = 0; 75 WSANETWORKEVENTS networkEvent; 76 HANDLE events[2]; 77 events[0] = client->socketEvent; 78 events[1] = client->stopEvent; 79 80 while (true) 81 { 82 ret = WSAWaitForMultipleEvents(2, events, FALSE, INFINITE, FALSE); 83 if (ret == WSA_WAIT_FAILED || ret == WSA_WAIT_TIMEOUT) 84 continue; 85 index = ret - WSA_WAIT_EVENT_0; 86 if (index == 0) 87 { 88 WSAEnumNetworkEvents(client->clientSock, events[0], &networkEvent); 89 if (networkEvent.lNetworkEvents & FD_READ) 90 { 91 if (networkEvent.iErrorCode[FD_READ_BIT != 0]) 92 { 93 //Error 94 continue; 95 } 96 char *buff = (char*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 4096); 97 ret = recv(client->clientSock, buff, 4096, 0); 98 if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET)) 99 { 100 client->OnConnectionClosed(); 101 break; //错误 102 } 103 client->OnRecvCompleted((BYTE*)buff, ret); 104 } 105 if (networkEvent.lNetworkEvents & FD_CLOSE) 106 { 107 108 client->OnConnectionClosed(); 109 break; //关闭 110 } 111 } 112 else 113 { 114 client->OnConnectionClosed(); 115 break; // 退出 116 } 117 118 } 119 return 1; 120 } 121 122 BOOL ClientBase::IsSocketAlive(SOCKET sock) 123 { 124 return 0; 125 }
1 #include "ClientBase.h" 2 #include <stdio.h> 3 4 class Client : public ClientBase 5 { 6 public: 7 // 连接关闭 8 virtual void OnConnectionClosed() 9 { 10 printf(" Close\\n"); 11 } 12 // 连接上发生错误 13 virtual void OnConnectionError() 14 { 15 printf(" Error\\n"); 16 } 17 // 读操作完成 18 virtual void OnRecvCompleted(BYTE* buffer, int len) 19 { 20 printf("recv[%d]:%s\\n", len, (char*)buffer); 21 } 22 // 写操作完成 23 virtual void OnSendCompleted() 24 { 25 printf("*Send success\\n"); 26 } 27 28 }; 29 30 int main() 31 { 32 Client client; 33 if (!client.Start("127.0.0.1", 10240)) 34 { 35 printf(" start error\\n"); 36 } 37 38 int i = 0; 39 while (true) 40 { 41 char buff[128]; 42 //scanf_s("%s", &buff, 128); 43 44 45 sprintf_s(buff, 128, "第%d条Msg", i++); 46 Sleep(500); 47 client.Send((BYTE*)buff, strlen(buff)+1); 48 } 49 }