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);
数脉科技六月优惠促销发布了!数脉科技对香港自营机房的香港服务器进行超低价促销,可选择30M、50M、100Mbps的优质bgp网络。更大带宽可在选购时选择同样享受优惠,目前仅提供HKBGP、阿里云产品,香港CN2、产品优惠码续费有效,仅限新购,每个客户可使用于一个订单。新客户可以立减400元,或者选择对应的机器用相应的优惠码,有需要的朋友可以尝试一下。点击进入:数脉科技官方网站地址数脉科技是一家成...
Vultr 商家有新增韩国首尔机房,这个是继日本、新加坡之后的第三个亚洲机房。不过可以大概率知道肯定不是直连中国机房的,因为早期的日本机房有过直连后来取消的。今天准备体验看看VULTR VPS主机商的韩国首尔机房的云服务器的速度和性能。1、全球节点PING速度测试这里先通过PING测试工具看看全球几十个节点的PING速度。看到好像移动速度还不错。2、路由去程测试测试看看VULTR韩国首尔机房的节点...
onevps最新消息,为了更好服务中国区用户:1、网站支付方式新增了支付宝,即将增加微信;原信用卡、PayPal方式不变;(2)可以切换简体中文版网站,在网站顶部右上角找到那个米字旗,下拉可以换中国简体版本。VPS可选机房有:中国(香港)、新加坡、日本(东京)、美国(纽约、洛杉矶)、英国(伦敦)、荷兰(阿姆斯特丹)、瑞士(苏黎世)、德国(法兰克福)、澳大利亚(悉尼)。不管你的客户在亚太区域、美洲区...