上一篇中實現出來的客戶端只能向服務器端發送一次數據,然後就斷開了連接,那麼如果需要向服務器端持續發送數據,那麼應該怎麼做?
一個很直觀地想法就是修改客戶端的第4步,即發送,接收數據那一步,在基本的客戶/服務器模型中我們是直接發送一個字符串給服務器端,現在我們從控制檯接收數據將接收到的數據發送給服務器端,然後從服務端端接收數據並打印輸出。並持續從控制檯讀取數據。
而服務器端也只需要更改第6步,即調用send和recv這兩個函數和客戶端進行通信這一步,在源代碼中通過註釋可以很方便找到這一步,現在服務器端也需要持續從客戶端接收數據,將接收到的數據顯示在客戶端,然後在這個數據前面加上「message from client:」這個字符串後再將字符串發送給客戶端。
可以發現,客戶端的代碼只在第4步進行了修改,而服務器端的代碼只在第6步進行了修改。
服務器端的代碼:
- #include <stdio.h>
- #include <stdlib.h>
- #include <WinSock2.h>
- #include <iostream>
-
- #pragma comment(lib, "ws2_32.lib")
-
-
- using namespace std;
-
- #define PORT 6000
- //#define IP_ADDRESS "10.11.163.113"
- #define IP_ADDRESS "127.0.0.1" //設置連接的服務器的ip地址
-
- void main()
- {
-
- WSADATA wsaData;
- int err;
-
- //1.加載套接字庫
- err=WSAStartup(MAKEWORD(1,1),&wsaData);
- if (err!=0)
- {
- cout<<"Init Windows Socket Failed::"<<GetLastError()<<endl;
- return ;
- }
-
- //2.創建socket
- //套接字描述符,SOCKET實際上是unsigned int
- SOCKET serverSocket;
- serverSocket=socket(AF_INET,SOCK_STREAM,0);
- if (serverSocket==INVALID_SOCKET)
- {
- cout<<"Create Socket Failed::"<<GetLastError()<<endl;
- return ;
- }
-
-
- //服務器端的地址和端口號
- struct sockaddr_in serverAddr,clientAdd;
- serverAddr.sin_addr.s_addr=inet_addr(IP_ADDRESS);
- serverAddr.sin_family=AF_INET;
- serverAddr.sin_port=htons(PORT);
-
- //3.綁定Socket,將Socket與某個協議的某個地址綁定
- err=bind(serverSocket,(struct sockaddr*)&serverAddr,sizeof(serverAddr));
- if (err!=0)
- {
- cout<<"Bind Socket Failed::"<<GetLastError()<<endl;
- return ;
- }
-
-
- //4.監聽,將套接字由默認的主動套接字轉換成被動套接字
- err=listen(serverSocket,10);
- if (err!=0)
- {
- cout<<"listen Socket Failed::"<<GetLastError()<<endl;
- return ;
- }
-
- cout<<"服務器端已啓動......"<<endl;
-
- int addrLen=sizeof(clientAdd);
-
- while(true)
- {
- //5.接收請求,當收到請求後,會將客戶端的信息存入clientAdd這個結構體中,並返回描述這個TCP連接的Socket
- SOCKET sockConn=accept(serverSocket,(struct sockaddr*)&clientAdd,&addrLen);
- if (sockConn==INVALID_SOCKET)
- {
- cout<<"Accpet Failed::"<<GetLastError()<<endl;
- return ;
- }
-
- cout<<"客戶端連接:"<<inet_ntoa(clientAdd.sin_addr)<<":"<<clientAdd.sin_port<<endl;
-
- char receBuff[MAX_PATH];
- char sendBuf[MAX_PATH];
- //6.調用send和recv這兩個函數和客戶端進行通信,相比於第一個版本,只有這一步發生了變化
- while(true)
- {
- memset(receBuff,0,sizeof(receBuff));
- memset(sendBuf,0,sizeof(sendBuf));
-
- //接收數據
- err=recv(sockConn,receBuff,MAX_PATH,0);
-
- //下面是客戶端退出的判斷條件,如果不加這個條件,在客戶端關閉後服務器會一直執行recv語句
- if (err==0||err==SOCKET_ERROR)
- {
- cout<<"客戶端退出"<<endl;
- break;
- }
-
- cout<<"message from client:"<<receBuff<<endl;
- strcpy(sendBuf,"server receive a message:");
- strcat(sendBuf,receBuff);
-
- //發送數據
- send(sockConn,sendBuf,strlen(sendBuf)+1,0);
- }
-
- //關閉這個socket
- closesocket(sockConn);
- }
-
- closesocket(serverSocket);
- //清理Windows Socket庫
- WSACleanup();
- }
客戶端代碼:
- #include <stdio.h>
- #include <WinSock2.h>
- #include <iostream>
- #include <string>
-
- #pragma comment(lib, "ws2_32.lib")
-
- #define PORT 6000
- //#define IP_ADDRESS "10.11.163.113" //表示服務器端的地址
- #define IP_ADDRESS "127.0.0.1" //直接使用本機地址
-
- using namespace std;
-
- void main()
- {
- WSADATA wsaData;
- int err;
-
- //1.首先執行初始化Windows Socket庫
- err=WSAStartup(MAKEWORD(1,1),&wsaData);
- if (err!=0)
- {
- cout<<"Init Socket Failed::"<<GetLastError()<<endl;
- return ;
- }
-
- //2.創建Socket
- SOCKET sockClient=socket(AF_INET,SOCK_STREAM,0);
-
- struct sockaddr_in addrServer;
- addrServer.sin_addr.s_addr=inet_addr(IP_ADDRESS);
- addrServer.sin_family=AF_INET;
- addrServer.sin_port=htons(PORT);
-
- //3.連接Socket,第一個參數爲客戶端socket,第二個參數爲服務器端地址
- err=connect(sockClient,(struct sockaddr *)&addrServer,sizeof(addrServer));
- if (err!=0)
- {
- cout<<"Connect Error::"<<GetLastError()<<endl;
- return ;
- }else
- {
- cout<<"連接成功!"<<endl;
- }
-
- char sendBuff[MAX_PATH];
- char recvBuf[MAX_PATH];
- while (true)
- {
- //4.發送,接收數據,相比與第一個版本,只有這一步發生了變化
- cin.getline(sendBuff,sizeof(sendBuff));
- send(sockClient,sendBuff,strlen(sendBuff)+1,0); //第三個參數加上1是爲了將字符串結束符'\0'也發送過去
- recv(sockClient,recvBuf,MAX_PATH,0);
- cout<<recvBuf<<endl;
- }
-
- //4.關閉套接字
- closesocket(sockClient);
- WSACleanup();
- }
上面這個模型在只有一個客戶端時是能正常運行的,如下:
客戶端連接服務器成功後會在客戶端顯示「連接成功!」的字符串,同時服務器端也顯示連接成功的客戶端的ip地址和端口號,接着客戶端發送hello,nihao,good給服務器端,服務器每次收到一個字符串會在服務器端顯示"message from client:"+字符串。
一個客戶端連接時服務器端能正常工作,但是有多個客戶端連接時服務器端就會出問題了,這是由於默認情況下socket是阻塞式的,在上面服務器端的代碼中第6步現在被替換成了一個循環來連續接受來自哪個連接的數據,在這個連接沒有斷開之前,它是不會再次進行accept調用,連接其它客戶端的。
如下圖所示:當再次啓動一個客戶端時,客戶端顯示「連接成功!」表示客戶端的connect成功返回,但是服務器端沒有顯示「客戶端連接:」這行字符串,這行字符串是在accept函數返回時調用的,所以accept函數此時沒有返回。原因也很簡單,服務器陷入第6步接收數據和發送數據中的死循環了。
當關閉第一個客戶端以後,服務器端會可以變成下圖所示:顯示有客戶端退出,並顯示有客戶端連接。
可執行文件可以在這兒下載,工程文件可以在這兒下載。
下一篇將介紹多個客戶端同時連接服務器進行通信的方法。