一道高頻騰訊面試題:tcp數據發送問題

2022年07月15日20:52:27 科技 1165


問題引出


好幾個讀者私信說在騰訊面試過程中,被面試官問到了一個問題:「一個tcp服務端和一個tcp客戶端,客戶端和服務端建立連接後,服務端一直sleep,然後客戶端一直發送數據會是什麼現象」。


要回答這個問題,需要我們清楚tcp協議的特點和tcp發送數據的大體過程。




tcp發送數據過程


恐怕接觸過網絡的同學都知道tcp是面向連接的可靠傳輸協議,意味着客戶端發送的數據服務端是一定能夠收到的,那麼對於上面的問題就不可能存在數據的丟棄。下面我們分析一下tcp的傳輸過程。


一道高頻騰訊面試題:tcp數據發送問題 - 天天要聞



如圖所示,tcp數據包的傳輸過程主要有如下幾個步驟:


  • • 1.應用程序調用write系列函數發送數據 ,數據首先由應用程序緩衝區複製到發送端的內核中的 套接字發送緩衝區,然後write成功返回;需要特別注意的是write成功返回只是說明數據成功的由應用進程緩衝區複製到了套接字發送緩衝區,並不代表數據發送到了對端主機。
  • • 2.內核協議棧將套接字發送緩衝區中的數據發送到對端主機,這個過程不受應用程序控制,而是發送端內核協議棧完成;
  • • 3.數據到達接收端主機的套接字接收緩衝區,注意這個接收過程也不受應用程序控制,而是由接收端內核協議棧完成;
  • • 4.數據由套接字接收緩衝區複製到接收端應用程序緩衝區,注意這個過程是由類似read等函數來完成。


清楚了tcp的傳輸過程,現在我們分情況來討論上面的問題。




相關視頻推薦

一道高頻騰訊面試題:tcp數據發送問題 - 天天要聞




阻塞方式的情況


write系列函數的工作方式默認是阻塞方式:調用write函數時,內核從應用進程的緩衝區到套接字的發送緩衝區複製數據。如果其發送緩衝區中沒有空間,進程將進入睡眠,直到有空間為止。


因此,阻塞方式下,如果服務端一直sleep不接收數據,而客戶端一直write,也就是只能執行上述過程中的前三步,這樣最後接收端的套接字接收緩衝區和發送端套接字發送緩衝區都被填滿,這樣write就無法繼續將數據從應用程序複製到發送端的套接字發送緩衝區了,從而發送端進程進入睡眠。可以用下面的程序驗證。


tcpClient.c是客戶端代碼用來發送數據,客戶端每次write成功一次,將計數器count加1,同時輸出本次write成功的位元組數。count保存客戶端write成功的次數。



#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <memory.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define PORT 8888
#define Buflen 1024
int main(int argc,char *argv[])
{
    struct sockaddr_in server_addr;
    int n,count=0;
    int sockfd;
    char sendline[Buflen];
    sockfd= socket(AF_INET,SOCK_STREAM,0);
    memset(&server_addr,0,sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_addr.s_addr = inet_addr(argv[1]);
    connect(sockfd,(struct sockaddr *)&server_addr,sizeof(server_addr));

    //與服務器端進行通信
    memset(sendline,'x',sizeof(Buflen));

    while ( (n=write(sockfd,sendline,Buflen))>0 )
    {
        count++;
        printf("already write %d bytes -- %d\n",n,count);
    }

    if(n<0)
        perror("write error");
    close(sockfd);
}



下面的tcpServer.c是服務端程序,服務端並不接收數據。



#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <memory.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#define PORT 8888 //定義通信端口
#define BACKLOG 5 //定義偵聽隊列長度
#define buflen 1024
int listenfd,connfd;
int main(int argc,char *argv[])
{
    struct sockaddr_in server_addr; //存儲服務器端socket地址結構
    struct sockaddr_in client_addr; //存儲客戶端 socket地址結構
    pid_t pid;
    listenfd = socket(AF_INET,SOCK_STREAM,0);
    memset(&server_addr,0,sizeof(server_addr));
    server_addr.sin_family = AF_INET; //協議族
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY); //本地地址
    server_addr.sin_port = htons(PORT);
    bind(listenfd,(struct sockaddr *)&server_addr,sizeof(server_addr));
    listen(listenfd,BACKLOG);
    for(;;)
    {
        socklen_t addrlen = sizeof(client_addr);
        connfd = accept(listenfd,(struct sockaddr *)&client_addr,&addrlen);
        if(connfd<0)
            perror("accept error");
        printf("receive connection\n");
        if((pid = fork()) == 0)
        {
            close(listenfd);
            sleep(1000);//子進程不接收數據,sleep 1000秒
            exit(0);
        }
        else
        {
            close(connfd);
        }
    }
}



首先編譯運行服務端,然後啟動客戶端,運行結果如下:


一道高頻騰訊面試題:tcp數據發送問題 - 天天要聞



可以看到客戶端write成功377次後就陷入了阻塞,注意這個時候不能說明發送端的套接字發送緩衝區一定是滿的,只能說明套接字發送緩衝區的可用空間小於write請求寫的自己數——1024。


非阻塞方式的情況


下面看一下非阻塞套接字情況下,write的工作方式:對於一個非阻塞的TCP套接字,如果發送緩衝區中根本沒用空間,輸出函數將立即返回一個EWOULDBLOCK錯誤。如果發送緩衝區中有一些空間,返回值將是內核能夠複製到該緩衝區的位元組數。這個位元組數也成為「不足計數」。


這樣就可以知道非阻塞情況下服務端一直sleep,客戶端一直write數據的效果了:開始客戶端write成功,隨着客戶端write,接收端的套接字接收緩衝區和發送端的套接字發送緩衝區會被填滿。當發送端的套接字發送緩衝區的可用空間小於write請求寫的位元組數時,write立即返回-1,並將errno置為EWOULDBLOCK。


可以用下面的程序驗證,其中,服務端程序代碼和上面例子一樣,我們只看客戶端非阻塞模式代碼:



#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <memory.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <errno.h>
#define PORT 8888
#define Buflen 1024
int main(int argc,char *argv[])
{
    struct sockaddr_in server_addr;
    int n,flags,count=0;
    int sockfd;
    char sendline[Buflen];
    sockfd= socket(AF_INET,SOCK_STREAM,0);
    memset(&server_addr,0,sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_addr.s_addr = inet_addr(argv[1]);
    connect(sockfd,(struct sockaddr *)&server_addr,sizeof(server_addr));
    flags=fcntl(sockfd,F_GETFL,0); //將已連接的套接字設置為非阻塞模式
    fcntl(sockfd,F_SETFL,flags|O_NONBLOCK);
    memset(sendline,'a',sizeof(Buflen));
    
    while ( (n=write(sockfd,sendline,Buflen))>0 )
   {
     count++;
     printf("already write %d bytes -- %d\n",n,count);
   }
    
   if(n<0)
  {
    if(errno!=EWOULDBLOCK)
       perror("write error");
    else
       printf("EWOULDBLOCK ERROR\n"); 
  }
   close(sockfd);
}


一道高頻騰訊面試題:tcp數據發送問題 - 天天要聞


首先編譯運行服務端,然後啟動客戶端,運行結果如下圖所示。


一道高頻騰訊面試題:tcp數據發送問題 - 天天要聞


一道高頻騰訊面試題:tcp數據發送問題 - 天天要聞

編輯


可以看到客戶端成功write 185次後就發生套接字發送緩衝區空間不足,從而返回EWOULDBLOCK錯誤。我們注意到每次write同樣的位元組數(1024)阻塞模式下能write成功377次,為什麼非阻塞情況下要少呢?


這是因為阻塞模式下一直write到接收端的套接字接收緩衝區和發送端的套接字發送緩衝區都滿的情況才會阻塞。而非阻塞模式情況下有可能是發送端發送過程的第二步較慢,造成發送端的套接字發送緩衝區很快寫滿,而接收端的套接字接收緩衝區還沒有滿,這樣write就會僅僅因為發送端的套接字發送緩衝區滿而返回錯誤。


原文地址:https://mp.weixin.qq.com/s/rpNTjTUt19Bbyx6IWm2-ig

科技分類資訊推薦

8月正式發佈,小鵬MONA M03開啟全球首秀 - 天天要聞

8月正式發佈,小鵬MONA M03開啟全球首秀

量化之美,破風而來。7月3日下午14點,小鵬MONA M03開啟全球首秀。這款為年輕用戶打造的智能純電掀背轎跑,以其獨特的AI量化美學設計吸引了行業關注。小鵬汽車董事長CEO何小鵬攜手造型中心副總裁胡安馬·洛佩茲(JuanMa Lopez)共同出席了此次直播,深入解讀了小鵬MONA M03的設計創作理念和背後的技術實力。AI量化美學設計...
外媒:馬斯克或線上出席世界人工智能大會 人形機械人Optimus也將參展 - 天天要聞

外媒:馬斯克或線上出席世界人工智能大會 人形機械人Optimus也將參展

騰訊科技訊 7月3日消息,據國外媒體報道,埃隆·馬斯克(Elon Musk)擬於周四在上海舉辦的世界人工智能大會(WAIC)開幕式上發表主旨演講。儘管議程未明確指出馬斯克是現場出席活動還是通過視頻連線方式參與,但據知情人士透露,他預計不會親自到場。對於關於馬斯克出席方式及演講內容的詢問,WAIC的組織者尚未給予回應,特...
蘋果Vision Pro市場表現「溫和」  推動國內市場高端化 - 天天要聞

蘋果Vision Pro市場表現「溫和」 推動國內市場高端化

21世紀經濟報道記者倪雨晴 實習生劉冰儀、朱梓燁 深圳、廣州報道近日,蘋果Vision Pro國行版正式發售。與今年1月美國預售時迅速售罄、導致發貨延遲至三月中旬的熱烈反響相比,國行版的Vision Pro市場表現顯得較為「溫和」。
LG推出全球首款144Hz無線4K電視 - 天天要聞

LG推出全球首款144Hz無線4K電視

LG公司LG OLED evo M4 是第一款能夠接收無線傳輸的視頻和音頻的電視,分辨率為 4K,刷新率為 144 Hz。為此,該套件包括一個連接盒,可讓您在電視和接收器之間建立空中連接。
家用優選全能SUV博越L,時尚科技並存,理想出行夥伴! - 天天要聞

家用優選全能SUV博越L,時尚科技並存,理想出行夥伴!

在緊湊型SUV這片競爭激烈的紅海中,一款車型想要脫穎而出,不僅需要在某個方面做到極致,更需具備全方位的均衡實力。吉利新博越L正是這樣一款車型,它以卓越的設計美學、前瞻的智能科技、靈活實用的空間布局,以及高效的動力系統,精準擊中了現代家庭用車的多元需求,被譽為「
小米SU7導航信號差,真讓余承東說中了,雷軍再三解釋也沒用 - 天天要聞

小米SU7導航信號差,真讓余承東說中了,雷軍再三解釋也沒用

誰也沒想到余承東的一席話,讓小米車主的心涼了半截。最近,小米su7導航不準信號差的問題衝上了熱搜,正當車主們一籌莫展的時候,余承東在新問界M5發佈會上說的實話,給大家揭曉了答案。那時,余承東先是坦白了自己做無線電出身的事實,然後直言汽車前風
618購車狂歡,不能錯過吉利星越L - 天天要聞

618購車狂歡,不能錯過吉利星越L

​天貓年中大促進行時,就連汽車行業也來了一場618購車盛宴。吉利就拿出了它的明星產品——這個集高科技、豪華配置於一身的星越L,讓每一位嚮往品質生活的消費者都無法抗拒。