socket通信之四:多线程版本的客户/服务器模型

上一篇中阻塞版本的客户/服务器模型实现了一个客户端能连续向服务器端发送数据,但是因为服务器端在循环体内会被阻塞,这样其它客户端再连接服务器端时服务器是无法处理的,这就导致了服务器只能处理一个客户端,其它客户端都会等待,直到当前处理的客户端退出,然后就会再有一个客户端和服务器端连接。


这一篇文章介绍一种可以让多个客户端同时能和服务器端通信的方式,就是服务器端的多线程模型,服务器每次调用accept建立一个连接后,启动一个新的线程为这个连接服务,然后服务器继续执行,这样就不会陷入阻塞状态了。


此时客户端的代码和前面阻塞模式是一样的,不需要进行改变,就是一个持续从控制台读取一行数据,然后发送到服务器,接着从服务器端读取数据的代码。


服务器端第6步的代码此时就不是调用recv()函数和send()函数从socket连接中获取和发送数据了,而是启动一个新的线程,将这个socket连接作为参数传递给这个新的线程,新启动的这个线程负责和当前这个连接的通讯,由于创建新线程后主循环会继续执行下去而不会出现阻塞,所以服务器可以处理多个客户端的请求了。


这个有点类似于之间阻塞版本中的recv()和send()函数相关的代码放置到了一个线程中执行。


服务端代码:


  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <WinSock2.h>  
  4. #include <iostream>  
  5.   
  6. #pragma comment(lib, "ws2_32.lib")  
  7.   
  8.   
  9. using namespace std;  
  10.   
  11. #define  PORT 6000  
  12. //#define  IP_ADDRESS "10.11.163.113"  //表示服务器端的地址  
  13. #define  IP_ADDRESS "127.0.0.1"  //直接使用本机地址  
  14.   
  15. //响应客户请求的线程  
  16. DWORD WINAPI clientService(LPVOID lpParameter)  
  17. {  
  18.     SOCKET clientSocket=(SOCKET)lpParameter;  
  19.     int err=0;  
  20.     char receBuff[MAX_PATH];  
  21.     char sendBuf[MAX_PATH];  
  22.     while(true)  
  23.     {  
  24.         memset(receBuff,0,sizeof(receBuff));  
  25.         memset(sendBuf,0,sizeof(sendBuf));  
  26.   
  27.         //接收数据  
  28.         err=recv(clientSocket,receBuff,MAX_PATH,0);  
  29.   
  30.         //下面是客户端退出的判断条件,如果不加这个条件,在客户端关闭后服务器会一直执行recv语句  
  31.         if (err==0||err==SOCKET_ERROR)  
  32.         {  
  33.             cout<<"客户端退出"<<endl;  
  34.             break;  
  35.         }  
  36.   
  37.         cout<<"message from client:"<<receBuff<<endl;  
  38.         strcpy(sendBuf,"server receive a message:");  
  39.         strcat(sendBuf,receBuff);  
  40.   
  41.         //发送数据  
  42.         send(clientSocket,sendBuf,strlen(sendBuf)+1,0);         //第三个参数加上1是为了将字符串结束符'\0'也发送过去  
  43.     }  
  44.   
  45.     //关闭这个socket  
  46.     closesocket(clientSocket);  
  47.   
  48.     return 0;  
  49. }  
  50.   
  51.   
  52.   
  53. void main()  
  54. {  
  55.       
  56.     WSADATA wsaData;  
  57.     int err;  
  58.   
  59.     //1.加载套接字库  
  60.     err=WSAStartup(MAKEWORD(1,1),&wsaData);  
  61.     if (err!=0)  
  62.     {  
  63.         cout<<"Init Windows Socket Failed::"<<GetLastError()<<endl;  
  64.         return ;  
  65.     }  
  66.   
  67.     //2.创建socket  
  68.     //套接字描述符,SOCKET实际上是unsigned int  
  69.     SOCKET serverSocket;  
  70.     serverSocket=socket(AF_INET,SOCK_STREAM,0);  
  71.     if (serverSocket==INVALID_SOCKET)  
  72.     {  
  73.         cout<<"Create Socket Failed::"<<GetLastError()<<endl;  
  74.         return ;  
  75.     }  
  76.   
  77.   
  78.     //服务器端的地址和端口号  
  79.     struct sockaddr_in serverAddr,clientAdd;  
  80.     serverAddr.sin_addr.s_addr=inet_addr(IP_ADDRESS);  
  81.     serverAddr.sin_family=AF_INET;  
  82.     serverAddr.sin_port=htons(PORT);  
  83.   
  84.     //3.绑定Socket,将Socket与某个协议的某个地址绑定  
  85.     err=bind(serverSocket,(struct sockaddr*)&serverAddr,sizeof(serverAddr));  
  86.     if (err!=0)  
  87.     {  
  88.         cout<<"Bind Socket Failed::"<<GetLastError()<<endl;  
  89.         return ;  
  90.     }  
  91.   
  92.   
  93.     //4.监听,将套接字由默认的主动套接字转换成被动套接字  
  94.     err=listen(serverSocket,10);  
  95.     if (err!=0)  
  96.     {  
  97.         cout<<"listen Socket Failed::"<<GetLastError()<<endl;  
  98.         return ;  
  99.     }  
  100.   
  101.     cout<<"服务器端已启动......"<<endl;  
  102.   
  103.     int addrLen=sizeof(clientAdd);  
  104.     HANDLE hThread=NULL;  
  105.       
  106.     while(true)  
  107.     {  
  108.         //5.接收请求,当收到请求后,会将客户端的信息存入clientAdd这个结构体中,并返回描述这个TCP连接的Socket  
  109.         SOCKET sockConn=accept(serverSocket,(struct sockaddr*)&clientAdd,&addrLen);  
  110.         if (sockConn==INVALID_SOCKET)  
  111.         {  
  112.             cout<<"Accpet Failed::"<<GetLastError()<<endl;  
  113.             return ;  
  114.         }  
  115.       
  116.         //6.此时启动一个新的线程调用send和recv这两个函数和客户端进行通信  
  117.         cout<<"客户端连接:"<<inet_ntoa(clientAdd.sin_addr)<<":"<<clientAdd.sin_port<<endl;  
  118.         hThread=CreateThread(NULL,0,clientService,(LPVOID)sockConn, 0, NULL);  
  119.         if (hThread==NULL)  
  120.         {  
  121.             cout<<"Create Thread Failed!"<<endl;  
  122.         }  
  123.         CloseHandle(hThread);  
  124.     }  
  125.   
  126.     closesocket(serverSocket);  
  127.     //清理Windows Socket库  
  128.     WSACleanup();  
  129. }  

客户端代码:(和前一个阻塞模式的代码是一样的)


  1. #include <stdio.h>  
  2. #include <WinSock2.h>  
  3. #include <iostream>  
  4. #include <string>  
  5.   
  6. #pragma comment(lib, "ws2_32.lib")  
  7.   
  8. #define  PORT 6000  
  9. //#define  IP_ADDRESS "10.11.163.113"  
  10. #define  IP_ADDRESS "127.0.0.1" //设置连接的服务器的ip地址  
  11.   
  12. using namespace std;  
  13.   
  14. void main()  
  15. {  
  16.     WSADATA wsaData;  
  17.     int err;  
  18.   
  19.     //1.首先执行初始化Windows Socket库  
  20.     err=WSAStartup(MAKEWORD(1,1),&wsaData);  
  21.     if (err!=0)  
  22.     {  
  23.         cout<<"Init Socket Failed::"<<GetLastError()<<endl;  
  24.         return ;  
  25.     }  
  26.   
  27.     //2.创建Socket  
  28.     SOCKET sockClient=socket(AF_INET,SOCK_STREAM,0);  
  29.   
  30.     struct sockaddr_in addrServer;  
  31.     addrServer.sin_addr.s_addr=inet_addr(IP_ADDRESS);  
  32.     addrServer.sin_family=AF_INET;  
  33.     addrServer.sin_port=htons(PORT);  
  34.       
  35.     //3.连接Socket,第一个参数为客户端socket,第二个参数为服务器端地址  
  36.     err=connect(sockClient,(struct sockaddr *)&addrServer,sizeof(addrServer));  
  37.     if (err!=0)  
  38.     {  
  39.         cout<<"Connect Error::"<<GetLastError()<<endl;  
  40.         return ;  
  41.     }else  
  42.     {  
  43.         cout<<"连接成功!"<<endl;  
  44.     }  
  45.   
  46.     char sendBuff[MAX_PATH];  
  47.     char recvBuf[MAX_PATH];  
  48.     while (true)  
  49.     {  
  50.         //3.发送,接收数据  
  51.         cin.getline(sendBuff,sizeof(sendBuff));  
  52.         send(sockClient,sendBuff,strlen(sendBuff)+1,0);     //第三个参数加上1是为了将字符串结束符'\0'也发送过去  
  53.         recv(sockClient,recvBuf,MAX_PATH,0);  
  54.         cout<<recvBuf<<endl;  
  55.     }  
  56.   
  57.     //4.关闭套接字  
  58.     closesocket(sockClient);  
  59.     WSACleanup();  
  60. }  

下面是测试的例子,同时可以有多个客户端和服务器通信:

下面是三个客户端和服务器通信的例子:




每次有客户端断开连接都会有提示。如下图:




到这一步才实现了一个多个客户端能同时和服务器通信的程序。


可执行程序可以在这里下载,整个工程的文件可以在这里下载。

阅读更多
个人分类: C++
上一篇socke通信之三:阻塞版本的客户/服务器模型
下一篇socket通信之五:select多路复用的客户/服务器模型
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭