介绍
应用程序不会持续等待单个网络事件发生,而是以不断轮询的方式不断询问各个socket的网络状态,直到有网络io条件满足的socket产生为止。
创建socket后,在非阻塞io模式下,发生网络时执行过程为,执行系统调用,如果当前io条件不满足,则立即返回。大多数情况下,调用失败代码为,WSAEWOULDBLOCK。这意味着请求的操作在调用期间美元完成。应用程序会等一段时间,再次执行该调用,直到返回成功为止。
相关函数
int ioctlsocket(SOCKET s;long cmd;u_long *argp;);
s:套接字句柄。
cmd:在套接字s上执行的命令。它的可选值主要有:
- FIONBIO:参数argp指向一个无符号长整型数值。将argp设置为非0值,表示启用套接字的非阻塞模式;将argp设置为0,表示禁用套接字的非阻塞模式。
- FIONREAD:返回一次调用接收函数可以读取的数据量,即决定可以从套接字s中读取的网络输入缓冲区中挂起的数据的数量,参数argp指向一个无符号长整型数值,用于保存调用ioctlsocket()函数的结果。
- SIOCATMARK:用于决定是否所有带外数据都已经被读取。
● argp:指针变量,指明cmd命令的参数。
在ioctlsocket()函数中使用FIONBIO,并将argp参数设置为非0值,可以将已创建的套接字设置为非阻塞模式。
流程图
代码
#include <WinSock2.h>#include <Windows.h>#include <WS2tcpip.h>#include <stdlib.h>#include <stdio.h>#pragma comment(lib,"ws2_32.lib")#define DEFAULT_BUFLEN 512#define DEFAULT_PORT 27015int __cdecl main(int argc,TCHAR* argv[]) {WSADATA wsaData;int iResult;SOCKET ServerSocket = INVALID_SOCKET;SOCKET AcceptSocket = INVALID_SOCKET;char recvbuf[DEFAULT_BUFLEN];int recvBufLen = DEFAULT_BUFLEN;sockaddr_in addrClient;int addrClinetLen = sizeof(sockaddr_in);//初始化socketiResult = WSAStartup(MAKEWORD(2, 2), &wsaData);if (iResult!=0){printf_s("wsatartup faild with error code %d\n", iResult);return 1;}//创建监听socketServerSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);if (ServerSocket==INVALID_SOCKET){printf_s("SOCKET FAILED WITH ERROR CODE %d",WSAGetLastError());WSACleanup();return 1;}//绑定地址和端口SOCKADDR_IN addrServ;addrServ.sin_family = AF_INET;addrServ.sin_port = htons(DEFAULT_PORT); //监听端口为DEFAULT_PORTaddrServ.sin_addr.S_un.S_addr = htonl(INADDR_ANY);iResult = bind(ServerSocket,(const struct sockaddr*)&addrServ,sizeof(SOCKADDR_IN));if (iResult==SOCKET_ERROR){printf_s("bind falied with error code %d\n",iResult);closesocket(ServerSocket);WSACleanup();return 1;}//设置socket为非阻塞模式int iMode = 1;iResult = ioctlsocket(ServerSocket, FIONBIO, (u_long*)&iMode);if (iResult==SOCKET_ERROR){printf_s("ioctlsocket failed with error code %d\n",iResult);closesocket(ServerSocket);WSACleanup();return 1;}iResult = listen(ServerSocket,SOMAXCONN);if (iResult==SOCKET_ERROR){printf_s("listen failed with error code %d",iResult);closesocket(ServerSocket);WSACleanup();return -1;}printf_s("TCP server starting");int err;while (true){AcceptSocket = accept(ServerSocket, (sockaddr FAR*) & addrClient, &addrClinetLen);if (AcceptSocket == INVALID_SOCKET){err = WSAGetLastError();if (err==WSAEWOULDBLOCK){Sleep(1000);continue;}else{printf_s("accept afiled \n");closesocket(ServerSocket);WSACleanup();return 1;}}while (true){memset(recvbuf, 0, recvBufLen);iResult = recv(AcceptSocket, recvbuf, recvBufLen, 0);if (iResult>0){printf_s("recv data %d byte\n",iResult);int iSendResult = send(AcceptSocket, recvbuf, iResult, 0);if (iSendResult == SOCKET_ERROR) {printf("send failed with error: %d\n", WSAGetLastError());closesocket(AcceptSocket);WSACleanup();return 1;}printf("Bytes sent: %d\n", iSendResult);continue;}else if (iResult == 0){printf_s("conn closeing ....\n");printf_s("waiting next conn...\n");closesocket(AcceptSocket);break;}else{err = WSAGetLastError();if (err==WSAEWOULDBLOCK){Sleep(1000);printf_s("current io/xx,waitting 1ms\n");continue;}else{printf_s("recv faild with error %d\n",err);closesocket(ServerSocket);closesocket(AcceptSocket);WSACleanup();return 1;}}}}closesocket(ServerSocket);closesocket(AcceptSocket);WSACleanup();return 0;}
阻塞模式
实例主要涉及两个常见io操作,accept()和recv(),数据接收到的时间是不确定的。
在阻塞模式中,这两个函数在网络io不满足的时候会阻塞卡住。
而在非阻塞模型中的函数处理则不同。
非阻塞模式:
改为非阻塞模式后,调用accept后,即使io条件不满足也会返回。
调用WSAGetLlastError 获取错误码,如果错误码为WSAEWOULDBLOCK,则表示无法立即完成socket操作,此时需要再次尝试accept或recv()操作,本实例使用sleep函数休息100ms,然后执行continue,返回到循环开始部分,重新调用函数进行io判断。
轮询方式实现了对多个套接字I/O条件的判断和处理
评价
优点:
- 非阻塞io使用简单的方法使程序避免在io等待的地方阻塞等待,进程不再睡眠等待,在这段时间内可以做其他事。
- 函数轮询的间隙可以对其他socket 进行类似操作。
- 避免了串型io带来的效率低下问题
缺点:
- 由于应用程序需不断尝试接口函数的调用,直到成功完成指定的操作,这对CPU时间是较大的浪费。
- 另外,如果设置了较长的延迟时间,那么最后一次成功的I/O操作对于I/O事件发生而言有滞后时间,因此这种方法并不适合对实时性要求比较高的应用。
