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通信之三:阻塞版本的客戶/服務器模型