介绍

应用程序不会持续等待单个网络事件发生,而是以不断轮询的方式不断询问各个socket的网络状态,直到有网络io条件满足的socket产生为止。
创建socket后,在非阻塞io模式下,发生网络时执行过程为,执行系统调用,如果当前io条件不满足,则立即返回。大多数情况下,调用失败代码为,WSAEWOULDBLOCK。这意味着请求的操作在调用期间美元完成。应用程序会等一段时间,再次执行该调用,直到返回成功为止。

相关函数

  1. int ioctlsocket(
  2. SOCKET s;
  3. long cmd;
  4. u_long *argp;
  5. );
  • s:套接字句柄。

  • cmd:在套接字s上执行的命令。它的可选值主要有:

- FIONBIO:参数argp指向一个无符号长整型数值。将argp设置为非0值,表示启用套接字的非阻塞模式;将argp设置为0,表示禁用套接字的非阻塞模式。
- FIONREAD:返回一次调用接收函数可以读取的数据量,即决定可以从套接字s中读取的网络输入缓冲区中挂起的数据的数量,参数argp指向一个无符号长整型数值,用于保存调用ioctlsocket()函数的结果。
- SIOCATMARK:用于决定是否所有带外数据都已经被读取。

● argp:指针变量,指明cmd命令的参数。
在ioctlsocket()函数中使用FIONBIO,并将argp参数设置为非0值,可以将已创建的套接字设置为非阻塞模式。

流程图

非阻塞io - 图1

代码

  1. #include <WinSock2.h>
  2. #include <Windows.h>
  3. #include <WS2tcpip.h>
  4. #include <stdlib.h>
  5. #include <stdio.h>
  6. #pragma comment(lib,"ws2_32.lib")
  7. #define DEFAULT_BUFLEN 512
  8. #define DEFAULT_PORT 27015
  9. int __cdecl main(int argc,TCHAR* argv[]) {
  10. WSADATA wsaData;
  11. int iResult;
  12. SOCKET ServerSocket = INVALID_SOCKET;
  13. SOCKET AcceptSocket = INVALID_SOCKET;
  14. char recvbuf[DEFAULT_BUFLEN];
  15. int recvBufLen = DEFAULT_BUFLEN;
  16. sockaddr_in addrClient;
  17. int addrClinetLen = sizeof(sockaddr_in);
  18. //初始化socket
  19. iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
  20. if (iResult!=0)
  21. {
  22. printf_s("wsatartup faild with error code %d\n", iResult);
  23. return 1;
  24. }
  25. //创建监听socket
  26. ServerSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
  27. if (ServerSocket==INVALID_SOCKET)
  28. {
  29. printf_s("SOCKET FAILED WITH ERROR CODE %d",WSAGetLastError());
  30. WSACleanup();
  31. return 1;
  32. }
  33. //绑定地址和端口
  34. SOCKADDR_IN addrServ;
  35. addrServ.sin_family = AF_INET;
  36. addrServ.sin_port = htons(DEFAULT_PORT); //监听端口为DEFAULT_PORT
  37. addrServ.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
  38. iResult = bind(ServerSocket,(const struct sockaddr*)&addrServ,sizeof(SOCKADDR_IN));
  39. if (iResult==SOCKET_ERROR)
  40. {
  41. printf_s("bind falied with error code %d\n",iResult);
  42. closesocket(ServerSocket);
  43. WSACleanup();
  44. return 1;
  45. }
  46. //设置socket为非阻塞模式
  47. int iMode = 1;
  48. iResult = ioctlsocket(ServerSocket, FIONBIO, (u_long*)&iMode);
  49. if (iResult==SOCKET_ERROR)
  50. {
  51. printf_s("ioctlsocket failed with error code %d\n",iResult);
  52. closesocket(ServerSocket);
  53. WSACleanup();
  54. return 1;
  55. }
  56. iResult = listen(ServerSocket,SOMAXCONN);
  57. if (iResult==SOCKET_ERROR)
  58. {
  59. printf_s("listen failed with error code %d",iResult);
  60. closesocket(ServerSocket);
  61. WSACleanup();
  62. return -1;
  63. }
  64. printf_s("TCP server starting");
  65. int err;
  66. while (true)
  67. {
  68. AcceptSocket = accept(ServerSocket, (sockaddr FAR*) & addrClient, &addrClinetLen);
  69. if (AcceptSocket == INVALID_SOCKET)
  70. {
  71. err = WSAGetLastError();
  72. if (err==WSAEWOULDBLOCK)
  73. {
  74. Sleep(1000);
  75. continue;
  76. }
  77. else
  78. {
  79. printf_s("accept afiled \n");
  80. closesocket(ServerSocket);
  81. WSACleanup();
  82. return 1;
  83. }
  84. }
  85. while (true)
  86. {
  87. memset(recvbuf, 0, recvBufLen);
  88. iResult = recv(AcceptSocket, recvbuf, recvBufLen, 0);
  89. if (iResult>0)
  90. {
  91. printf_s("recv data %d byte\n",iResult);
  92. int iSendResult = send(AcceptSocket, recvbuf, iResult, 0);
  93. if (iSendResult == SOCKET_ERROR) {
  94. printf("send failed with error: %d\n", WSAGetLastError());
  95. closesocket(AcceptSocket);
  96. WSACleanup();
  97. return 1;
  98. }
  99. printf("Bytes sent: %d\n", iSendResult);
  100. continue;
  101. }
  102. else if (iResult == 0)
  103. {
  104. printf_s("conn closeing ....\n");
  105. printf_s("waiting next conn...\n");
  106. closesocket(AcceptSocket);
  107. break;
  108. }
  109. else
  110. {
  111. err = WSAGetLastError();
  112. if (err==WSAEWOULDBLOCK)
  113. {
  114. Sleep(1000);
  115. printf_s("current io/xx,waitting 1ms\n");
  116. continue;
  117. }
  118. else
  119. {
  120. printf_s("recv faild with error %d\n",err);
  121. closesocket(ServerSocket);
  122. closesocket(AcceptSocket);
  123. WSACleanup();
  124. return 1;
  125. }
  126. }
  127. }
  128. }
  129. closesocket(ServerSocket);
  130. closesocket(AcceptSocket);
  131. WSACleanup();
  132. return 0;
  133. }

阻塞模式
实例主要涉及两个常见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事件发生而言有滞后时间,因此这种方法并不适合对实时性要求比较高的应用。