首页 winsocket进门教程二:非壅塞式干事器和客户端法度模范(tcp)[专题]

winsocket进门教程二:非壅塞式干事器和客户端法度模范(tcp)[专题]

举报
开通vip

winsocket进门教程二:非壅塞式干事器和客户端法度模范(tcp)[专题]winsocket进门教程二:非壅塞式干事器和客户端法度模范(tcp)[专题] Winsocket入门教程二:非阻塞式服务器和客户端程序(TCP) 收藏 上次为大家介绍了阻塞式多线程服务端程序和阻塞式客户端程序的设计方法,但是在上文的最后也提到过,服务器程序会因为建立连接和关闭连接而频繁的创建和关闭线程会产生大量的内存碎片,从而导致服务端程序不能保证长时间的稳定运行。因此我在这里为大家介绍另外一种建立服务器和客户端程序的方法,即建立非阻塞式的服务器和客户端程序。 那什么是非阻塞呢,非阻塞是相对于阻塞而言,阻...

winsocket进门教程二:非壅塞式干事器和客户端法度模范(tcp)[专题]
winsocket进门教程二:非壅塞式干事器和客户端法度模范(tcp)[专 快递公司问题件快递公司问题件货款处理关于圆的周长面积重点题型关于解方程组的题及答案关于南海问题 ] Winsocket入门教程二:非阻塞式服务器和客户端程序(TCP) 收藏 上次为大家介绍了阻塞式多线程服务端程序和阻塞式客户端程序的设计方法,但是在上文的最后也提到过,服务器程序会因为建立连接和关闭连接而频繁的创建和关闭线程会产生大量的内存碎片,从而导致服务端程序不能保证长时间的稳定运行。因此我在这里为大家介绍另外一种建立服务器和客户端程序的方法,即建立非阻塞式的服务器和客户端程序。 那什么是非阻塞呢,非阻塞是相对于阻塞而言,阻塞指的是在进行一个操作的时候,如服务器接收客户端的连接(accept),服务器或者客户端读写数据(read、write),如果该操作没有执行完成(成功或者失败都算是执行完成),则程序会一直阻塞在操作执行的地方,直到该操作返回一个明确的结果。而非阻塞式程序则不一样,非阻塞式程序会在产生阻塞操作的地方阻塞一定的时间(该时间可以由程序员自己设置)。如果操作没有完成,在到达所设置的时间之后,无论该操作成功与否,都结束该操作而执行程序下面的操作。 为了执行非阻塞操作,我们在创建了一个套接口后,需要将套接口设置为非阻塞的套接口。为了将套接口设置成为非阻塞套接口,我们需要调用ioctlsocket函数将套接口设置为非阻塞的套接口。ioctlsocket函数的定义如下: int ioctlsocket( SOCKET s, long cmd, u_long FAR *argp ) 该函数的作用是控制套接口的I/O模式。 参数s表示要设置的套接口;参数cmd表示要对该套接口设置的命令,为了要将套接口设置成为非阻塞的,我们应该填写FIONBIO;argp表示填写命令的值,如我们要将套接口设置成非阻塞的,我们需要将值设置成为1,如果我们要将套接口设置成为非阻塞状态的话,我们将值设置成为0就是了。 为了进行非阻塞的操作,我们需要在进行操作之前调用select函数,select函数的定义如下: int select(int nfds, fd_set FAR *readfds, fd_set FAR *writefds, fd_set FAR *exceptfds, const struct timeval FAR *timeout ); 该函数设定一个或多个套接口的状态,并进行必要的等待,以便执行异步I/0(非阻塞)操作。 参数nfds被忽略,该参数的作用仅仅是为了与伯克利套接口相兼容;参数readfds表示要检测的可读套接口的集合(该参数可选,可为设置 为NULL);参数readfds表示要检测的可写套接口的集合(该参数可选,可为设置为NULL);参数exceptfds表示要检测的套接口的错误(该参数可选,可为设置为NULL);参数timeout表示执行该函数时需要等待的时间,如果为NULL则表示阻塞操作,为0则表示立即返回。 下面让我们来看看参数类型fd_set,fd_set表示套接字的集合。在使用select函数时,我们需要将相应的套接字加入到相应的集合中。如果集合中的套接字有信号,select函数的返回值即为集合中有信号的套接字数量。 我们用下面的几个宏来操作fd_set集合。我们可以使用FD_SET(s, *set)将套接字s加入到集合set中;我们可以使用FD_CLR(s, *set)将套接字s移除出集合set;我们可以使用FD_ZERO(*set)将集合set清空;最后,我们可以使用FD_ISSET(s, *set)来判断套接字s是否在集合中有信号。 接下来再让我们来看看select函数的三个集合参数readfds、writefds以及exceptfds。 readfds表示可读套接字的集合,可读套接字在三种情况下有信号出现:一、如果集合中有套接字处于监听状态,并且该套接字上有来自客户端的连接请求;二、如果集合中的套接字收到了send操作发送过来的数据;三、如果集合中的套接字被关闭、重置或者中断。 writefds表示可写套接字的集合,可写套接字在两种情况下有信号出现:一、集合中的套接字经过connect操作后,连接成功;二、可以用send操作向集合中的套接字写数据。 exceptfds表示错误套接字的集合,错误套接字在两种情况下有信号出现:一、集合中的套接字经过connect操作后,连接失败;二、有带外数据到来。 在我们了解了创建服务器和客户端程序的 基础知识 税务基础知识象棋入门,基础知识常见鼠类基础知识常用电子元器件基础知识电梯基础知识培训资料 后,我们再来看看示例程序,以加深我们对知识的理解。 程序的运行结果如下所示: 下面是服务器程序的代码: view plaincopy to clipboardprint? 1. #include 2. #include 3. #include 4. #include 5. #pragma comment(lib, "ws2_32.lib") 6. #define ASSERT assert 7. using std::cin; 8. using std::cout; 9. using std::endl; 10. using std::list; 11. typedef list SocketList; 12. typedef list::iterator SocketListIterator; 13. static const int c_iPort = 10001; 14. bool GraceClose(SOCKET *ps); 15. int main() 16. { 17. int iRet = SOCKET_ERROR; 18. // 初始化Winsocket,所有Winsocket程序必须先使用WSAStartup 进行初始化 19. WSADATA data; 20. ZeroMemory(&data, sizeof(WSADATA)); 21. iRet = WSAStartup(MAKEWORD(2, 0), &data); 22. ASSERT(SOCKET_ERROR != iRet); 23. // 建立服务端程序的监听套接字 24. SOCKET skListen = INVALID_SOCKET; 25. skListen = socket(AF_INET, SOCK_STREAM, 0); 26. ASSERT(INVALID_SOCKET != skListen); 27. // 初始化监听套接字地址信息 28. sockaddr_in adrServ; // 表示网络地址 29. ZeroMemory(&adrServ, sizeof(sockaddr_in)); 30. adrServ.sin_family = AF_INET; // 初始化地址格式,只能 为AF_INET 31. adrServ.sin_port = htons(c_iPort); // 初始化端口,由于网络 字节顺序和主机字节顺序相反,所以必须使用htons将主机字节顺序转换成网络字 节顺序 32. adrServ.sin_addr.s_addr = INADDR_ANY; // 初始化IP,由于 是服务器程序,所以可以将INADDR_ANY赋给该字段,表示任意的IP 33. // 绑定监听套接字到本地 34. iRet = bind(skListen, (sockaddr*)&adrServ, sizeof(sockaddr_ in)); 35. ASSERT(SOCKET_ERROR != iRet); 36. // 使用监听套接字进行监听 37. iRet = listen(skListen, FD_SETSIZE); // SOMAXCONN表示可以 连接到该程序的最大连接数 38. ASSERT(SOCKET_ERROR != iRet); 39. cout << "Server began listening..." << endl; 40. // 将套接口从阻塞状态设置到费阻塞状态 41. unsigned long ulEnable = 1; 42. iRet = ioctlsocket(skListen, FIONBIO, &ulEnable); 43. ASSERT(SOCKET_ERROR != iRet); 44. fd_set fsListen; 45. FD_ZERO(&fsListen); 46. fd_set fsRead; 47. FD_ZERO(&fsRead); 48. timeval tv; 49. tv.tv_sec = 1; 50. tv.tv_usec = 0; 51. SocketList sl; 52. for(;;) 53. { 54. // 接收来自客户端的连接, 并将新建的套接字加入 55. // 套接字列表中 56. FD_SET(skListen, &fsListen); 57. iRet = select(1, &fsListen, NULL, NULL, &tv); 58. if(iRet > 0) 59. { 60. sockaddr_in adrClt; 61. int iLen = sizeof(sockaddr_in); 62. ZeroMemory(&adrClt, iLen); 63. SOCKET s = accept(skListen, (sockaddr*)&adrClt,&iLen); 64. ASSERT(INVALID_SOCKET != s); 65. sl.push_back(s); 66. cout << "Server accepted a connection. The socket i s " << s << endl; 67. } 68. // 将套接字列表中的套接字加入到可读套接字集合中 69. // 以便在可以检测集合中的套接字是否有数据可读 70. FD_ZERO(&fsRead); 71. for(SocketListIterator iter = sl.begin(); iter != sl.end(); ++ iter) 72. { 73. FD_SET(*iter, &fsRead); 74. } 75. // 检测集合中的套接字是否有数据可读 76. iRet = select(sl.size(), &fsRead, NULL, NULL, &tv); 77. if(iRet > 0) 78. { 79. for(SocketListIterator iter = sl.begin(); iter != sl.end (); ++iter) 80. { 81. // 如果有数据可读, 则遍历套接字列表中的所有套接字 82. // 检测出有数据可读的套接字 83. iRet = FD_ISSET(*iter, &fsRead); 84. if(iRet > 0) 85. { 86. // 读取套接字上的数据 87. const int c_iBufLen = 512; 88. char szBuf[c_iBufLen + 1] = {'\0'}; 89. int iRead = SOCKET_ERROR; 90. iRead = recv(*iter, szBuf, c_iBufLen, 0); 91. if (0 >= iRead)// 读取出现错误或者对方关闭连接 92. { 93. iRead == 0 ? cout << "Connection shutdown at s ocket " << *iter << endl : 94. cout << "Connection recv error at socket " < < *iter << endl; 95. iRet = GraceClose(&(*iter));// 如果出错则关闭套接字 96. ASSERT(iRet); 97. } 98. else 99. { 100. szBuf[iRead] = '\0'; 101. cout << "Server recved message from socket " < < *iter << ": " << szBuf << endl; 102. // 创建可写集合 103. FD_SET fsWrite; 104. FD_ZERO(&fsWrite); 105. FD_SET(*iter, &fsWrite); 106. // 如果有数据可写, 则向客户端发送数据 107. iRet = select(1, NULL, &fsWrite, NULL, &tv); 108. if (0 < iRet) 109. { 110. int iWrite = SOCKET_ERROR; 111. iWrite = send(*iter, szBuf, iRead, 0); 112. if (SOCKET_ERROR == iWrite) 113. { 114. cout << "Send message error at socket " < < *iter << endl; 115. iRet = GraceClose(&(*iter)); 116. ASSERT(iRet); 117. } 118. } 119. } 120. } 121. } 122. sl.remove(INVALID_SOCKET); // 删除无效的套接字, 套接字在 关闭后被设置为无效 123. } 124. } 125. // 将套接字设置回阻塞状态 126. ulEnable = 0; 127. iRet = ioctlsocket(skListen, FIONBIO, &ulEnable); 128. ASSERT(SOCKET_ERROR != iRet); 129. // 关闭监听套接字 130. iRet = GraceClose(&skListen); 131. ASSERT(iRet); 132. // 清理Winsocket资源 133. iRet = WSACleanup(); 134. ASSERT(SOCKET_ERROR != iRet); 135. system("pause"); 136. return 0; 137. } 138. bool GraceClose(SOCKET *ps) 139. { 140. const int c_iBufLen = 512; 141. char szBuf[c_iBufLen + 1] = {'\0'}; 142. // 关闭该套接字的连接 143. int iRet = shutdown(*ps, SD_SEND); 144. while(recv(*ps, szBuf, c_iBufLen, 0) > 0); 145. if (SOCKET_ERROR == iRet) 146. { 147. return false; 148. } 149. // 清理该套接字的资源 150. iRet = closesocket(*ps); 151. if (SOCKET_ERROR == iRet) 152. { 153. return false; 154. } 155. *ps = INVALID_SOCKET; 156. return true; 157. } #include #include #include #include #pragma comment(lib, "ws2_32.lib")#define ASSERT assertusing std::cin;using std::cout;using std::endl;using std::list;typedef list SocketList;typedef list::iterator SocketListIterator;static const int c_iPort = 10001;bool GraceClose(SOCKET *ps);int main(){int iRet = SOCKET_ERROR; // 初始化Winsocket,所有Winsocket程序必须先使 服务器程序的重点是我们需要将接受自客户端程序的套接字加入到一个链表中,以方便我们的管理。 view plaincopy to clipboardprint? 1. FD_SET(skListen, &fsListen); 2. iRet = select(1, &fsListen, NULL, NULL, &tv); 3. if(iRet > 0) 4. { 5. sockaddr_in adrClt; 6. int iLen = sizeof(sockaddr_in); 7. ZeroMemory(&adrClt, iLen); 8. SOCKET s = accept(skListen, (sockaddr*)&adrClt,&iLen); 9. ASSERT(INVALID_SOCKET != s); 10. sl.push_back(s); 11. cout << "Server accepted a connection. The socket is " < < s << endl; 12. } FD_SET(skListen, &fsListen);iRet = select(1, &fsListen, NULL, Nif(iRet > 0){sockaddr_in adrClt;int iLen = sizeof(sockaddr_ZeroMemory(&adrClt, iLen);SOCKET s = accept(skListen,ASSERT(INVALID_SOCKET != s)sl.push_back(s);cout << "Server accepted a } 然后在套接字被客户端关闭或者异常中断后,我们需要将链表中的套接字设置为无效 套接字,并在操作完所有的套接字一次后,将无效的套接字从集合中移除。 view plaincopy to clipboardprint? 1. if (0 >= iRead)// 读取出现错误或者对方关闭连接 2. { 3. iRead == 0 ? cout << "Connection shutdown at socke t " << *iter << endl : 4. cout << "Connection recv error at socket " << *ite r << endl; 5. iRet = GraceClose(&(*iter));// 如果出错则关闭套接字 6. ASSERT(iRet); 7. } if (0 >= iRead)// 读取出现错误或者对方关闭连接{iRe iReASS} view plaincopy to clipboardprint? 1. sl.remove(INVALID_SOCKET); // 删除无效的套接字, 套接字在关闭后被设置 为无效 sl.remove(INVALID_SOCKET); // 删除无效的套接字, 套接 接下来再让我们来看看客户端程序的代码。 view plaincopy to clipboardprint? 1. #include 2. #include 3. #include 4. #pragma comment(lib, "ws2_32.lib") 5. #define ASSERT assert 6. using std::cin; 7. using std::cout; 8. using std::endl; 9. static const char c_szIP[] = "127.0.0.1"; 10. static const int c_iPort = 10001; 11. bool GraceClose(SOCKET *ps); 12. int main() 13. { 14. int iRet = SOCKET_ERROR; 15. // 初始化Winsocket,所有Winsocket程序必须先使用WSAStartup进行初 始化 16. WSADATA data; 17. ZeroMemory(&data, sizeof(WSADATA)); 18. iRet = WSAStartup(MAKEWORD(2, 0), &data); 19. ASSERT(SOCKET_ERROR != iRet); 20. // 建立连接套接字 21. SOCKET skClient = INVALID_SOCKET; 22. skClient = socket(AF_INET, SOCK_STREAM, 0); 23. ASSERT(INVALID_SOCKET != skClient); 24. // 初始化连接套接字地址信息 25. sockaddr_in adrServ; // 表示网络地址 26. ZeroMemory(&adrServ, sizeof(sockaddr_in)); 27. adrServ.sin_family = AF_INET; // 初始化地址格式,只能为 AF_INET 28. adrServ.sin_port = htons(c_iPort); // 初始化端口,由于网络字 节顺序和主机字节顺序相反,所以必须使用htons将主机字节顺序转换成网络字节顺 序 29. adrServ.sin_addr.s_addr = inet_addr(c_szIP); // 初始化IP, 由于网 络字节顺序和主机字节顺序相反,所以必须使用inet_addr将主机字节顺序转换成网 络字节顺序 30. // 将套接口从阻塞状态设置到非阻塞状态 31. unsigned long ulEnable = 1; 32. iRet = ioctlsocket(skClient, FIONBIO, &ulEnable); 33. ASSERT(SOCKET_ERROR != iRet); 34. fd_set fsWrite; 35. TIMEVAL tv; 36. tv.tv_sec = 1; 37. tv.tv_usec = 0; 38. cout << "Client began to connect to the server..." << endl; 39. for (;;) 40. { 41. // 使用非阻塞方式连接服务器,请注意connect操作的返回值总是为SOC KET_ERROR 42. iRet = connect(skClient, (sockaddr*)&adrServ, sizeof(sockaddr_i n)); 43. int iErrorNo = SOCKET_ERROR; 44. int iLen = sizeof(int); 45. // 如果getsockopt返回值不为0,则说明有错误出现 46. if (SOCKET_ERROR == iRet && 0 != getsockopt(skClient, SOL_S OCKET, SO_ERROR, (char*)&iErrorNo, &iLen)) 47. { 48. cout << "An error happened on connecting to server. The erro r no is " << iErrorNo 49. << ". The program will exit now." << endl; 50. exit(-1); 51. } 52. 53. FD_ZERO(&fsWrite); 54. FD_SET(skClient, &fsWrite); 55. // 如果集合fsWrite中的套接字有信号,则说明连接成功,此时iRet的返回 值大于0 56. iRet = select(1, NULL, &fsWrite, NULL, &tv); 57. if (0 < iRet) 58. { 59. cout << "Successed connect to the server..." << endl; 60. break; 61. } 62. } 63. for(;;) 64. { 65. const int c_iBufLen =512; 66. char szBuf[c_iBufLen + 1] = {'\0'}; 67. cout << "what you will say:"; 68. cin >> szBuf; 69. if(0 == strcmp("exit", szBuf)) 70. { 71. break; 72. } 73. FD_ZERO(&fsWrite); 74. FD_SET(skClient, &fsWrite); 75. // 如果集合fsWrite中的套接字有信号, 则可以用send操作写数据 76. iRet = select(1, NULL, &fsWrite, NULL, &tv); 77. if (0 < iRet) 78. { 79. iRet = send(skClient, szBuf, strlen(szBuf), 0); 80. if(SOCKET_ERROR == iRet) 81. { 82. cout << "send error." << endl; 83. break; 84. } 85. fd_set fsRead; 86. FD_ZERO(&fsRead); 87. FD_SET(skClient, &fsRead); 88. // 如果集合fsRead中的套接字有信号, 则可以用recv操作写数据 89. iRet = select(1, &fsRead, NULL, NULL, &tv); 90. if (0 < iRet) 91. { 92. iRet = recv(skClient, szBuf, c_iBufLen, 0); 93. if(0 == iRet) 94. { 95. cout << "connection shutdown." << endl; 96. break; 97. } 98. else if(SOCKET_ERROR == iRet) 99. { 100. cout << "recv error." << endl; 101. break; 102. } 103. szBuf[iRet] = '\0'; 104. cout << szBuf << endl; 105. } 106. } 107. } 108. // 将套接字设置回阻塞状态 109. ulEnable = 0; 110. iRet = ioctlsocket(skClient, FIONBIO, &ulEnable); 111. ASSERT(SOCKET_ERROR != iRet); 112. // 关闭监听套接字 113. iRet = GraceClose(&skClient); 114. ASSERT(iRet); 115. // 清理Winsocket资源 116. iRet = WSACleanup(); 117. ASSERT(SOCKET_ERROR != iRet); 118. system("pause"); 119. return 0; 120. } 121. bool GraceClose(SOCKET *ps) 122. { 123. const int c_iBufLen = 512; 124. char szBuf[c_iBufLen + 1] = {'\0'}; 125. // 关闭该套接字的连接 126. int iRet = shutdown(*ps, SD_SEND); 127. while(recv(*ps, szBuf, c_iBufLen, 0) > 0); 128. if (SOCKET_ERROR == iRet) 129. { 130. return false; 131. } 132. // 清理该套接字的资源 133. iRet = closesocket(*ps); 134. if (SOCKET_ERROR == iRet) 135. { 136. return false; 137. } 138. *ps = INVALID_SOCKET; 139. return true; 140. } #include #include #include #pragma comment(lib, "ws2_32.lib")#define ASSERT assertusing std::cin;using std::cout;using std::endl;static const char c_szIP[] = "127.0.0.1";static const int c_iPort = 10001;bool GraceClose(SOCKET *ps);int main(){int iRet = SOCKET_ERROR; // 初始化Winsocket,所有Winsocket程序必须先使WSADATA data;ZeroMemory(&data, sizeof(WSADATA));iRet = WSAStartup(MAKEWORD(2, 0), &data); 客户端程序比较简单,并且在代码中已经有十分详细的注释了,所以就不在这里详细说明了。 好了,非阻塞式服务器和客户端程序就介绍到这里,下一集中将向大家介绍一下使用Windows消息机制构建socket程序的方法。
本文档为【winsocket进门教程二:非壅塞式干事器和客户端法度模范&#40;tcp&#41;[专题]】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
该文档来自用户分享,如有侵权行为请发邮件ishare@vip.sina.com联系网站客服,我们会及时删除。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。
本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。
网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。
下载需要: 免费 已有0 人下载
最新资料
资料动态
专题动态
is_196623
暂无简介~
格式:doc
大小:47KB
软件:Word
页数:0
分类:生活休闲
上传时间:2018-08-30
浏览量:7