简介
WSAEventSelect 模型也是 WinSock 中最常见的异步 I/O 模型。
这篇文章我们就来看看如何使用 WSAEventSelect api 来实现一个简单的 TCP 服务器.
API 基础
WSAEventSelect
WSAEventSelect 用来把一个 SOCKET 对象和一个 WSAEVENT 对象关联起来。 lNetworkEvents 表示我们关心的 FD_XXX 网络事件. 如果关心多个 SOCKET 事件,可以使用 OR 的方式指定多个 FD_XXX 标志。
int WSAAPI WSAEventSelect(
SOCKET s,
WSAEVENT hEventObject,
long lNetworkEvents
);
当特定的事件发生在相应的 SOCKET 上,该 SOCKET 上接下来的事件将会被阻塞,直到当前的事件被应用处理. 该事件被处理之后,接下来的事件将可以被进一步触发.
事件 | 处理函数 |
---|---|
FD_READ | recv, recvFrom, WSARecv, WSARecvEx, WSARecvFrom |
FD_WRITE | send, sendTo, WSASend, WSASentTo |
FD_OOB | recv, recvFrom, WSARecv, WSARecvEx, WSARecvFrom |
FD_ACCEPT | accept, AcceptEx, WSAAccept |
FD_CONNECT | None |
FD_CLOSE | None |
FD_QOS | WSAIoctl (with SIO_GET_QOS) |
FD_GROUP_QOS | Reserved |
FD_ROUTINE_INTERFACE_CHANGE | WSAIoctl (with SIO_ROUTINE_INTERFACE_CHANGE) |
FD_ADDRESS_LIST_CHANGE | WSAIoctl (with SIO_ADDRESS_LIST_CHANGE) |
WSAEvent
WSACreateEvent 方法用来创建一个 WSAEvent 对象
WSAEVENT WSAAPI WSACreateEvent();
WSAWaitForMultipleEvents 用于等待一组事件中的一个或全部被触发。
DWORD WSAAPI WSAWaitForMultipleEvents(
DWORD cEvents,
const WSAEVENT *lphEvents,
BOOL fWaitAll,
DWORD dwTimeout,
BOOL fAlertable
);
- cEvents:指定 lphEvents 数组中事件对象的数量。 该参数的最大值是 WSA_MAXIMUM_WAIT_EVENTS (64)
- lphEvents:事件对象的集合
- fWaitAll: 指定等待 lphEvents 中所有事件被触发或者其中之一被触发。 如果指定为 TRUE, 那么该函数只有在所有事件对象都被触发之后才会返回。 如果指定为 FALSE, 当事件集合中任何一个事件被触发之后,该方法就会返回。如果在这种情况下有多个事件对象被触发,那个返回值将会返回该事件集合中索引值最小的索引值. 索引值减去 WSA_WAIT_EVENT_0 便是指向 lphEvents 中被触发的事件的索引值.
- dwTimeout: 如果在 timeout 事件间隔内,没有事件被触发,函数不会一直阻塞,而是在等待 timeout 毫秒后返回。 指定该参数为 WSA_INFINITE, 该函数会一直等待,直到有事件被触发. 指定该参数为 0, 该函数会立即返回.
- fAlertable: 略
WSAEnumNetworkEvents 用于查询当前 SOCKET 上触发事件对象 hEventObject 的对应 socket 事件(FD_READ, FD_WRITE 等).
int WSAAPI WSAEnumNetworkEvents(
SOCKET s,
WSAEVENT hEventObject,
LPWSANETWORKEVENTS lpNetworkEvents
);
实现思路
- 创建一个 socket 作为监听 socket
- 使用 WSAEventSelect 监听该 SOCKET 上的网络事件
- 使用 WSAWaitForMultipleEvents 等待 SOCKET 事件
- 当 SOCKET 上有事件被触发,使用 WSAEnumNetworkEvents 查询具体的 SOCKET 事件,并使用相应的 API 处理事件.
- 当有新的 SOCKET 连接到来,接收该连接,重复 2-4 步骤.
实例
接下来我们通过一个实例来看看如何实现.
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>
#pragma comment(lib,"ws2_32.lib")
#define PORT 8080
#define DATA_BUFSIZE 8192
typedef struct _SOCKET_CONTEXT {
CHAR Buffer[DATA_BUFSIZE];
WSABUF DataBuf;
SOCKET Socket;
DWORD BytesSEND;
DWORD BytesRECV;
} SOCKET_CONTEXT, * LPSOCKET_CONTEXT;
BOOL CreateSocketInformation(SOCKET s);
void FreeSocketInformation(DWORD Event);
// 这里我们维护了如下数据结构:
// EeventArray: 我们为每个 SOCKET 对象创建一个对应的事件对象,以便我们能监听该 SOCKET 上的网络事件
// SocketArray: 毫无疑问,我们也需要维护所有SOCKET连接的的数组。其中包含 Listen Socket
DWORD EventTotal = 0;
WSAEVENT EventArray[WSA_MAXIMUM_WAIT_EVENTS];
LPSOCKET_CONTEXT SocketArray[WSA_MAXIMUM_WAIT_EVENTS];
int main() {
SOCKET ListenSocket;
SOCKET AcceptSocket;
SOCKADDR_IN Addr;
LPSOCKET_CONTEXT SocketContext;
WSANETWORKEVENTS NetworkEvents;
DWORD Event;
WSADATA wsaData;
DWORD Flags;
DWORD RecvBytes;
DWORD SendBytes;
// 初始化 Listen Socket 对象
if (WSAStartup(0x0202, &wsaData) != 0) {
printf("WSAStartup() failed with error %d\n", WSAGetLastError());
return 1;
}
if ((ListenSocket = socket(AF_INET