整理Winsocket入门教程一多线程阻塞式服务器和阻塞式客户端程序TCP_第1页
整理Winsocket入门教程一多线程阻塞式服务器和阻塞式客户端程序TCP_第2页
整理Winsocket入门教程一多线程阻塞式服务器和阻塞式客户端程序TCP_第3页
整理Winsocket入门教程一多线程阻塞式服务器和阻塞式客户端程序TCP_第4页
整理Winsocket入门教程一多线程阻塞式服务器和阻塞式客户端程序TCP_第5页
已阅读5页,还剩14页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、服务器socket!)biicK)1listen 01Faccept()客户机阻撇等待客户数据请求故振lraci ()处州W务诸求Win socket入门教程一:多线程阻塞式服务器和阻塞式客户端程序(TCP)收藏&自最近因为工作需要学习了Win socket客户端服务器模型程序的设计。在学习的过程中,我发现学习Win socket的资料不多并且十分的零散。我一直没有找到一本学习Win socket方面的经典国外著作。而且这些资料中并没有提供源代码文件,所以我只有将这 些源代码在自己敲一遍。在敲代码的过程中,我发现了这些源代码中的一些错误的地方和一 些已经过时的 Windows程序的输写方法(W

2、in 16?)。现将学习经验和通过阅读各种资料总结 出来的模型以及代码分享出来。希望对学习 Win socket的初学者有一定的帮助。我们首先来了解一下什么是Win socket。Win socket 是 unix/linux 下的 berkeley socket 在 Windows 下的实现。 unix/linux 下的 berkeley socket 是网络通讯方面的基石,应用程序通过 调用 berkeley socket 的 API 进行相互通讯, berkeley socket 则利用具体的网络通讯协议和操 作系统的调用来为我们完成具体的通讯工作。 Winsocket 保留了 berk

3、eley socket 的所有内容, 并且为了其能在 Win32 消息机制和多线程的环境下更好的工作。 Winsocket 在 berkeley socket 原有的基础上对其进行了扩充。如我们可以利用 WSAAsyncSelect 对 Socket 消息进行订阅, 以及使用 WSAGetLastError 对多线程环境下的 Winsocket 错误进行捕获。接着再让我们来了解一下服务器 客户端应用程序模型。 该模型是构建分布式系统的模 型之一。 服务器程序一直处于监听的状态, 等待客户端程序的连接。 客户端程序像服务器程 序发送连接请求, 服务器程序接受该连接请求, 同时与客户端程序建立连接

4、。 此时客户端程 序就可以向服务器发送具体的请求, 获取相关的数据。服务器 客户端模型有三种连接方式, 一种是面向连接的(TCP),面向连接的服务是一种可靠的服务,它通过数据流进行数据的传 输,面向连接的服务实现了无差错无重复的顺寻数据发送。一种是面向无连接的(UDP),面向无连接的服务是一种不可靠的服务, 它通过数据报进行数据传输,由于数据报进行传输时的顺序是无序的, 所以它是不可靠的服务。 最后一种是多播的方式,及服务器程序主动向多个客户端程序发送信息。面向连接的服务器 客户端应用程序模型的程序流程图如下所示:在此模型的阻塞模式中,服务端程序在执行accept操作、客户端程序 connec

5、t操作、以及服务端 客户端在进行 read 和 write 操作时,如果这些操作既没有成功也没有失败,应用 程序会在执行这些操作的地方一直阻塞着。 所以我们应该在服务端应用程序的主线程中不停 的调用 accept 操作,以使服务端程序能不停地接受客户端程序发送过来的连接请求。而在 接受了一个客户端的连接请求后, 我们应改为每一个接受的连接请求开辟一个专门的线程来 接受客户端程序发送的请求以及为具体的请求返回特定的信息。根据以上的程序流程图以及说明,我们可以写出以下的服务端程序源代码:view plaincopy to clipboardprint? / file ServerMultThrea

6、dServerMultThread.cpp/ brief 阻塞式多线程服务器程序。每当客户端程序请求与服务端连接时,服务端程序开放 一个线程接受客户端程序的请求/ 并且向客户端回馈请求的信息。客户端请求的信息输出到控制台中./ #include #include #include #include #pragma comment(lib, ws2_32.lib )#define ASSERT assert#define THREAD HANDLE#define EVENT HANDLE#define CloseThread CloseHandle#define CloseEvent Close

7、Handleusing std:cin;using std:cout;using std:endl;/ struct tagServerRecv/ brief 线程函数参数结构体,其中包含已建立连接的 socket./ author Shining100/ date 2010-05-18/typedef struct tagServerRecvSOCKET skAccept; / 已建立连接的 socketCRITICAL_SECTION *pcs; / 同步控制台输出的临界区EVENT e; / 保证结构体各个字段在结构体字段改变之前将其拷贝到线程中的信口号量THREAD t; / 当前线程

8、的内核对象DWORD dwThreadID; / 当前线程的 IDSERVER_RECV, *PSERVER_RECV;/ fn static int ServerRecv(LPVOID lParam)/ brief 服务器与建立连接的客户端进行通讯 ./ author Shining100/ date 2010-05-18 / param lParam 线程函数参数 , 详细信息见上面说明 ./ return 总是返回 0./static int ServerRecv(LPVOID lParam);static const int c_iPort = 10001;int main()int

9、iRet = SOCKET_ERROR;/ 初始化 Winsocket ,所有 Winsocket 程序必须先使用 WSAStartup 进行初始化WSADATA data;ZeroMemory(&data, sizeof(WSADATA);iRet = WSAStartup(MAKEWORD(2, 0), &data);ASSERT(SOCKET_ERROR != iRet);/ 建立服务端程序的监听套接字SOCKET skListen = INV ALID_SOCKET;skListen = socket(AF_INET, SOCK_STREAM, 0);ASSERT(INV ALID_S

10、OCKET != skListen);/ 初始化监听套接字地址信息sockaddr_in adrServ; / 表示网络地址ZeroMemory(&adrServ, sizeof(sockaddr_in);adrServ.sin_family = AF_INET; / 初始化地址格式,只能为 AF_INETadrServ.sin_port = htons(c_iPort); / 初始化端口, 由于网络字节顺序和主机字节顺序相 反,所以必须使用 htons 将主机字节顺序转换成网络字节顺序adrServ.sin_addr.s_addr = INADDR_ANY; / 初始化 IP ,由于是服务器

11、程序,所以可以 将 INADDR_ANY 赋给该字段,表示任意的 IP/ 绑定监听套接字到本地iRet = bind(skListen, (sockaddr*)&adrServ, sizeof(sockaddr_in);ASSERT(SOCKET_ERROR != iRet);/ 使用监听套接字进行监听iRet = listen(skListen, SOMAXCONN); / SOMAXCONN表示可以连接到该程序的最大连接数ASSERT(SOCKET_ERROR != iRet);/ 输出控制台缓冲区,由于可能有多个客户端程序可能同时向缓冲区发送请求信息/ 为了保证输出时能够一次性完整的输出

12、完一个客户端的请求信息,所以在输出客/ 户程序的信息到控制台时,必须使用临界区阻塞其它线程 CRITICAL_SECTION cs;InitializeCriticalSection(&cs);/ 保证结构体各个字段在结构体字段改变之前将其拷贝到线程中的信号量/ 因为当该结构体拷贝到线程中之前 , 有可能有新的连接到来并改变了结构体的值/ 所以我们必须先保证值拷贝过后再接受连接EVENT e = NULL;e = CreateEvent(NULL, FALSE, FALSE, NULL);ASSERT(NULL != e);for(;)/ 客户端向服务器端发送连接请求,服务器端接受客户端的连接

13、SOCKET skAccept = INV ALID_SOCKET; sockaddr_in adrClit;ZeroMemory(&adrClit, sizeof(sockaddr_in);int iLen = sizeof(sockaddr_in);skAccept = accept(skListen, (sockaddr*)&adrClit, &iLen); / 如果没有客户端程序请求连 接,服务端程序会一直阻塞在这里等待连接ASSERT(INV ALID_SOCKET != skAccept);SERVER_RECV sr;/ 成功创建连接后创建一个独立的线程应答客户请求, 以防止应用

14、程序因为阻塞无法应 答新的客户请求/ 我们应该先将线程挂起, 以便我们能够在线程执行之前初始化线程所需要的结构体变 量中的各个字段THREAD hThread = NULL;DWORD dwThreadID = 0;hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ServerRecv,&sr, CREATE_SUSPENDED, &dwThreadID);ASSERT(NULL != hThread);/ 初始化结构体字段sr.skAccept = skAccept;sr.pcs= &cs;sr.e= e;sr.t= hThrea

15、d;sr.dwThreadID = dwThreadID;/ 启动线程DWORD dwRet = ResumeThread(hThread);ASSERT(-1 != dwRet);/ 保证结构体被拷贝到线程中后再应答新的连接 dwRet = WaitForSingleObject (e, INFINITE); ASSERT(WAIT_FAILED != dwRet);/ 清理线程同步资源DeleteCriticalSection(&cs);BOOL bRet = FALSE;bRet = CloseEvent(e);ASSERT(bRet);/ 关闭该套接字的连接iRet = shutdow

16、n(skListen, SD_SEND);ASSERT(SOCKET_ERROR != iRet);/ 清理该套接字的资源iRet = closesocket(skListen);ASSERT(SOCKET_ERROR != iRet);/ 清理 Winsocket 资源iRet = WSACleanup();ASSERT(SOCKET_ERROR != iRet);cin.get();return 0;int ServerRecv(LPVOID lParam)/ 拷贝结构体各个字段到线程中PSERVER_RECV psr = (PSERVER_RECV)lParam;SERVER_RECV

17、sr = psr-skAccept, psr-pcs, psr-e, psr-t, psr-dwThreadID;/ 设置信号量, 使主线程能够接受新的连接BOOL bRet = FALSE; bRet = SetEvent(sr.e);ASSERT(bRet);const int c_iBufLen = 512;char szBufc_iBufLen + 1 = 0;const char c_szPrefix = Server recv:;const int c_iPrefLen = strlen(c_szPrefix);char szRelyc_iBufLen + 16 + 1 = 0;

18、strcpy(szRely, c_szPrefix);int iRet = SOCKET_ERROR;for(;)iRet = recv(sr.skAccept, szBuf, c_iBufLen, 0); / 接收客户端发送的信息, 如果客户端不发 送信息,则线程会阻塞到此处if(0 = iRet) / 客户端优雅的关闭了此连接cout Connection sr.dwThreadID shutdown. endl; break;else if(SOCKET_ERROR = iRet) / 客户端粗鲁的关闭了此连接或者接受信息出错cout Connection sr.dwThreadID r

19、ecv error. endl; break; szBufiRet = 0;EnterCriticalSection(sr.pcs);cout Connection sr.dwThreadID says: szBuf endl; /输出接收到的信LeaveCriticalSect ion( sr.pcs);/向客户端发送信息strcpy(szRely + c_iPrefLe n, szBuf);iRet = send(sr.skAccept, szRely, strlen(szRely), 0); / 客户端如果没有足够的缓冲区接受信息,贝熾程会阻塞到此处if(SOCKET_ERROR = i

20、Ret)cout Connection sr.dwThreadID send error. 0); ASSERT(SOCKET_ERROR != iRet);/清理该套接口的资源/ fileServerMultThreadServerMultThread.c/ASSERT(SOCKET ERROR != iRet);/ brief阻塞式多线程服务器程序。每当客户端程序%关闭脇/程对/象/并且向客户端回馈请求的信息。客#include #bReid=COsser-Thread(sr.t);#include 鷄勰mm甑ws2_32.lib)#un JTSCOANDde sr.dwThreadID

21、exit. #define EVENT HANDLE#efinnOo seThread CloseHandle #define CloseEvent CloseHandleI using std:cin;using std:cout;Jll I在用 Win socket编写程序时,我们首先必须要进行如下的操作,以为该进程初始化Win socket和 Ws2_32.dll,而使后面的函数调用有效。view pla in copy to clipboardpri nt?WSADATA data;ZeroMemory(&data, sizeof(WSADATA);Ret = WSAStartup(M

22、AKEW0RD(2, 0), &data);ASSERT(SOCKET_ERROR != iRet);WSAStartup第一个参数为要使用的 Win socket的版本,MAKEWORD(2, 0)表示我们使用sWCnTCket2e0。第V二个参数在t; WSAStaMp初始化后,可以获得一些 Win socket相关信息, 如AS版本iiWASDASeC所支持的最專徑OCket数量以及udp包的最大大小。在初始化了 Win socket后,我们就可以创建一个socket监听客户端的连接请求了。view pla in copy to clipboardpri nt?SOCKET skList

23、en = INV ALID_SOCKET;skListen = socket(AF_INET, SOCK_STREAM, 0);ASSERT(INV ALID_SOCKET != skListen);socket函数分配相应的资源并将该socket绑定到一个特定的传输服务提供者。socket的第一个参数为网络地址族,该参数只能为AF_INET,第二个参数可以为 SOCK_STREAM或者SOCK_DGRAM。SOCK_STREAM 为一个流式套接口, 它提供双向可靠、面向连接的TCP服务。SOCKsDG RAMadd为一个数据报套接口,它提供不可靠、面向无连接的 三个参数一般;RROR!表示由

24、Win socket选择具体的协议使用。UDP服务。第在建立了一个监听socket后,我们就可以将该套接口与本地地址进行绑定,已将其设置成为网络中一个独一无二的地址。view pla in copy to clipboardpri nt?Ret = bin d(skListe n, (sockaddr* )& adrServ, sizeof(sockaddr_i n);ASSERT(SOCKET_ERROR != iRet);iRet = listen(skListen, SOMAXCONN); / SOMAXCONN 表示 ASSERT(SOCKET_ERROR != iRet);在绑定了本

25、地地址后,我们就可以将该 socket设置为监听状态,以使该socket可以检测到来自客户端程序的连接请求。view pla in copy to clipboardpri nt?Ret = liste n(skListe n, SOMAXCONN); / SOMAXCONN表示可以连接到该程序的最大连接ASSERT(SOCKET_ERROR != iRet);_l接下来我们我们就可以利用套接口接受来自客户端程序的连接了。我们以该套接口为参数调用accept函数,accept函数调用成功后,将建立一个可以接受和发送数据的套接口skAccept。接受端接受客连户端view pla in copy

26、 to clipboardpri nt?/客户端端向服务器端发送连连接请求服务SOCKET skAccept = INVALID SOCKET; SOCIKdT_skAccept = INV 入LID_SOCKET;ZeroMemor y(&adrClit, sizeof(sockaddr_in); sdSBdpmeStLd驚n(sockaddr;&adrClit, &iASSERT(INVALID_SOCKE.T 匸 skAccept). ZeroMemory(&adrClit, sizeof(sockaddr_i n);如果没有客户端程序请求连接,n t iLe n = sizeof(so

27、ckaddr_ in);skAccept = accept(skListe n, (sockaddr*)&adrClit, & Le n); /务端程序会一直阻塞在这里等待连接ASSERT(INV ALID_SOCKET != skAccept);在成功的建立了新套接口后,我们就可以利用该套接口在我们的线程函数中接收和发送 数据了。view pla in copy to clipboardpri nt?iRet = recv(sr.skAccept, szBuf, c_iBufLe n, 0); / 接收客户端发送的信息,如果客户端不发送信息,贝熾程会阻塞到此处if(0 = iRet) /客户

28、端优雅的关闭了此连接cout Connection sr.dwThreadID shutdow n. en dl;iRet = recVskAccept, szBuf, c_iBufLen, 0); /接if(0 = iRet) /客户端优雅的关闭了此cout Connection srelse if(SOCKET_ERRORe= iRet) /客户端粗鲁的关闭了此连接或者接受信息出错 else if(SOCKET_ERROR = iRet) / 客cout Connection srcout Connection break;sr.dwThreadlD recv error. en dl;

29、break;szBufiRet = 0;szBufiRet = 0;Jview pla in copy to clipboardpri nt? strcpy(szRely + c_iPrefLe n, szBuf);strcpy(szRely + c_iPrefLen, szBuf);iRet = send(sr.skAccept, szRely, st、,一八iRet = send(sr.isjSOcKptERRR=siRen(szRely), 0); /客户端如果没有足够的缓冲区接受信ER息,则线程会阻塞到此处cout Connection srbreak;if(SOCKET_EfRROR

30、 = iRet)cout Connection sr.dwThreadID send error. 0);ASSERT(SOCKET_ERROR != iRet);/清理该套接口的资源iRet = closesocket(sr.skAccept);ASSERT(SOCKET_ERROR != iRet);ill监听socket的关闭也与上面套接口关闭的方法一致。在关闭了监听套接口后,我们的服务器程序应该调用 WSAClea nup函数,已完成对 Win socket和ws2_32.dll的清理。上述就 是该类型服务器程序应用程序执行的全过程了。客户端程序的代码跟服务器程序的代码相 似,程序代码

31、如下所示:view pla in copy to clipboardpri nt?/ file Clie ntBlockClie ntBlock.cpp/ brief连接服务器并向服务器发送信息,然后接受服务器发送的信息/#include #include #include #pragma comment(lib, ws2_32.lib )#define ASSERT assertusing std:cin;using std:cout;using std:endl;static const char c_szIP = 127.0.0.1;static const int c_iPort =

32、10001;int main()int iRet = SOCKET_ERROR;/ 初始化 Winsocket ,所有 Winsocket 程序必须先使用 WSAStartup 进行初始化WSADATA data;ZeroMemory(&data, sizeof(WSADATA);iRet = WSAStartup(MAKEWORD(2, 0), &data);ASSERT(SOCKET_ERROR != iRet);/ 建立连接套接字SOCKET skClient = INV ALID_SOCKET;skClient = socket(AF_INET, SOCK_STREAM, 0);ASS

33、ERT(INV ALID_SOCKET != skClient);/ 初始化连接套接字地址信息sockaddr_in adrServ;/ 表示网络地址ZeroMemory(&adrServ, sizeof(sockaddr_in);adrServ.sin_family = AF_INET; / 初始化地址格式,只能为 AF_INETadrServ.sin_port = htons(c_iPort); / 初始化端口, 由于网络字节顺序和主机字节顺序 相反,所以必须使用 htons 将主机字节顺序转换成网络字节顺序adrServ.sin_addr.s_addr = inet_addr(c_szI

34、P); / 初始化 IP , 由于网络字节顺序和主机字节 顺序相反,所以必须使用 inet_addr 将主机字节顺序转换成网络字节顺序/ 使用连接套接字进行连接iRet = connect(skClient, (sockaddr*)&adrServ, sizeof(sockaddr_in);ASSERT(SOCKET_ERROR != iRet);const int c_iBufLen = 512;char szBufc_iBufLen + 16 + 1 = 0;for(;)cout szBuf;if(0 = strcmp(exit, szBuf)break;/ 向服务器端发送信息iRet =

35、 send(skClient, szBuf, strlen(szBuf), 0); / 服务器端如果没有足够的缓冲区接受信 息,则线程会阻塞到此处if(SOCKET_ERROR = iRet)cout send error. endl;break;/ 接收服务器端发送的信息iRet = recv(skClient, szBuf, c_iBufLen, 0); / 如果服务器端没有发送数据,则会阻塞到此 处if(0 = iRet)cout connection shutdown. endl;break;else if(SOCKET_ERROR = iRet)cout recv error. endl;break;szBufiRet = 0;cout szBuf 0); ASSERT(SOCKET_ERROR != iRet);/清理该套接口的资源iRet = closesocket(skClie nt);ASSERT(SOCKET_ERROR != iRet);/清理Win socket资源iRet = WSAClea nup();/ fileClientBlockClientBlock.cpp/ASSERT(SOCKET ERROR != iRet);/ brief连接服务器并向

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论