<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          一道高頻騰訊面試題:tcp數(shù)據(jù)發(fā)送問題

          共 8086字,需瀏覽 17分鐘

           ·

          2022-07-17 17:20

          問題引出

          好幾個讀者私信說在騰訊面試過程中,被面試官問到了一個問題:“一個tcp服務(wù)端和一個tcp客戶端,客戶端和服務(wù)端建立連接后,服務(wù)端一直sleep,然后客戶端一直發(fā)送數(shù)據(jù)會是什么現(xiàn)象”。

          要回答這個問題,需要我們清楚tcp協(xié)議的特點(diǎn)和tcp發(fā)送數(shù)據(jù)的大體過程。

          tcp發(fā)送數(shù)據(jù)過程

          恐怕接觸過網(wǎng)絡(luò)的同學(xué)都知道tcp面向連接可靠傳輸協(xié)議,意味著客戶端發(fā)送的數(shù)據(jù)服務(wù)端是一定能夠收到的,那么對于上面的問題就不可能存在數(shù)據(jù)的丟棄。下面我們分析一下tcp的傳輸過程。

          如圖所示,tcp數(shù)據(jù)包傳輸過程主要有如下幾個步驟:

          • ? 1.應(yīng)用程序調(diào)用write系列函數(shù)發(fā)送數(shù)據(jù) ,數(shù)據(jù)首先應(yīng)用程序緩沖區(qū)復(fù)制到發(fā)送端的內(nèi)核中的 套接字發(fā)送緩沖區(qū),然后write成功返回;需要特別注意的是write成功返回只是說明數(shù)據(jù)成功的由應(yīng)用進(jìn)程緩沖區(qū)復(fù)制到了套接字發(fā)送緩沖區(qū),并不代表數(shù)據(jù)發(fā)送到了對端主機(jī)。

          • ? 2.內(nèi)核協(xié)議棧將套接字發(fā)送緩沖區(qū)中的數(shù)據(jù)發(fā)送到對端主機(jī),這個過程不受應(yīng)用程序控制,而是發(fā)送端內(nèi)核協(xié)議棧完成;

          • ? 3.數(shù)據(jù)到達(dá)接收端主機(jī)的套接字接收緩沖區(qū),注意這個接收過程也不受應(yīng)用程序控制,而是由接收端內(nèi)核協(xié)議棧完成;

          • ? 4.數(shù)據(jù)由套接字接收緩沖區(qū)復(fù)制到接收端應(yīng)用程序緩沖區(qū),注意這個過程是由類似read等函數(shù)來完成。

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

          阻塞方式的情況

          write系列函數(shù)的工作方式默認(rèn)是阻塞方式:調(diào)用write函數(shù)時,內(nèi)核從應(yīng)用進(jìn)程的緩沖區(qū)到套接字的發(fā)送緩沖區(qū)復(fù)制數(shù)據(jù)。如果其發(fā)送緩沖區(qū)中沒有空間,進(jìn)程將進(jìn)入睡眠,直到有空間為止。

          因此,阻塞方式下,如果服務(wù)端一直sleep不接收數(shù)據(jù),而客戶端一直write,也就是只能執(zhí)行上述過程中的前三步,這樣最后接收端的套接字接收緩沖區(qū)和發(fā)送端套接字發(fā)送緩沖區(qū)都被填滿,這樣write就無法繼續(xù)將數(shù)據(jù)從應(yīng)用程序復(fù)制到發(fā)送端的套接字發(fā)送緩沖區(qū)了,從而發(fā)送端進(jìn)程進(jìn)入睡眠。可以用下面的程序驗證。

          tcpClient.c是客戶端代碼用來發(fā)送數(shù)據(jù),客戶端每次write成功一次,將計數(shù)器count加1,同時輸出本次write成功的字節(jié)數(shù)。count保存客戶端write成功的次數(shù)。

          #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));

              //與服務(wù)器端進(jìn)行通信
              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是服務(wù)端程序,服務(wù)端并不接收數(shù)據(jù)。

          #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//存儲服務(wù)器端socket地址結(jié)構(gòu)
              struct sockaddr_in client_addr//存儲客戶端 socket地址結(jié)構(gòu)
              pid_t pid;
              listenfd = socket(AF_INET,SOCK_STREAM,0);
              memset(&server_addr,0,sizeof(server_addr));
              server_addr.sin_family = AF_INET; //協(xié)議族
              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);//子進(jìn)程不接收數(shù)據(jù),sleep 1000秒
                      exit(0);
                  }
                  else
                  {
                      close(connfd);
                  }
              }
          }

          首先編譯運(yùn)行服務(wù)端,然后啟動客戶端,運(yùn)行結(jié)果如下:

          可以看到客戶端write成功377次后就陷入了阻塞,注意這個時候不能說明發(fā)送端的套接字發(fā)送緩沖區(qū)一定是滿的,只能說明套接字發(fā)送緩沖區(qū)的可用空間小于write請求寫的自己數(shù)——1024。

          非阻塞方式的情況

          下面看一下非阻塞套接字情況下,write的工作方式:對于一個非阻塞的TCP套接字,如果發(fā)送緩沖區(qū)中根本沒用空間,輸出函數(shù)將立即返回一個EWOULDBLOCK錯誤。如果發(fā)送緩沖區(qū)中有一些空間,返回值將是內(nèi)核能夠復(fù)制到該緩沖區(qū)的字節(jié)數(shù)。這個字節(jié)數(shù)也成為“不足計數(shù)”。

          這樣就可以知道非阻塞情況下服務(wù)端一直sleep,客戶端一直write數(shù)據(jù)的效果了:開始客戶端write成功,隨著客戶端write,接收端的套接字接收緩沖區(qū)和發(fā)送端的套接字發(fā)送緩沖區(qū)會被填滿。當(dāng)發(fā)送端的套接字發(fā)送緩沖區(qū)的可用空間小于write請求寫的字節(jié)數(shù)時,write立即返回-1,并將errno置為EWOULDBLOCK。

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

          #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); //將已連接的套接字設(shè)置為非阻塞模式
              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);
          }

          首先編譯運(yùn)行服務(wù)端,然后啟動客戶端,運(yùn)行結(jié)果如下圖所示。

          可以看到客戶端成功write 185次后就發(fā)生套接字發(fā)送緩沖區(qū)空間不足,從而返回EWOULDBLOCK錯誤。我們注意到每次write同樣的字節(jié)數(shù)(1024)阻塞模式下能write成功377次,為什么非阻塞情況下要少呢?

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

          本文源碼地址:
          https://github.com/qinlizhong1/C/write

          本文示例代碼環(huán)境:
          操作系統(tǒng):macOs 12.1
          編譯器:gcc

          —  —

          歡迎關(guān)注原創(chuàng)技術(shù)號↓↓↓
          如有幫助,辛苦點(diǎn)贊和在看
          瀏覽 58
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  啦啦啦www日本高清 | 操美女嫩逼网站 | 内射网站 | 久久久久久黄色视频 | 成人网址大全 |