Winsocket入门教程二非阻塞式服务器和客户端程序TCP.docx
- 文档编号:10790700
- 上传时间:2023-02-22
- 格式:DOCX
- 页数:19
- 大小:55.90KB
Winsocket入门教程二非阻塞式服务器和客户端程序TCP.docx
《Winsocket入门教程二非阻塞式服务器和客户端程序TCP.docx》由会员分享,可在线阅读,更多相关《Winsocket入门教程二非阻塞式服务器和客户端程序TCP.docx(19页珍藏版)》请在冰豆网上搜索。
Winsocket入门教程二非阻塞式服务器和客户端程序TCP
Winsocket入门教程二:
非阻塞式服务器和客户端程序(TCP)收藏
上次为大家介绍了阻塞式多线程服务端程序和阻塞式客户端程序的设计方法,但是在上文的最后也提到过,服务器程序会因为建立连接和关闭连接而频繁的创建和关闭线程会产生大量的内存碎片,从而导致服务端程序不能保证长时间的稳定运行。
因此我在这里为大家介绍另外一种建立服务器和客户端程序的方法,即建立非阻塞式的服务器和客户端程序。
那什么是非阻塞呢?
非阻塞是相对于阻塞而言,阻塞指的是在进行一个操作的时候,如服务器接收客户端的连接(accept),服务器或者客户端读写数据(read、write),如果该操作没有执行完成(成功或者失败都算是执行完成),则程序会一直阻塞在操作执行的地方,直到该操作返回一个明确的结果。
而非阻塞式程序则不一样,非阻塞式程序会在产生阻塞操作的地方阻塞一定的时间(该时间可以由程序员自己设置)。
如果操作没有完成,在到达所设置的时间之后,无论该操作成功与否,都结束该操作而执行程序下面的操作。
为了执行非阻塞操作,我们在创建了一个套接口后,需要将套接口设置为非阻塞的套接口。
为了将套接口设置成为非阻塞套接口,我们需要调用ioctlsocket函数将套接口设置为非阻塞的套接口。
ioctlsocket函数的定义如下:
intioctlsocket(SOCKETs,longcmd,u_longFAR*argp)
该函数的作用是控制套接口的I/O模式。
参数s表示要设置的套接口;参数cmd表示要对该套接口设置的命令,为了要将套接口设置成为非阻塞的,我们应该填写FIONBIO;argp表示填写命令的值,如我们要将套接口设置成非阻塞的,我们需要将值设置成为1,如果我们要将套接口设置成为非阻塞状态的话,我们将值设置成为0就是了。
为了进行非阻塞的操作,我们需要在进行操作之前调用select函数,select函数的定义如下:
intselect(intnfds,fd_setFAR*readfds,fd_setFAR*writefds,
fd_setFAR*exceptfds,
conststructtimevalFAR*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操作后,连接失败;二、有带外数据到来。
在我们了解了创建服务器和客户端程序的基础知识后,我们再来看看示例程序,以加深我们对知识的理解。
程序的运行结果如下所示:
下面是服务器程序的代码:
viewplaincopytoclipboardprint?
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
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 is " << 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 socket " << *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.}
服务器程序的重点是我们需要将接受自客户端程序的套接字加入到一个链表中,以方便我们的管理。
viewplaincopytoclipboardprint?
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. }
然后在套接字被客户端关闭或者异常中断后,我们需要将链表中的套接字设置为无效套接字,并在操作完所有的套接字一次后,将无效的套接字从集合中移除。
viewplaincopytoclipboardprint?
1.if (0 >= iRead)// 读取出现错误或者对方关闭连接
2. {
3. iRead == 0 ?
cout << "Connection shutdown at socket " << *iter << endl :
4. cout << "Connection recv error at socket " << *iter << endl;
5. iRet = GraceClose(&(*iter));// 如果出错则关闭套接字
6. ASSERT(iRet);
7. }
viewplaincopytoclipboardprint?
1.sl.remove(INVALID_SOCKET); // 删除无效的
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Winsocket 入门教程 阻塞 服务器 客户端 程序 TCP
![提示](https://static.bdocx.com/images/bang_tan.gif)