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;
- WSAEnumNetworkEvents 查看套接字对应的具体事件 ; 这也就是与select 返回时机的不同的原因; 注意:此函数将重置事件,
重要就这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;
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。