阻塞winsocket入门教程二:非阻塞式服务器和客户端程序tcp

服务器教程  时间:2021-03-22  阅读:()

Winsocket入门教程二非阻塞式服务器和客户端程序(TCP)收藏

上次为大家介绍了阻塞式多线程服务端程序和阻塞式客户端程序的设计方法但是在上文的最后也提到过服务器程序会因为建立连接和关闭连接而频繁的创建和关闭线程会产生大量的内存碎片从而导致服务端程序不能保证长时间的稳定运行。因此我在这里为大家介绍另外一种建立服务器和客户端程序的方法即建立非阻塞式的服务器和客户端程序。

那什么是非阻塞呢非阻塞是相对于阻塞而言阻塞指的是在进行一个操作的时候如服务器接收客户端的连接(accept)服务器或者客户端读写数据(read、write)如果该操作没有执行完成(成功或者失败都算是执行完成)则程序会一直阻塞在操作执行的地方直到该操作返回一个明确的结果。而非阻塞式程序则不一样非阻塞式程序会在产生阻塞操作的地方阻塞一定的时间(该时间可以由程序员自己设置)。如果操作没有完成在到达所设置的时间之后无论该操作成功与否都结束该操作而执行程序下面的操作。

为了执行非阻塞操作我们在创建了一个套接口后需要将套接口设置为非阻塞的套接口。为了将套接口设置成为非阻塞套接口我们需要调用ioctlsocket函数将套接口设置为非阻塞的套接口。 ioctlsocket函数的定义如下int ioctlsocket( SOCKET s, long cmd, u_long FAR*argp)

该函数的作用是控制套接口的I/O模式。

参数s表示要设置的套接口参数cmd表示要对该套接口设置的命令为了要将套接口设置成为非阻塞的我们应该填写FIO NBIO 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(非阻塞)操作。

参数n fds被忽略该参数的作用仅仅是为了与伯克利套接口相兼容参数read fds表示要检测的可读套接口的集合(该参数可选可为设置

为NULL)参数readfds表示要检测的可写套接口的集合(该参数可选可为设置为NUL

L)参数exceptfds表示要检测的套接口的错误(该参数可选可为设置为NULL)参数timeout表示执行该函数时需要等待的时间如果为NULL则表示阻塞操作为0则表示立即返回。

下面让我们来看看参数类型fd_set fd_set表示套接字的集合。在使用select函数时我们需要将相应的套接字加入到相应的集合中。如果集合中的套接字有信号 select函数的返回值即为集合中有信号的套接字数量。

我们用下面的几个宏来操作fd_set集合。我们可以使用FD_SET(s, *set)将套接字s加入到集合set中我们可以使用FD_C LR(s, *set)将套接字s移除出集合set我们可以使用FD_ZERO(*set)将集合set清空最后我们可以使用FD_ISSET(s,

*set)来判断套接字s是否在集合中有信号。

接下来再让我们来看看select函数的三个集合参数readfds、writefds以及exceptfd s。readfds表示可读套接字的集合可读套接字在三种情况下有信号出现一、如果集合中有套接字处于监听状态并且该套接字上有来自客户端的连接请求二、如果集合中的套接字收到了send操作发送过来的数据三、如果集合中的套接字被关闭、重置或者中断。writefds表示可写套接字的集合可写套接字在两种情况下有信号出现一、集合中的套接字经过connect操作后连接成功二、可以用send操作向集合中的套接字写数据。exceptfds表示错误套接字的集合错误套接字在两种情况下有信号出现一、集合

中的套接字经过connect操作后连接失败二、有带外数据到来。

在我们了解了创建服务器和客户端程序的基础知识后我们再来看看示例程序 以加深我们对知识的理解。

程序的运行结果如下所示

下面是服务器程序的代码view plaincopy to cl ipboardprint?

1. #include <iostream>

2. #include <cassert>

3. #include<list>

4. #include<WinSock2.h>

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<SOCKET>SocketList;

12. typedef list<SOCKET>: : 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程序必须先使用WSAStartu p

进行初始化

19. WSADATA data;

20. ZeroMemory(&data,sizeof(WSADATA));

21. iRet=WSAStartup(MAKEWORD(2,0),&data);

22. ASSE RT(SOCKET_E RROR!=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. ASSE RT(SOCKET_E RROR!=iRet);

36. //使用监听套接字进行监听

37. iRet=listen(skListen,FD_SETSIZE);//SOMAXCONN表示可以连接到该程序的最大连接数

38. ASSE RT(SOCKET_E RROR!=iRet);

39. cout<<"Server began listening..."<<endl;

40. //将套接口从阻塞状态设置到费阻塞状态

41. unsigned long ulEnable=1;

42. iRet=ioctlsocket(skListen, FIO NBIO,&ulEnable);

43. ASSE RT(SOCKET_E RROR!=iRet);

44. fd_set fsListen;

45. FD_ZE RO(&fsListen);

46. fd_set fsRead;

47. FD_ZE RO(&fsRead);

48. timeval tv;

49. tv.tv_s e c =1;

50. tv.tv_u sec=0;

51. SocketList sl;

52. fo r(;;)

53. {

54. //接收来自客户端的连接,并将新建的套接字加入

55. //套接字列表中

56. FD_SET(skListen,&fsListen);

57. iRet=se lect(1,&fsListen,NU LL,NU LL,&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 is"<<s<<endl;

67. }

68. //将套接字列表中的套接字加入到可读套接字集合中

69. //以便在可以检测集合中的套接字是否有数据可读

70. FD_ZE RO(&fsRead);

71. for(SocketListIterator iter=sl.beg in(); iter !=sl.end();++iter)

72. {

73. FD_SET(*iter,&fsRead);

74. }

75. //检测集合中的套接字是否有数据可读

76. iRet=se lect(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 socket "<<*iter<<end l :

94. cout<<"Connection recv error at socket "<<*iter<<end l;

95. iRet=GraceC lose(&(*iter));//如果出错则关闭套接字

96. ASSE RT(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_ZE RO(&fsWrite);

105. FD_SET(*iter,&fsWrite);

106. //如果有数据可写,则向客户端发送数据

107. iRet=sel ect(1,NU LL,&fsWr ite,NU LL,&tv);

108. if (0<iRet)

109. {

110. int iWrite=SOCKET_ERROR;

111. iWrite=send(*iter,szB uf, iRead,0);

112. if (SOCKET_ERROR==iWrite)

113. {

114. cout<<"Send message error at socket "<<*iter<<end l;

115. iRet=GraceC lose(&(*iter));

116. ASSERT(iRet);

117. }

118. }

119. }

120. }

121. }

122. sl.remove(INVALID_SOCKET);//删除无效的套接字,套接字在关闭后被设置为无效

123. }

124. }

125. //将套接字设置回阻塞状态

126. ulEnable=0;

127. iRet=ioctlsocket(skListen, FIO NBIO,&ulEnable);

128. ASSE RT(SOCKET_E RROR!=iRet);

129. //关闭监听套接字

130. iRet=GraceClose(&skListen);

131. ASSE RT(iRet);

132. //清理Win socket资源

133. iRet=WSACleanup();

134. ASSE RT(SOCKET_E RROR!=iRet);

135. system("pau se");

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 (SOC KET_E RROR==iRet)

146. {

147. return false;

148. }

149. //清理该套接字的资源

150. iRet=closesocket(*ps);

151. if (SOC KET_E RROR==iRet)

152. {

153. return false;

154. }

155. *ps=INVALID_SOCKET;

156. return true;

157.}

服务器程序的重点是我们需要将接受自客户端程序的套接字加入到一个链表中 以方便我们的管理。view plaincopy to cl ipboardprint?

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

Vinahost - 越南VPS主机商月6美元 季付以上赠送时长最多半年

Vinahost,这个主机商还是第一次介绍到,翻看商家的介绍信息,是一家成立于2008年的老牌越南主机商,业务涵盖网站设计、域名、SSL证书、电子邮箱、虚拟主机、越南VPS、云计算、越南服务器出租以及设备托管等,机房主要在越南胡志明市的Viettle和VNPT数据中心,其中VNPT数据中心对于国内是三网直连,速度优。类似很多海外主机商一样,希望拓展自己的业务,必须要降价优惠或者增加机房迎合需求用户...

提速啦(24元/月)河南BGP云服务器活动 买一年送一年4核 4G 5M

提速啦的来历提速啦是 网站 本着“良心 便宜 稳定”的初衷 为小白用户避免被坑 由赣州王成璟网络科技有限公司旗下赣州提速啦网络科技有限公司运营 投资1000万人民币 在美国Cera 香港CTG 香港Cera 国内 杭州 宿迁 浙江 赣州 南昌 大连 辽宁 扬州 等地区建立数据中心 正规持有IDC ISP CDN 云牌照 公司。公司购买产品支持3天内退款 超过3天步退款政策。提速啦的市场定位提速啦主...

pacificrack:超级秒杀,VPS低至$7.2/年,美国洛杉矶VPS,1Gbps带宽

pacificrack又追加了3款特价便宜vps搞促销,而且是直接7折优惠(一次性),低至年付7.2美元。这是本月第3波便宜vps了。熟悉pacificrack的知道机房是QN的洛杉矶,接入1Gbps带宽,KVM虚拟,纯SSD RAID10,自带一个IPv4。官方网站:https://pacificrack.com支持PayPal、支付宝等方式付款7折秒杀优惠码:R3UWUYF01T内存CPUSS...

服务器教程为你推荐
投标在线代理linesnswordpress模板wordpress模板和主题是一个概念么复制党,广告党绕路支付宝蜻蜓发布刷脸支付加盟,支付宝蜻蜓刷脸设备出后,微信也出了青蛙刷脸设备,感觉很有前景,大伙觉得呢?sqlserver2000挂起SQL server2000 安装为什么老是提示挂起?温州商标注册温州注册公司在哪里注册科创板首批名单首批公布的24个历史文化明城是那些三五互联科技股份有限公司三五互联 网站做的怎么样 公司打算做网站,近来接到电话,不知道网站做的如何,水平怎么样,后期的服务呢什么是通配符DOS命令具体讲的是什么?discuz伪静态DZ怎么开启全站伪静态
云服务器租用 沈阳虚拟主机 美国vps评测 directspace mobaxterm 一元域名 中国特价网 天互数据 什么是服务器托管 服务器合租 能外链的相册 根服务器 免费邮件服务器 512mb web服务器是什么 东莞idc 阿里云官方网站 德隆中文网 广州虚拟主机 防cc攻击 更多