一道高頻騰訊面試題: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

科技分類資訊推薦

地平線總裁陳黎明:高階智能輔助駕駛的拐點已經到來,將會快速增長 - 天天要聞

地平線總裁陳黎明:高階智能輔助駕駛的拐點已經到來,將會快速增長

紅星資本局7月11日消息,今日,地平線(09660.HK)總裁陳黎明在2025中國汽車論壇上表示,高階智能輔助駕駛的拐點已經到來,後面會有比較快速的增長。對此,他表示依據有三:第一,智能駕駛是我國智能網聯新能源汽車發展戰略的一個重要組成部分,政府工作報告對智能汽車的發展做出了規劃和行動方向。在電動化上,地平線可以...
洛斐發佈Flow2矮軸三模無線機械鍵盤 - 天天要聞

洛斐發佈Flow2矮軸三模無線機械鍵盤

沒想到,洛斐Flow系列這麼快就進入迭代了,發佈全新Flow2。引入了全新的設計語言,依然是全鋁金屬外殼機身,邊框像蘋果手機那樣的直板機身。其中,右側部分加長,側面則是增加了Touch Bar觸控滑動條,支持調節燈光亮度或系統音量。另一方面,鍵盤背面增加了同樣極具辨識度的撐腳模塊,支持兩種角度調節。 同時還全新升級了...
大爺寫《我的母親》火爆全網!視頻發佈者最新發聲 - 天天要聞

大爺寫《我的母親》火爆全網!視頻發佈者最新發聲

連日來,一段關於大爺挑戰寫作1957年高考同題作文《我的母親》的視頻火爆全網。不少網民表示,文字質樸,催人淚下,深深被打動。7月11日,大皖新聞記者聯繫上了視頻發佈者連文傑。對方表示,視頻中展示的作文內容由大爺獨立寫作,產生的版權收入也全給大爺。大爺寫作文視頻走紅網絡。大爺寫的作文在網上爆火大皖新聞記者注...
10.38萬元起,吉利銀河A7發佈預售價 - 天天要聞

10.38萬元起,吉利銀河A7發佈預售價

7月11日,吉利銀河發佈了預售價,預售價格範圍為10.38-13.38萬元,在發佈預售價格的同時,官方也帶來了多項權益,包括500元訂金抵扣1500元,贈送價值8800元的Flyme Sound音響等。
「中欣卡」突然停擺,超150萬用戶退款難 - 天天要聞

「中欣卡」突然停擺,超150萬用戶退款難

覆蓋北京2000多家門店、擁有超150萬持卡用戶的預付卡「中欣卡」近日停擺,線上線下消費渠道均被暫停,恢復時間未知。大量持卡用戶面臨資金凍結困境。消費渠道全面「癱瘓」近日,中欣卡發佈公告稱「因業務調整,全面暫停所有門店合作,無法消費」。中國新聞網《民生調查局》記者發現,其官網合作商戶名錄已清空,線上提貨渠...
突發,聊聊Manus「刪博跑路」 - 天天要聞

突發,聊聊Manus「刪博跑路」

摘要:「如果最後有不錯的結果,證明作為中國出生的創始人,也能在新的環境下做好全球化的產品,那就太好了」鳳凰網科技 出品作者|董雨晴7月11日,AI Agent產品Manus官方賬號清空了發佈在微博、小紅書的內容。兩天前,Manus剛剛陷入裁