Winsocket入门教程一多线程阻塞式服务器和阻塞式客户端程序TCP.docx
- 文档编号:28711129
- 上传时间:2023-07-19
- 格式:DOCX
- 页数:22
- 大小:251.61KB
Winsocket入门教程一多线程阻塞式服务器和阻塞式客户端程序TCP.docx
《Winsocket入门教程一多线程阻塞式服务器和阻塞式客户端程序TCP.docx》由会员分享,可在线阅读,更多相关《Winsocket入门教程一多线程阻塞式服务器和阻塞式客户端程序TCP.docx(22页珍藏版)》请在冰豆网上搜索。
Winsocket入门教程一多线程阻塞式服务器和阻塞式客户端程序TCP
Winsocket入门教程一:
多线程阻塞式服务器和阻塞式客户端程序(TCP)收藏
最近因为工作需要学习了Winsocket客户端服务器模型程序的设计。
在学习的过程中,我发现学习Winsocket的资料不多并且十分的零散。
我一直没有找到一本学习Winsocket方面的经典国外著作。
而且这些资料中并没有提供源代码文件,所以我只有将这些源代码在自己敲一遍。
在敲代码的过程中,我发现了这些源代码中的一些错误的地方和一些已经过时的Windows程序的输写方法(Win16?
)。
现将学习经验和通过阅读各种资料总结出来的模型以及代码分享出来。
希望对学习Winsocket的初学者有一定的帮助。
我们首先来了解一下什么是Winsocket。
Winsocket是unix/linux下的berkeleysocket在Windows下的实现。
unix/linux下的berkeleysocket是网络通讯方面的基石,应用程序通过调用berkeleysocket的API进行相互通讯,berkeleysocket则利用具体的网络通讯协议和操作系统的调用来为我们完成具体的通讯工作。
Winsocket保留了berkeleysocket的所有内容,并且为了其能在Win32消息机制和多线程的环境下更好的工作。
Winsocket在berkeleysocket原有的基础上对其进行了扩充。
如我们可以利用WSAAsyncSelect对Socket消息进行订阅,以及使用WSAGetLastError对多线程环境下的Winsocket错误进行捕获。
接着再让我们来了解一下服务器\客户端应用程序模型。
该模型是构建分布式系统的模型之一。
服务器程序一直处于监听的状态,等待客户端程序的连接。
客户端程序像服务器程序发送连接请求,服务器程序接受该连接请求,同时与客户端程序建立连接。
此时客户端程序就可以向服务器发送具体的请求,获取相关的数据。
服务器\客户端模型有三种连接方式,一种是面向连接的(TCP),面向连接的服务是一种可靠的服务,它通过数据流进行数据的传输,面向连接的服务实现了无差错无重复的顺寻数据发送。
一种是面向无连接的(UDP),面向无连接的服务是一种不可靠的服务,它通过数据报进行数据传输,由于数据报进行传输时的顺序是无序的,所以它是不可靠的服务。
最后一种是多播的方式,及服务器程序主动向多个客户端程序发送信息。
面向连接的服务器\客户端应用程序模型的程序流程图如下所示:
在此模型的阻塞模式中,服务端程序在执行accept操作、客户端程序connect操作、以及服务端\客户端在进行read和write操作时,如果这些操作既没有成功也没有失败,应用程序会在执行这些操作的地方一直阻塞着。
所以我们应该在服务端应用程序的主线程中不停的调用accept操作,以使服务端程序能不停地接受客户端程序发送过来的连接请求。
而在接受了一个客户端的连接请求后,我们应改为每一个接受的连接请求开辟一个专门的线程来接受客户端程序发送的请求以及为具体的请求返回特定的信息。
根据以上的程序流程图以及说明,我们可以写出以下的服务端程序源代码:
viewplaincopytoclipboardprint?
1.////////////////////////////////////////////////////////////////////////////////////////////////////
2./// \file ServerMultThread\ServerMultThread.cpp
3.///
4./// \brief 阻塞式多线程服务器程序。
每当客户端程序请求与服务端连接时,服务端程序开放一个线程接受客户端程序的请求
5./// 并且向客户端回馈请求的信息。
客户端请求的信息输出到控制台中.
6.////////////////////////////////////////////////////////////////////////////////////////////////////
7.#include
8.#include
9.#include
10.#include
11.#pragma comment(lib, "ws2_32.lib" )
12.#define ASSERT assert
13.#define THREAD HANDLE
14.#define EVENT HANDLE
15.#define CloseThread CloseHandle
16.#define CloseEvent CloseHandle
17.using std:
:
cin;
18.using std:
:
cout;
19.using std:
:
endl;
20.////////////////////////////////////////////////////////////////////////////////////////////////////
21./// \struct tagServerRecv
22.///
23./// \brief 线程函数参数结构体,其中包含已建立连接的socket.
24.///
25./// \author Shining100
26./// \date 2010-05-18
27.////////////////////////////////////////////////////////////////////////////////////////////////////
28.typedef struct tagServerRecv
29.{
30. SOCKET skAccept; // 已建立连接的socket
31. CRITICAL_SECTION *pcs; // 同步控制台输出的临界区
32. EVENT e; // 保证结构体各个字段在结构体字段改变之前将其拷贝到线程中的信号量
33. THREAD t; // 当前线程的内核对象
34. DWORD dwThreadID; // 当前线程的ID
35.}SERVER_RECV, *PSERVER_RECV;
36.////////////////////////////////////////////////////////////////////////////////////////////////////
37./// \fn static int ServerRecv(LPVOID lParam)
38.///
39./// \brief 服务器与建立连接的客户端进行通讯.
40.///
41./// \author Shining100
42./// \date 2010-05-18
43.///
44./// \param lParam 线程函数参数, 详细信息见上面说明.
45.///
46./// \return 总是返回0.
47.////////////////////////////////////////////////////////////////////////////////////////////////////
48.static int ServerRecv(LPVOID lParam);
49.static const int c_iPort = 10001;
50.int main()
51.{
52. int iRet = SOCKET_ERROR;
53. // 初始化Winsocket,所有Winsocket程序必须先使用WSAStartup进行初始化
54. WSADATA data;
55. ZeroMemory(&data, sizeof(WSADATA));
56. iRet = WSAStartup(MAKEWORD(2, 0), &data);
57. ASSERT(SOCKET_ERROR !
= iRet);
58. // 建立服务端程序的监听套接字
59. SOCKET skListen = INVALID_SOCKET;
60. skListen = socket(AF_INET, SOCK_STREAM, 0);
61. ASSERT(INVALID_SOCKET !
= skListen);
62. // 初始化监听套接字地址信息
63. sockaddr_in adrServ; // 表示网络地址
64. ZeroMemory(&adrServ, sizeof(sockaddr_in));
65. adrServ.sin_family = AF_INET; // 初始化地址格式,只能为AF_INET
66. adrServ.sin_port = htons(c_iPort); // 初始化端口,由于网络字节顺序和主机字节顺序相反,所以必须使用htons将主机字节顺序转换成网络字节顺序
67. adrServ.sin_addr.s_addr = INADDR_ANY; // 初始化IP,由于是服务器程序,所以可以将INADDR_ANY赋给该字段,表示任意的IP
68. // 绑定监听套接字到本地
69. iRet = bind(skListen, (sockaddr*)&adrServ, sizeof(sockaddr_in));
70. ASSERT(SOCKET_ERROR !
= iRet);
71. // 使用监听套接字进行监听
72. iRet = listen(skListen, SOMAXCONN); // SOMAXCONN表示可以连接到该程序的最大连接数
73. ASSERT(SOCKET_ERROR !
= iRet);
74. // 输出控制台缓冲区,由于可能有多个客户端程序可能同时向缓冲区发送请求信息
75. // 为了保证输出时能够一次性完整的输出完一个客户端的请求信息,所以在输出客
76. // 户程序的信息到控制台时,必须使用临界区阻塞其它线程
77. CRITICAL_SECTION cs;
78. InitializeCriticalSection(&cs);
79. // 保证结构体各个字段在结构体字段改变之前将其拷贝到线程中的信号量
80. // 因为当该结构体拷贝到线程中之前, 有可能有新的连接到来并改变了结构体的值
81. // 所以我们必须先保证值拷贝过后再接受连接
82. EVENT e = NULL;
83. e = CreateEvent(NULL, FALSE, FALSE, NULL);
84. ASSERT(NULL !
= e);
85. for(;;)
86. {
87. // 客户端向服务器端发送连接请求,服务器端接受客户端的连接
88. SOCKET skAccept = INVALID_SOCKET;
89. sockaddr_in adrClit;
90. ZeroMemory(&adrClit, sizeof(sockaddr_in));
91. int iLen = sizeof(sockaddr_in);
92. skAccept = accept(skListen, (sockaddr*)&adrClit, &iLen); // 如果没有客户端程序请求连接,服务端程序会一直阻塞在这里等待连接
93. ASSERT(INVALID_SOCKET !
= skAccept);
94. SERVER_RECV sr;
95. // 成功创建连接后创建一个独立的线程应答客户请求,以防止应用程序因为阻塞无法应答新的客户请求
96. // 我们应该先将线程挂起,以便我们能够在线程执行之前初始化线程所需要的结构体变量中的各个字段
97. THREAD hThread = NULL;
98. DWORD dwThreadID = 0;
99. hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ServerRecv,
100. &sr, CREATE_SUSPENDED, &dwThreadID);
101. ASSERT(NULL !
= hThread);
102. // 初始化结构体字段
103. sr.skAccept = skAccept;
104. sr.pcs = &cs;
105. sr.e = e;
106. sr.t = hThread;
107. sr.dwThreadID = dwThreadID;
108. // 启动线程
109. DWORD dwRet = ResumeThread(hThread);
110. ASSERT(-1 !
= dwRet);
111.
112. // 保证结构体被拷贝到线程中后再应答新的连接
113. dwRet = WaitForSingleObject (e, INFINITE);
114. ASSERT(WAIT_FAILED !
= dwRet);
115. }
116. // 清理线程同步资源
117. DeleteCriticalSection(&cs);
118. BOOL bRet = FALSE;
119. bRet = CloseEvent(e);
120. ASSERT(bRet);
121. // 关闭该套接字的连接
122. iRet = shutdown(skListen, SD_SEND);
123. ASSERT(SOCKET_ERROR !
= iRet);
124. // 清理该套接字的资源
125. iRet = closesocket(skListen);
126. ASSERT(SOCKET_ERROR !
= iRet);
127. // 清理Winsocket资源
128. iRet = WSACleanup();
129. ASSERT(SOCKET_ERROR !
= iRet);
130. cin.get();
131. return 0;
132.}
133.int ServerRecv(LPVOID lParam)
134.{
135. // 拷贝结构体各个字段到线程中
136. PSERVER_RECV psr = (PSERVER_RECV)lParam;
137. SERVER_RECV sr = {
138. psr->skAccept,
139. psr->pcs,
140. psr->e,
141. psr->t,
142. psr->dwThreadID
143. };
144. // 设置信号量, 使主线程能够接受新的连接
145. BOOL bRet = FALSE;
146. bRet = SetEvent(sr.e);
147. ASSERT(bRet);
148. const int c_iBufLen = 512;
149. char szBuf[c_iBufLen + 1] = {'\0'};
150. const char c_szPrefix[] = "Server recv:
";
151. const int c_iPrefLen = strlen(c_szPrefix);
152. char szRely[c_iBufLen + 16 + 1] = {'\0'};
153. strcpy(szRely, c_szPrefix);
154. int iRet = SOCKET_ERROR;
155. for(;;)
156. {
157. iRet = recv(sr.skAccept, szBuf, c_iBufLen, 0); // 接收客户端发送的信息, 如果客户端不发送信息,则线程会阻塞到此处
158. if(0 == iRet) // 客户端优雅的关闭了此连接
159. {
160. cout << "Connection " << sr.dwThreadID << " shutdown." << endl;
161. break;
162. }
163. else if(SOCKET_ERROR == iRet) // 客户端粗鲁的关闭了此连接或者接受信息出错
164. {
165. cout << "Connection " << sr.dwThreadID << " recv error." << endl;
166. break;
167. }
168. szBuf[iRet] = '\0';
169. EnterCriticalSection(sr.pcs);
170. cout << "Connection " << sr.dwThreadID << " says:
" << szBuf << endl; // 输出接收到的信息
171. LeaveCriticalSection(sr.pcs);
172. // 向客户端发送信息
173. strcpy(szRely + c_iPrefLen, szBuf);
174. iRet = send(sr.skAccept, szRely, strlen(szRely), 0); // 客户端如果没有足够的缓冲区接受信息,则线程会阻塞到此处
175. if(SOCKET_ERROR == iRet)
176. {
177. cout << "Connection " << sr.dwThreadID << " send error." << endl;
178. break;
179. }
180. }
181. // 关闭该套接口
182. iRet = shutdown(sr.skAccept, SD_SEND);
183. while(recv(sr.skAccept, szBuf, c_iBufLen, 0) > 0);
184. ASSERT(SOCKET_ERROR !
= iRet);
185. // 清理该套接口的资源
186. iRet = closesocket(sr.skAccept);
187. ASSERT(SOCKET_ERROR !
= iRet);
188. // 关闭该线程对象
189. bRet = CloseThread(sr.t);
190. ASSERT(bRet);
191. cout << "Connection " << sr.dwThreadID << " exit." << endl;
192. return 0;
193.}
在用Winsocket编写程序时,我们首先必须要进行如下的操作,以为该进程初始化Winsocket和Ws2_32.dll,而使后面的函数调用有效。
viewplaincopytoclipboardprint?
1.WSADATA data;
2.ZeroMemory(&data, sizeof(WSADATA));
3.iRet = WSAStartup(MAKEWORD(2, 0), &data);
4.ASSERT(SOCKET_ERROR !
= iRet);
WSAStartup第一个参数为要使用的Winsocket的版本,MAKEWORD(2,0)表示我们使用Winsocket2.0。
第二个参数在WSAStartup初始化后,可以获得一些Winsocket相关信息,如该版本Winsocket所支持的最大socket数量以及UDP包的最大大小。
在初始化了Winsocket后,我们就可以创建一个socket监听客户端的连接请求了。
viewplaincopytoclipboardprint?
1.SOCKET skListen = IN
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Winsocket 入门教程 多线程 阻塞 服务器 客户端 程序 TCP