計算機通訊之謎,帶你徹底理解socket網路程式設計(二)
在《計算機通訊之謎,帶你徹底理解socket網路程式設計(一)》一文裡我們提到了,客戶端傳送了資料了之後,不管服務端還是客戶端都close退出了,也就是說只能傳送一次資料,這顯然不符合實際的用途。那麼該如何更改程式呢?
1、持續傳送
要想實現持續傳送,聰明的你可能想到了用迴圈,思路完全正確,但是迴圈多少次呢?實際的使用情況服務端一直都要執行,除非系統崩掉了,而客戶端和服務端的長連線也要一直連著,除非客戶端自己關閉了連線。所以我們的思路是雙端都無限迴圈!所以可以用一個while(1)來迴圈,那麼應該在哪裡開始迴圈,這個要看自己具體的要求,如果要求三次握手只一次,隨後傳送資料的話進行持續傳送。那麼服務端和客戶端的程式碼分別如下:
1.1、服務端程式碼
#include <stdio.h> #include <sys/socket.h> #include <netinet/in.h> #include <stdlib.h> #include <arpa/inet.h> #include <unistd.h> #include <string.h> #define BUF_SIZE 512 #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ }while (0) int main() { //建立套接字 int m_sockfd = socket(AF_INET, SOCK_STREAM, 0); if (m_sockfd < 0) { ERR_EXIT("create socket fail"); } //初始化socket元素 struct sockaddr_in server_addr; int server_len = sizeof(server_addr); memset(&server_addr, 0, server_len); server_addr.sin_family= AF_INET; //server_addr.sin_addr.s_addr = inet_addr("0.0.0.0"); //用這個寫法也可以 server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(39002); //繫結檔案描述符和伺服器的ip和埠號 int m_bindfd = bind(m_sockfd, (struct sockaddr *)&server_addr, server_len); if (m_bindfd < 0) { ERR_EXIT("bind ip and port fail"); } //進入監聽狀態,等待使用者發起請求 int m_listenfd = listen(m_sockfd, 20); if (m_listenfd < 0) { ERR_EXIT("listen client fail"); } //定義客戶端的套接字,這裡返回一個新的套接字,後面通訊時,就用這個m_connfd進行通訊 struct sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); int m_connfd = accept(m_sockfd, (struct sockaddr *)&client_addr, &client_len); printf("client accept success\n"); //接收客戶端資料,並相應 char buffer[BUF_SIZE]; while (1) { memset(buffer, 0, sizeof(buffer)); //重置緩衝區 recv(m_connfd, buffer, sizeof(buffer)-1, 0); printf("server recv:%s\n", buffer); strcat(buffer, "+ACK"); send(m_connfd, buffer, sizeof(buffer)-1, 0); } //關閉套接字 close(m_connfd); close(m_sockfd); printf("server socket closed!!!\n"); return 0; }
1.2、客戶端程式碼
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #define BUF_SIZE 512 #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while (0) int main() { //建立套接字 int m_sockfd = socket(AF_INET, SOCK_STREAM, 0); if (m_sockfd < 0) { ERR_EXIT("create socket fail"); } //伺服器的ip為本地,埠號 struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = inet_addr("81.68.140.74"); server_addr.sin_port = htons(39002); //向伺服器傳送連線請求 int m_connectfd = connect(m_sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)); if (m_connectfd < 0) { ERR_EXIT("connect server fail"); } //傳送並接收資料 char buffer[BUF_SIZE]; while (1) { memset(buffer, 0, sizeof(buffer)); //重置緩衝區 printf("client send:"); scanf("%s", buffer); send(m_sockfd, buffer, sizeof(buffer)-1, 0); recv(m_sockfd, buffer, sizeof(buffer)-1, 0); printf("client recv:%s\n", buffer); } //斷開連線 close(m_sockfd); printf("client socket closed!!!\n"); return 0; }
以上程式碼就可以正確的持續傳送資料,程式碼已經在Linux主機上執行過,完全正確,如果有問題,歡迎評論說明。需要的童鞋可以拿去用。
2、服務端一直收到空包
那麼以上程式碼有沒有什麼問題呢?如果親自在linux主機上執行過的小夥伴可能會發現如果用Ctrl+C掐掉客戶端或者kill掉客戶端程序的話,服務端會不停的列印收到的資料,但是資料卻是空字串,GDB除錯發現收到的實際上的0長度的空包(有時候持續收到多個空包後服務端就會斷開連線)。也就是說
當客戶端斷開,服務端不停的接收到一個0位元組
這個非常奇怪,客戶端已經斷開了,為什麼服務端還會收到一個0位元組的資料呢?這個問題我找了很久依舊沒有找到一個合理的答案,網上的解釋不是很明白,有懂的同學可以在評論區幫忙解釋下。以下解釋來自網路
一、使用者正常退出,這時你還迴圈呼叫了Receive方法時會出現,並會陷入死迴圈。
二、使用者非正常退出,包括拔網線,宕機,伺服器會捕捉到一個異常
問題的原因暫時無法找到,但是解決的辦法卻是有的,我們在收到客戶端的資料的時候可以判斷資料包的大小,如果為0的可能就是表示關閉客戶端了。那麼這時候服務端要做的就是判斷收到的資料包的大小,如果是0的話就去close()客戶端的連線,這樣子就可以正常的接受和傳送資料,客戶端關閉了,服務端也不會有問題。修改後的while部分的程式碼如下
while (1) { if (m_connfd < 0) { m_connfd = accept(m_sockfd, (struct sockaddr *)&client_addr, &client_len); printf("client accept success again!!!\n"); } memset(buffer, 0, sizeof(buffer)); //重置緩衝區 int recvLen = recv(m_connfd, buffer, sizeof(buffer) - 1, 0); if (recvLen <= 0) { close(m_connfd); m_connfd = -1; printf("client lose connection!!!\n"); continue; } printf("server recv:%s\n", buffer); strcat(buffer, "+ACK"); send(m_connfd, buffer, sizeof(buffer) - 1, 0); }
3、程式碼缺陷,問題思考
以上的程式碼確實可以實現客戶端持續傳送資料,客戶端斷開與重連,服務端仍舊保持正常的過程。但是如果你以為socket網路程式設計就這麼簡單的話,那麼你就真的太天真了。以上的客戶端和服務端僅僅只是一對一的,就是說服務端就一個程序,客戶端也就一個程序。但實際應用的過程中怎麼可能這麼簡單呢?服務端可能就一臺,但是客戶端是多臺的。
那這時候請同學動手做個實驗,在啟動一個客戶端程序,你會發現服務端沒反應,客戶端傳送資料的話,服務端也是沒反應!那麼該怎麼辦呢?聰明的你能夠想到解決辦法嗎?欲知後事如何,請聽下回分解!
更多精彩內容,請關注同名公眾:一點筆記alittle