伺服器與一個客戶端的多次連線連線
這次是一個伺服器與一個客戶端可以多次連線,與上次有所不同的是讓客戶端可以持續傳送資料與伺服器端連線,不僅僅是之連線一次,下面我們直接給出程式碼,然後分析結果
//伺服器端 #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<string.h> #include<assert.h> #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h> int main() { int sockfd = socket(AF_INET,SOCK_STREAM,0); assert(sockfd != -1); struct sockaddr_in saddr, caddr; memset(&saddr,0,sizeof(saddr)); saddr.sin_family = AF_INET; saddr.sin_port = htons(6000); //<1024 root saddr.sin_addr.s_addr = inet_addr("127.0.0.1");//轉換成無符號的整形 //bind()命名套接字的第二個引數那裡是進行了強轉,因為一般的用的都是通用地址結構,但我們現在用的是IPV4協議,所以要強轉成IPV4專用的地址結構 int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr)); assert(res != -1); listen(sockfd,5); //建立監聽佇列,然後客戶端connect通過三次握手建立連線然後伺服器端accept接受連線 //當開啟兩個視窗的時候,你會發現在該程式中第一個客戶端視窗什麼都不輸入並且不關閉它的時候第二個視窗即使輸入內容客戶端也不會收到伺服器傳來的ok, while(1) { int len = sizeof(caddr); int c = accept(sockfd,(struct sockaddr*)&caddr,&len); if(c < 0) { continue; } printf("accept c = %d\n",c); while(1) //在此處加一個迴圈就可以 實現一個客戶端可以多次連線伺服器端 { char buff[128] = {0}; int n = recv(c,buff,127,0); if(n <= 0) //如果客戶端輸入資料並且被接收了那n必然是>0的,如果客戶端不輸入資料那就會阻塞,n小於0是因為對方關閉了連線,所以我們用四次揮手藉助返回值n = 0,來表示對方已經close { break; } printf("buff=%s\n",buff); send(c,"ok",2,0); } close(c); } }
//客戶端 #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<assert.h> #include<string.h> #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h> int main() { int sockfd = socket(AF_INET,SOCK_STREAM,0); assert(sockfd != -1); struct sockaddr_in saddr; memset(&saddr,0,sizeof(saddr)); saddr.sin_family = AF_INET; saddr.sin_port = htons(6000); saddr.sin_addr.s_addr = inet_addr("127.0.0.1");//這裡寫伺服器端的ip,就例如打電話你肯定撥打的是對方的手機號 int res = connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr)); assert(res != -1); while(1) { char buff[128] = {0}; printf("input:\n"); fgets(buff,128,stdin); if(strncmp(buff,"end",3) == 0) //以end結束 { break; } //將輸入的資料傳送給伺服器端然後清空buff,以便存放伺服器端傳送來的確認資訊ok send(sockfd,buff,strlen(buff),0); memset(buff,0,128); recv(sockfd,buff,127,0); printf("buff = %s\n",buff); } close(sockfd); exit(0); }
我們同時開啟兩個客戶端埠然後看執行結果並分析:
第一個視窗不傳送訊息也不關閉他而是一直佔用它的時候,如果第二個視窗傳送訊息是,它是不會收到伺服器端的確認回覆訊息ok的,但是如果第一個客戶端輸入end,結束並且關閉第一個客戶端的時候,第二個客戶端你什麼都不用輸入,伺服器就會將之前第二個客戶端輸入的內容依次打印出來,第二個客戶端就會依次收到伺服器端發來的確認資訊ok。
分析:第一個客戶端視窗不輸入資料執行的時候程式是阻塞在了recv接收處,因為客戶端沒有傳送資料所以伺服器端接收不上資料,第二個客戶端視窗開始傳送資料但是伺服器端沒有回覆確認資訊,是因為第二個視窗是阻塞在了accept處,從程式碼角度講,recv沒有結束的時候該處的小迴圈就不能退出,上面的大迴圈執行不了那麼c = accept就不能執行,理論上講就是說現在的第二個客戶端視窗是已經完成三次握手的,在已完成三次握手的佇列中等待accept去處理它的,但是由於前面的阻塞,accept前一個還沒有處理完它現在顧不上第二個客戶端的處理,所以在accept
第二個客戶端為什麼會依次將輸入的資訊全部列印?是因為之前第二個客戶端輸入的資訊都是存在已完成第三次握手的佇列中,等待accept去處理的,現在accept有空的時候會一一處理,accept處理一個recv就接收一個然後打印出來,然後accept再接受處理,這裡要和傳送/接收緩衝區、流式服務區分開。
為什麼埠號是6000?答:
埠值:1024以內叫知名埠,只有root管理員可以使用, 例如:http使用80
1024——4096 叫保留埠
>4096 叫臨時埠 應用程式使用的(我們自己寫程式)
為什麼第二個客戶端的ok也會依次全部列印,input直接跳過呢?沒來的及讓客戶端輸入。
返回的檔案描述符 c = 4,是因為前面的都被佔用,0標準輸入、1標準輸出、2標準錯誤、3是前面的套接字sockfd佔用,所以c是4,(檔案描述符總是使用的是最小未被使用的)。