版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、Winsocket 入门教程一:多线程阻塞式服务器和阻塞式客户端程序 (TCP)Winsocket入门教程一:多线程阻塞式服务器和阻塞式客户端程序(TCP)收藏最近因为工作需要学习了 Winsocket 客户端服务器模型程序的设计。在学习的过程中,我发现学习 Winsocket 的资料不多并且十分的零散。我一直没有找到一本学习 Winsocket 方面的经典国外著作。而且这些资料中并没有提供源代码文件, 所以我只有将这些源代码在自己敲一遍。在敲代码的过程中, 我发现了这些源代码中的一些错误的地方和一些已经过时的 Windows 程序的输写方法 (Win16?) 。现将学习经验和通过阅读各种资料
2、总结出来的模型以及代码分享出来。希望对学习 Winsocket 的初学者有一定的帮助。我们首先来了解一下什么是t 。Winsocket是 unix/linuxWinsocke下的 berkeleyinuxsocket 下的在 Windows下的实现。 unix/lberkeleysocket是网络通讯方面的基石,应用程序通过调用berkeleysocket的 API进行相互通讯, berkeleysocket 则利用具体的网络通讯协议和操作系统的调用来为我们完成具体的通讯工作。 Winsocket保留了 berkeleysocket的所有内容,并且为了其能在 Win32消息机制和多线程的环境下
3、更好的工作。 Winsocket在 berkeleysocket 原有的基础上对其进行了扩充。如我们可以利用 WSAAsyncSelect对 Socket消息进行订阅,以及使用WSAGetLastError对多线程环境下的 Winsocket错误进行捕获。接着再让我们来了解一下服务器 客户端应用程序模型。该模型是构建分布式系统的模型之一。服务器程序一直处于监听的状态, 等待客户端程序的连接。 客户端程序像服务器程序发送连接请求,服务器程序接受该连接请求, 同时与客户端程序建立连接。 此时客户端程序就可以向服务器发送具体的请求, 获取相关的数据。 服务器 客户端模型有三种连接方式,一种是面向连接
4、的 (TCP) ,面向连接的服务是一种可靠的服务,它通过数据流进行数据的传输, 面向连接的服务实现了无差错无重复的顺寻数据发送。 一种是面向无连接的 (UDP) ,面向无连接的服务是一种不可靠的服务,它通过数据报进行数据传输,由于数据报进行传输时的顺序是无序的, 所以它是不可靠的服务。最后一种是多播的方式,及服务器程序主动向多个客户端程序发送信息。面向连接的服务器 客户端应用程序模型的程序 流 程 图 如 下 所 示 :在此模型的阻塞模式中, 服务端程序在执行 a ccept 操作、客户端程序 connect 操作、以及服务端 客户端在进行 read 和 write 操作时,如果这些操作既没有
5、成功也没有失败, 应用程序会在执行这些操作的地方一直阻塞着。 所以我们应该在服务端应用程序的主线程中不停的调用 accept 操作,以使服务端程序能不停地接受客户端程序发送过来的连接请求。 而在接受了一个客户端的连接请求后, 我们应改为每一个接受的连接请求开辟一个专门的线程来接受客户端程序发送的请求以及为具体的请求返回特定的信息。根据以上的程序流程图以及说明,我们可以写出以下的服务端程序源代码:viewplaincopytoclipboardprint?1. /2. / file ServerMultThreadServerMultThread.cpp3. /4. / brief 阻塞式多线程
6、服务器程序。 每当客户端程序请求与服务端连接时,服务端程序开放一个线程接受客户端程序的请求5. /并且向客户端回馈请求的信息。客户端请求的信息输出到控制台中.6. /7. #include 8. #include 9. #include 10. #include 11. #pragma comment(lib, ws2_ 32.lib )12. #define ASSERT assert13. #define THREAD HANDLE14. #define EVENT HANDLE15. #define CloseThread CloseHandle16. #define CloseEven
7、t CloseHandle17. using std:cin;18. using std:cout;19. using std:endl;20. /21. / struct tagServerRecv22. /23. / brief 线程函数参数结构体,其中包含已建立连接的 socket.24. /25. / author Shining10026. / date 2010-05-1827. /28. typedef struct tagServerRecv29. 30.SOCKET skAccept;/已建立连接的 socket31.CRITICAL_SECTION *pcs; / 同步控制
8、台输出的临界区32.EVENTe;/保证结构体各个字段在结构体字段改变之前将其拷贝到线程中的信号量33.THREAD t;/当前线程的内核对象34.DWORDdwThreadID;/ 当前线程的 ID35. SERVER_RECV, *PSERVER_RECV;36. /37. / fn static int ServerRecv(L PVOID lParam)38. /39. / brief 服务器与建立连接的客户端进行通讯 .40. /41. / author Shining10042. / date 2010-05-1843. /44./ param lParam线程函数参数 , 详细信
9、息见上面说明 .45. /46./ return总是返回 0.47. /48. static int ServerRecv( LPVOID l Param);49.static constint c_iPort = 10001;50.int main()51.52.int iRet = SOCKET_ERROR;53./初始化 Winsocket,所有socket程序必须先使用WSAStartupWin进行初始化54.WSADATA data;55. ZeroMemory(&data, sizeof (WSADATA);56. iRet = WSAStartup(MAKEW ORD(2, 0)
10、, &data);57. ASSERT(SOCKET_ERROR != i Ret);58./建立服务端程序的监听套接字59. SOCKET skListen = INVALID _SOCKET;60. skListen = socket(AF_INE T, SOCK_STREAM, 0);61.ASSERT(INVALID_SOCKET != skListen);62./初始化监听套接字地址信息63.sockaddr_in adrServ;/表示网络地址64. ZeroMemory(&adrServ, size of(sockaddr_in);65.adrServ.sin_family= A
11、F_INET;/初始化地址格式,只能为AF_INET66.adrServ.sin_port= hton初始化端口,由于网络字节顺序和主机字节顺序相反, 所以必须使用 h tons 将主机字节顺序转换成网络字节顺序67.adrServ.sin_addr.s_addr = INADDR_ANY;/初始化 IP ,由于是服务器程序,所以可以将 INADDR_AN Y 赋给该字段,表示任意的 IP68./绑定监听套接字到本地69. iRet = bind(skListen, (socka ddr*)&adrServ, sizeof(sockaddr_i n);70.ASSERT(SOCKET_ERRO
12、R != iRet);71./使用监听套接字进行监听72.iRet = listen(skListen, SOMAXCONN); / SOMAXCONN表示可以连接到该程序的最大连接数73.ASSERT(SOCKET_ERROR != iRet);74./输出控制台缓冲区,由于可能有多个客户端程序可能同时向缓冲区发送请求信息75./为了保证输出时能够一次性完整的输出完一个客户端的请求信息,所以在输出客76./户程序的信息到控制台时,必须使用临界区阻塞其它线程77.CRITICAL_SECTION cs;78.InitializeCriticalSection(&cs);79./保证结构体各个字
13、段在结构体字段改变之前将其拷贝到线程中的信号量80./因为当该结构体拷贝到线程中之前 , 有可能有新的连接到来并改变了结构体的值81./所以我们必须先保证值拷贝过后再接受连接82.EVENT e = NULL;83.e = CreateEvent(NULL, FALSE, FALSE, NULL);84.ASSERT(NULL != e);85.for(;)86.87./客户端向服务器端发送连接请求,服务器端接受客户端的连接88. SOCKET skAccept = INVA LID_SOCKET;89.sockaddr_in adrClit;90. ZeroMemory(&adrClit,
14、siz eof(sockaddr_in);91. int iLen = sizeof(sockaddr _in);92. skAccept = accept(skListe n, (sockaddr*)&adrClit, &iLen); / 如果没有客户端程序请求连接,服务端程序会一直阻塞在这里等待连接93.ASSERT(INVALID_SOCKET != skAccept);94.SERVER_RECV sr;95./成功创建连接后创建一个独立的线程应答客户请求,以防止应用程序因为阻塞无法应答新的客户请求96./我们应该先将线程挂起,以便我们能够在线程执行之前初始化线程所需要的结构体变量中的
15、各个字段97.THREAD hThread= NULL;98.DWORDdwThreadID = 0;99.hThread = CreateThread(NULL, 0, (LPTHREAD_START_RO UTINE)ServerRecv,100. &sr, CREATE_SUSPEND ED, &dwThreadID);101. ASSERT(NULL != hThrea d);102./初始化结构体字段103.sr.skAccept= skAccept;104.sr.pcs= &cs;105.sr.e= e;106.sr.t= hThread;107.sr.dwThreadID = d
16、wThreadID;108./启动线程109.DWORD dwRet = ResumeThread(hThread);110.ASSERT(-1 != dwRet);111.112./保证结构体被拷贝到线程中后再应答新的连接113. dwRet = WaitForSingleOb ject (e, INFINITE);114.ASSERT(WAIT_FAILED != dwRet);115. 116. / 清理线程同步资源117. DeleteCriticalSection(&cs);118.BOOLbRet = FALSE;119. bRet = CloseEvent(e);120. ASS
17、ERT(bRet);121. / 关闭该套接字的连接122. iRet = shutdown(skListen, S D_SEND);123. ASSERT(SOCKET_ERROR != i124./清理该套接字的资源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 S
18、erverRecv(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-dwThreadID143. ;144. / 设置信号量, 使主线程能够接受新的连接145.BOOLbRet = FALSE;146. bRet = SetEvent(sr.e);147. ASSERT(bRet);148.constintc_iBufL
19、en = 512;149.charszBufc_iBufLen + 1 = 0;150.constchar c_szPrefix = Server recv:;151.constintc_iPrefLen = strlen(c_szPrefix);152.charszRelyc_iBufLen + 16 + 1 = 0;153. strcpy(szRely, c_szPrefix);154.intiRet = SOCKET_ERROR;155. for(;)156. 157.iRet = recv(sr.skAccept, szBuf, c_iBufLen, 0); /接收客户端发送的信息,
20、如果客户端不发送信息,则线程会阻塞到此处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.szBufiRet = 0;169.EnterCriticalSection(sr.pcs);170.cout
21、 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 = iRe t)176.177. cout Connectio n sr.dwThreadID sen d er
22、ror. 0);184. ASSERT(SOCKET_ERROR != i185./清理该套接口的资源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. / fileServerMultThreadServerMultThread.c/ brief阻塞式多线
23、程服务器程序。每当客户端程序/并且向客户端回馈请求的信息。客/#include #include #include #include #pragma comment(lib, ws2_32.lib )#define ASSERT assert#define THREAD HANDLE#define EVENT HANDLE#define CloseThread CloseHandle#define CloseEvent CloseHandleusing std:cin;using std:cout;在用 Winsocket编写程序时,我们首先必须要进行如下的操作,以为该进程初始化Winsock
24、et和 Ws2_32.dll,而使后面的函数调用有效。viewplaincopytoclipboardprint?1. WSADATA data;2. ZeroMemory(&data, sizeof(WSAD ATA);3. iRet = WSAStartup(MAKEWORD (2, 0), &data);4. ASSERT(SOCKET_ERROR != iRet);WSADATA data;ZeroMemory(&data, sizeof(WSADATA);iRet = WSAStartup(MAKEWORD(2, 0), &data);ASSERT(SOCKET_ERROR != i
25、Ret);WSAStartup第一个参数为要使用的Winsocket的版本, MAKEWORD(2,0)表示我们使用 Winsocket2.0。第二个参数在WSAStartup初始化后,可以获得一些Winsocket相关信息,如该版本Winsocket所支持的最大 socket 数量以及 UDP 包的最大大小。在初始化了 Winsocket 后,我们就可以创建一个 socket 监听客户端的连接请求了。viewplaincopytoclipboardprint?1. SOCKET skListen = INVALID_SOC KET;2. skListen = socket(AF_INET,
26、SOC K_STREAM, 0);3. ASSERT(INVALID_SOCKET != skListen);SOCKET skListen = INVALID_SOCKET;skListen = socket(AF_INET, SOCK_STREAM, 0);ASSERT(INVALID_SOCKET != skListen);s ocket函数分配相应的资源并将该socket绑定到一个特定的传输服务提供者。socket的第一个参数为网络地址族, 该参数只能为 AF_INET ,第二个参数可以为SOCK_STREAM或者 SOCK_DGRAM 。SOCK_STREAM 为一个流式套接口, 它
27、提供双向可靠、面向连接的 TCP 服务。SOCK_DGRAM 为一个数据报套接口,它提供不可靠、面向无连接的 UDP 服务。第三个参数一般选择为0 ,表示由 Winsocket 选择具体的协议使用。在建立了一个监听 socket 后,我们就可以将该套接口与本地地址进行绑定, 已将其设置成为网络中一个独一无二的地址。viewplaincopytoclipboardprint?1. iRet = bind(skListen, (sockaddr*) &adrServ, sizeof(sockaddr_in);2. ASSERT(SOCKET_ERROR != iRet);iRet = bind(s
28、kListen, (sockaddr*)&adrServ, sizeof(sASSERT(SOCKET_ERROR != iRet);在绑定了本地地址后, 我们就可以将该 s ocket 设置为监听状态,以使该 socket 可以检测到来自客户端程序的连接请求。viewplaincopytoclipboardprint?1. iRet = listen(skListen, SOMAXCONN); / SOMAXCONN表示可以连接到该程序的最大连接数2. ASSERT(SOCKET_ERROR != iRet);iRet = listen(skListen, SOMAXCONN); / SOM
29、AXCONN表示ASSERT(SOCKET_ERROR != iRet);接下来我们我们就可以利用套接口接受来自客户端程序的连接了。 我们以该套接口为参数调用 accept 函数,accept 函数调用成功后, 将建立一个可以接受和发送数据的套接口 skAcc ept 。viewplaincopytoclipboardprint?1. / 客户端向服务器端发送连接请求,服务器端接受客户端的连接2. SOCKET skAccept = INVALID_SO CKET;3. sockaddr_in adrClit;4. ZeroMemory(&adrClit, sizeof(soc kaddr_i
30、n);5. int iLen = sizeof(sockaddr_in);6. skAccept = accept(skListen, (sockaddr*)&adrClit, &iLen); /如果没有客户端程序请求连接,服务端程序会一直阻塞在这里等待连接7. ASSERT(INVALID_SOCKET != skAccept);/ 客户端向服务器端发送连接请求,服务器端接受客户端的连SOCKET skAccept = INVALID_SOCKET; sockaddr_in adrClit; ZeroMemory(&adrClit, sizeof(sockaddr_in); int iLen
31、 = sizeof(sockaddr_in);skAccept = accept(skListen, (sockaddr*)&adrClit, &i ASSERT(INVALID_SOCKET != skAccept);在成功的建立了新套接口后, 我们就可以利用该套接口在我们的线程函数中接收和发送数据了。viewplaincopytoclipboardprint?1. iRet = recv(sr.skAccept, szBuf, c_iBufLen, 0); /接收客户端发送的信息, 如果客户端不发送信息,则线程会阻塞到此处2.if(0 = iRet) /客户端优雅的关闭了此连接3.4.co
32、ut Connection sr.dwThreadID shutdown. endl;5.break;6.7.else if(SOCKET_ERROR = iRet) /客户端粗鲁的关闭了此连接或者接受信息出错8.9.cout Connection sr.dwThreadID recv error. endl;10.break;.12.szBufiRet = 0;iRet = recv(sr.skAccept, szBuf, c_iBufLen, 0); /接if(0 = iRet) /客户端优雅的关闭了此cout Connection srbreak;else if(SOCK
33、ET_ERROR = iRet) /客cout Connection srbreak;szBufiRet = 0;viewplaincopytoclipboardprint?1. strcpy(szRely + c_iPrefLen, szBu f);2.iRet = send(sr.skAccept, szRely, strlen(szRely), 0); /客户端如果没有足够的缓冲区接受信息,则线程会阻塞到此处if(SOCKET_ERROR = iRet)cout Connection sr.dwThreadID send error. endl;6.break;7.strcpy(szRe
34、ly + c_iPrefLen, szBuf);iRet = send(sr.skAccept, szRely, stif(SOCKET_ERROR = iRet)cout Connection 0);4. ASSERT(SOCKET_ERROR != iRet);5./清理该套接口的资源6. iRet = closesocket(sr.skAccept);7. ASSERT(SOCKET_ERROR != iRet);/ 关闭该套接口iRet = shutdown(sr.skAccept, SD_SEND);while(recv(sr.skAccept, szBuf, c_iBufLen,
35、0ASSERT(SOCKET_ERROR != iRet);/ 清理该套接口的资源iRet = closesocket(sr.skAccept);ASSERT(SOCKET_ERROR != iRet);监听 socket 的关闭也与上面套接口关闭的方法一致。 在关闭了监听套接口后, 我们的服务器程序应该调用 WSACleanup 函数,已完成对 Winsocket 和 ws2_32.dll 的清理。上述就是该类型服务器程序应用程序执行的全过程了。客户端程序的代码跟服务器程序的代码相似,程序代码如下所示:viewplaincopytoclipboardprint?1. /2. / file C
36、lientBlockClientBloc k.cpp3. /4. / brief 连接服务器并向服务器发送信息,然后接受服务器发送的信息 .5. /6. #include 7. #include 8. #include 9. #pragma comment(lib, ws2_32. lib )10. #define ASSERT assert11. using std:cin;12. using std:cout;13. using std:endl;14.static constcharc_szIP = ;15.static constintc_iPort = 10001;
37、16. int main()17. 18. int iRet = SOCKET_ERRO R;19./初始化 Winsocket,所有socket程序必须先使用WSAStartupWin进行初始化20.WSADATA data;21.ZeroMemory(&data, sizeof(WSADATA);22.iRet = WSAStartup(MAKEWORD(2, 0), &data);23.ASSERT(SOCKET_ERROR != iRet);24./建立连接套接字25.SOCKET skClient = INVALID_SOCKET;26.skClient = socket(AF_IN
38、ET, SOCK_STREAM, 0);27.ASSERT(INVALID_SOCKET != skClient);28./ 初始化连接套接字地址信息29.sockaddr_in adrServ;/表示网络地址30.ZeroMemory(&adrServ, sizeof(sockaddr_in);31.adrServ.sin_family= AF_INET;/初始化地址格式, 只能为AF_INET32.adrServ.sin_port= htons(c_iPort);/初始化端口,由于网络字节顺序和主机字节顺序相反,所以必须使用 htons 将主机字节顺序转换成网络字节顺序33.adrServ
39、.sin_addr.s_addr = inet_addr(c_szIP);/初始化 IP ,由于网络字节顺序和主机字节顺序相反,所以必须使用 inet_addr 将主机字节顺序转换成网络字节顺序34./使用连接套接字进行连接35. iRet = connect(skClient, (so ckaddr*)&adrServ, sizeof(sockad dr_in);36. ASSERT(SOCKET_ERROR != i Ret);37.constint c_iBufLen = 512;38.charszBufc_iBufLen + 16 + 1 = 0;39.for(;)40.41.cout
40、 szBuf;43.if(0 = strcmp(exit, szBuf)44.45.break;46.47./向服务器端发送信息48.iRet = send(skClient, szBuf, strlen(szBuf), 0); /服务器端如果没有足够的缓冲区接受信息,则线程会阻塞到此处49.if(SOCKET_ERROR = iRet)50.51.cout send error. endl;52.break;53.54./接收服务器端发送的信息55.iRet = recv(skClient, szBuf, c_iBufLen, 0); /如果服务器端没有发送数据,则会阻塞到此处56.if(0 = iRet)57.58.cout connection shu
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 别墅施工组织设计方案
- 木材腐朽防治专项施工方案
- 2026宣传活动面试题目及答案
- 2026药研运营面试题及答案
- 2026邮件客服面试题及答案
- 2026能源设备制造业市场分析与发展策略研究
- 2026中国无人驾驶汽车市场现状及投资风险评估报告
- 2025年医院卫生院信息安全管理制度-1
- 2026叶黄素酯与其他抗氧化剂复配效果比较研究
- 赣州市瑞金市2027届数学三上期末考试模拟试题含解析
- 钢铁厂铁矿石验收规章
- 2025年江苏省档案职称考试(新时代档案工作理论与实践)历年参考题库含答案详解(5套)
- 2025年高级经济师知识产权考试历年真题及答案
- 矿场股权融资方案(3篇)
- 学校用品配送管理办法
- T-CIAPS0002-2017 锂离子电池企业安全生产规范
- 货车驾驶员安全驾驶培训
- 食品行业停水、停电、停汽时应急预案
- 化工厂设备技术员工作总结报告
- 小儿喘息性支气管炎课件
- 文学类文本赵树理《套不住的手》阅读练习及答案
评论
0/150
提交评论