异步io通知 WSAEventSelect

版权声明: https://blog.csdn.net/dashoumeixi/article/details/85329225

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;
}

猜你喜欢

转载自blog.csdn.net/dashoumeixi/article/details/85329225
今日推荐