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

华纳云不限流量¥324/年,香港双向CN2(GIA)云服务器/1核1G/50G存储/2Mbps

华纳云(HNCloud Limited)是一家专业的全球数据中心基础服务提供商,总部在香港,隶属于香港联合通讯国际有限公司,拥有香港政府颁发的商业登记证明,保证用户的安全性和合规性。 华纳云是APNIC 和 ARIN 会员单位。主要提供香港和美国机房的VPS云服务器和独立服务器。商家支持支付宝、网银、Paypal付款。华纳云主要面向国内用户群,所以线路质量还是不错的,客户使用体验总体反响还是比较好...

百纵科技(19元/月),美国洛杉矶10G防御服务器/洛杉矶C3机房 带金盾高防

百纵科技官网:https://www.baizon.cn/百纵科技:美国云服务器活动重磅来袭,洛杉矶C3机房 带金盾高防,会员后台可自助管理防火墙,添加黑白名单 CC策略开启低中高.CPU全系列E52680v3 DDR4内存 三星固态盘列阵。另有高防清洗!美国洛杉矶 CN2 云服务器CPU内存带宽数据盘防御价格1H1G10M10G10G19元/月 购买地址2H1G10M10G10G29元/月 购买...

物语云-VPS-美国洛杉矶VPS无限流量云windows大带宽100M不限流量 26/月起

物语云计算怎么样?物语云计算(MonogatariCloud)是一家成立于2016年的老牌国人商家,主营国内游戏高防独服业务,拥有多家机房资源,产品质量过硬,颇有一定口碑。本次带来的是特惠活动为美国洛杉矶Cera机房的不限流量大带宽VPS,去程直连回程4837,支持免费安装Windows系统。值得注意的是,物语云采用的虚拟化技术为Hyper-v,因此并不会超售超开。一、物语云官网点击此处进入物语云...

服务器教程为你推荐
安顺网易yeah360退出北京时间utc+8 13:30-14:00换成北京时间是什么时候全国企业信息查询有没有可以查全国企业信息的工商查询网站yixingjia合家欢是一种什么东西?12306.com12306身份信息待核验要多久?审核要多久3g手机有哪些电信3g手机有哪些?骑士人才系统公司要采购一套人才系统源码,看了一下骑士和嘉缘的,谁家的比较好一点呢?托就不要回答了。联系我们代码卸载失败!请联系我们帮助您解决!(错误代码13)--是什么情况discuz论坛discuz论坛怎么做长沙电话号码升位湖南长沙电话号码是几位数
网站域名 亚洲大于500m idc评测网 42u机柜尺寸 iis安装教程 服务器日志分析 私有云存储 服务器架设 双拼域名 北京双线机房 股票老左 东莞数据中心 vip购优惠 百度云1t 昆明蜗牛家 东莞服务器 万网主机管理 外贸空间 备案空间 帽子云排名 更多