异步io通知 WSAEventSelect

6次阅读

共计 4586 个字符,预计需要花费 12 分钟才能阅读完成。

WSAEventSelect 就是 select 的增强版;
注意 WSAEventSelect 是通知异步, 而不是传送数据异步;
总的来说就是一个异步的阻塞模型;
如果要与 select 做个比较的话 :
select 在 需要进行或者可以进行 io 处理时 返回. 而 WSAEventSelect 在返回时 (WSAWaitForMultipleEvents) 与 io 状态无关;
例如: select 在收到数据 并返回时 , 他监听此套接字并检查接受缓冲区, 等到缓冲区能读时再返回.
而 WSAEventSelect 的等待函数 WSAWaitForMultipleEvents 只要此套接字有事件发生就返回;
因此称为异步通知;
另 WSAEventSelect 与 nix 下的 epoll 整体编程模型很像; epoll : epoll 说明具体使用到的函数:
WSACreateEvent 创建一个事件, 默认手动模式
WSAEventSelect 让一个套接字与一个事件捆绑在一起 注册到操作系统, 无需像 select 每次重置;

WSAWaitForMultipleEvents (阻塞) 等待套接字对应的事件发生, 最多能监听 WSA_MAXIMUM_WAIT_EVENTS 个事件;
WSAEnumNetworkEvents 查看套接字对应的具体事件 ; 这也就是与 select 返回时机的不同的原因; 注意: 此函数将重置事件, 因此无需调用 WSAResetEvent;

重要就这 4 个函数, 其他的函数调用查看 msdn 即可;
echo_serv.c
#include “../utils.h”
#define BUFF_SIZE 8192

// 关闭套接字后 . 调整数组
static void adjust_sockarr(SOCKET * sock_arr, int start_index, int total){
for (int i = start_index; i < total; ++i)
sock_arr[i] = sock_arr[i + 1];
}

// 关闭事件后, 调整数组
static void adjust_eventarr(WSAEVENT * event_arr, int start_index, int total){
for (int i = start_index; i < total; ++i)
event_arr[i] = event_arr[i + 1];
}

int _tmain(int argc, _TCHAR* argv[])
{
unsigned short port = 0;
scanf(” %hd”, &port);
WSADATA wsadata;
WSAStartup(MAKEWORD(2,2),&wsadata);
SOCKET listensock = socket(AF_INET, SOCK_STREAM, 0);
SOCKADDR_IN serv_addr, cli_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
memset(&cli_addr, 0, sizeof(cli_addr));
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);

if (bind(listensock, (SOCKADDR*)&serv_addr, sizeof(serv_addr)) == SOCKET_ERROR){
print_error(WSAGetLastError());
return 0;
}

if (listen(listensock, 5) == SOCKET_ERROR){
print_error(WSAGetLastError());
return 0;
}

// 创建一个与监听套接字捆绑的事件
WSAEVENT wsaevent = WSACreateEvent();

//FD_ACCEPT:一旦连接发生, 此事件将发生
if (WSAEventSelect(listensock, wsaevent, FD_ACCEPT) == SOCKET_ERROR){
print_error(WSAGetLastError());
return 0;
}

int event_count = 0 , cli_len = sizeof(cli_addr);

// 准备 2 个数组, 用于存放套接字与事件
SOCKET sock_arr[WSA_MAXIMUM_WAIT_EVENTS] = {0};
WSAEVENT event_arr[WSA_MAXIMUM_WAIT_EVENTS] = {0};

// 把套接字与事件存放在数组里 , 并一一对应
sock_arr[event_count] = listensock;
event_arr[event_count] = wsaevent;
++event_count; // 事件总数 == 套接字总数

DWORD pos , start_index ,event_index , strlen;
WSANETWORKEVENTS netevents;
SOCKET cli_socket;
char buf[BUFF_SIZE];

while (1){

// 等待事件发生, 只要一个事件发生就返回, 具体参数查看 msdn;
pos = WSAWaitForMultipleEvents(event_count, event_arr, FALSE, WSA_INFINITE, FALSE);
printf(“pos:%d , event_count:%d\n”, pos,event_count);

start_index = pos – WSA_WAIT_EVENT_0;

// 循环的意义: 在一个繁忙的服务器上有可能在一个事件发生的瞬间, 又有一个事件发生了

for (int i = start_index; i < event_count; ++i){

// 依次验证从这个已经返回的事件, 以及之后的套接字事件有没有发生.
// 由于 timeout 参数是 0 , 所以非阻塞
event_index = WSAWaitForMultipleEvents(1, event_arr+i, TRUE, 0, FALSE);

if (WSA_WAIT_FAILED == event_index || WSA_WAIT_TIMEOUT == event_index){
printf(“index:%d , timeout or failed\n”, i);
continue;
}

// 有事件发生了, 查看套接字对应的具体事件是什么
if (WSAEnumNetworkEvents(sock_arr[i], event_arr[i], &netevents) == SOCKET_ERROR){
_tprintf(TEXT(“WSAEnumNetworkEvents error : index:%d \ndetail:”), i);
print_error(WSAGetLastError());
continue;
}

strlen = sprintf(buf, “sock_arr[%d] occurs “, i);
buf[strlen] = 0;
if (netevents.lNetworkEvents & FD_CONNECT)
strcat(buf, ” connect “);
if (netevents.lNetworkEvents & FD_ACCEPT)
strcat(buf , ” accept “);
if (netevents.lNetworkEvents & FD_WRITE)
strcat(buf , ” write “);
if (netevents.lNetworkEvents & FD_CLOSE)
strcat(buf , ” close “);
puts(buf);

// 如果有连接
if (netevents.lNetworkEvents & FD_ACCEPT){
// 如果有错
if (netevents.iErrorCode[FD_ACCEPT_BIT] != 0){
_tprintf(TEXT(“accept error , index:%d,detail:\n”),i);
print_error(WSAGetLastError());
continue;
}
cli_len = sizeof(cli_addr);

cli_socket = accept(sock_arr[i], (SOCKADDR*)&cli_addr, &cli_len);

// 给新的连接创建事件
wsaevent = WSACreateEvent();
// 把套接子与事件注册进操作系统, 用于监听
if (WSAEventSelect(cli_socket, wsaevent, FD_READ | FD_CLOSE) != SOCKET_ERROR){
// 放进数组
sock_arr[event_count] = cli_socket;
event_arr[event_count] = wsaevent;
++event_count;
printf(“new client ip:%s, port:%d\n”, inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));
}
}

// 如果套接字能读
if (netevents.lNetworkEvents & FD_READ){
if (netevents.iErrorCode[FD_READ_BIT] != 0){
_tprintf(TEXT(“read error , index:%d detail:\n”), i);
print_error(WSAGetLastError());
continue;
}
strlen = recv(sock_arr[i], buf, BUFF_SIZE, 0);
buf[strlen] = 0;
strlen = send(sock_arr[i], buf, strlen, 0);
printf(“recv len : %d, buf:%s\n”, strlen, buf);
}

// 如果对端关闭了
if (netevents.lNetworkEvents & FD_CLOSE){
// 关闭事件
WSACloseEvent(event_arr[i]);
closesocket(sock_arr[i]);
–event_count;

// 调整数组
adjust_sockarr(sock_arr, i, event_count);
adjust_eventarr(event_arr, i, event_count);
if (netevents.iErrorCode[FD_CLOSE_BIT] != 0){
_tprintf(TEXT(“close error , index:%d , error:%d .detail:\n”), i, netevents.iErrorCode[FD_CLOSE_BIT]);
print_error(WSAGetLastError());
continue;
}
cli_len = sizeof(cli_addr);
if (getpeername(sock_arr[i], (SOCKADDR*)&cli_addr, &cli_len) != SOCKET_ERROR){
printf(“peer ip:%s, port:%d\n”, inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));
}
printf(“sock_arr[%d] closed\n”, i);
}
}
}

WSACleanup();
return 0;
}

正文完
 0