socke通信之三:阻塞版本的客户/服务器模型

原文地址为: socke通信之三:阻塞版本的客户/服务器模型

上一篇中实现出来的客户端只能向服务器端发送一次数据,然后就断开了连接,那么如果需要向服务器端持续发送数据,那么应该怎么做?


一个很直观地想法就是修改客户端的第4步,即发送,接收数据那一步,在基本的客户/服务器模型中我们是直接发送一个字符串给服务器端,现在我们从控制台接收数据将接收到的数据发送给服务器端,然后从服务端端接收数据并打印输出。并持续从控制台读取数据。


而服务器端也只需要更改第6步,即调用send和recv这两个函数和客户端进行通信这一步,在源代码中通过注释可以很方便找到这一步,现在服务器端也需要持续从客户端接收数据,将接收到的数据显示在客户端,然后在这个数据前面加上“message from client:”这个字符串后再将字符串发送给客户端。


可以发现,客户端的代码只在第4步进行了修改,而服务器端的代码只在第6步进行了修改。


服务器端的代码:

  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" //设置连接的服务器的ip地址  
  14.   
  15. void main()  
  16. {  
  17.       
  18.     WSADATA wsaData;  
  19.     int err;  
  20.   
  21.     //1.加载套接字库  
  22.     err=WSAStartup(MAKEWORD(1,1),&wsaData);  
  23.     if (err!=0)  
  24.     {  
  25.         cout<<"Init Windows Socket Failed::"<<GetLastError()<<endl;  
  26.         return ;  
  27.     }  
  28.   
  29.     //2.创建socket  
  30.     //套接字描述符,SOCKET实际上是unsigned int  
  31.     SOCKET serverSocket;  
  32.     serverSocket=socket(AF_INET,SOCK_STREAM,0);  
  33.     if (serverSocket==INVALID_SOCKET)  
  34.     {  
  35.         cout<<"Create Socket Failed::"<<GetLastError()<<endl;  
  36.         return ;  
  37.     }  
  38.   
  39.   
  40.     //服务器端的地址和端口号  
  41.     struct sockaddr_in serverAddr,clientAdd;  
  42.     serverAddr.sin_addr.s_addr=inet_addr(IP_ADDRESS);  
  43.     serverAddr.sin_family=AF_INET;  
  44.     serverAddr.sin_port=htons(PORT);  
  45.   
  46.     //3.绑定Socket,将Socket与某个协议的某个地址绑定  
  47.     err=bind(serverSocket,(struct sockaddr*)&serverAddr,sizeof(serverAddr));  
  48.     if (err!=0)  
  49.     {  
  50.         cout<<"Bind Socket Failed::"<<GetLastError()<<endl;  
  51.         return ;  
  52.     }  
  53.   
  54.   
  55.     //4.监听,将套接字由默认的主动套接字转换成被动套接字  
  56.     err=listen(serverSocket,10);  
  57.     if (err!=0)  
  58.     {  
  59.         cout<<"listen Socket Failed::"<<GetLastError()<<endl;  
  60.         return ;  
  61.     }  
  62.   
  63.     cout<<"服务器端已启动......"<<endl;  
  64.   
  65.     int addrLen=sizeof(clientAdd);  
  66.       
  67.     while(true)  
  68.     {  
  69.         //5.接收请求,当收到请求后,会将客户端的信息存入clientAdd这个结构体中,并返回描述这个TCP连接的Socket  
  70.         SOCKET sockConn=accept(serverSocket,(struct sockaddr*)&clientAdd,&addrLen);  
  71.         if (sockConn==INVALID_SOCKET)  
  72.         {  
  73.             cout<<"Accpet Failed::"<<GetLastError()<<endl;  
  74.             return ;  
  75.         }  
  76.       
  77.         cout<<"客户端连接:"<<inet_ntoa(clientAdd.sin_addr)<<":"<<clientAdd.sin_port<<endl;  
  78.   
  79.         char receBuff[MAX_PATH];  
  80.         char sendBuf[MAX_PATH];  
  81.         //6.调用send和recv这两个函数和客户端进行通信,相比于第一个版本,只有这一步发生了变化  
  82.         while(true)  
  83.         {  
  84.             memset(receBuff,0,sizeof(receBuff));  
  85.             memset(sendBuf,0,sizeof(sendBuf));  
  86.   
  87.             //接收数据  
  88.             err=recv(sockConn,receBuff,MAX_PATH,0);  
  89.   
  90.             //下面是客户端退出的判断条件,如果不加这个条件,在客户端关闭后服务器会一直执行recv语句  
  91.             if (err==0||err==SOCKET_ERROR)  
  92.             {  
  93.                 cout<<"客户端退出"<<endl;  
  94.                 break;  
  95.             }  
  96.   
  97.             cout<<"message from client:"<<receBuff<<endl;  
  98.             strcpy(sendBuf,"server receive a message:");  
  99.             strcat(sendBuf,receBuff);  
  100.   
  101.             //发送数据  
  102.             send(sockConn,sendBuf,strlen(sendBuf)+1,0);       
  103.         }  
  104.   
  105.         //关闭这个socket  
  106.         closesocket(sockConn);  
  107.     }  
  108.   
  109.     closesocket(serverSocket);  
  110.     //清理Windows Socket库  
  111.     WSACleanup();  
  112. }  

客户端代码:

  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"  //直接使用本机地址  
  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.         //4.发送,接收数据,相比与第一个版本,只有这一步发生了变化  
  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. }  

上面这个模型在只有一个客户端时是能正常运行的,如下:

客户端连接服务器成功后会在客户端显示“连接成功!”的字符串,同时服务器端也显示连接成功的客户端的ip地址和端口号,接着客户端发送hello,nihao,good给服务器端,服务器每次收到一个字符串会在服务器端显示"message from client:"+字符串。


一个客户端连接时服务器端能正常工作,但是有多个客户端连接时服务器端就会出问题了,这是由于默认情况下socket是阻塞式的,在上面服务器端的代码中第6步现在被替换成了一个循环来连续接受来自哪个连接的数据,在这个连接没有断开之前,它是不会再次进行accept调用,连接其它客户端的。


如下图所示:当再次启动一个客户端时,客户端显示“连接成功!”表示客户端的connect成功返回,但是服务器端没有显示“客户端连接:”这行字符串,这行字符串是在accept函数返回时调用的,所以accept函数此时没有返回。原因也很简单,服务器陷入第6步接收数据和发送数据中的死循环了。




当关闭第一个客户端以后,服务器端会可以变成下图所示:显示有客户端退出,并显示有客户端连接。



可执行文件可以在这儿下载,工程文件可以在这儿下载。


下一篇将介绍多个客户端同时连接服务器进行通信的方法。


转载请注明本文地址: socke通信之三:阻塞版本的客户/服务器模型