日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

动画图解 socket 缓冲区的那些事儿

發布時間:2024/4/11 编程问答 69 豆豆
生活随笔 收集整理的這篇文章主要介紹了 动画图解 socket 缓冲区的那些事儿 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

先上這篇文章的目錄。

目錄

代碼執行send成功后,數據就發出去了嗎?

回答這個問題之前,需要了解什么是Socket 緩沖區

Socket 緩沖區

什么是 socket 緩沖區

編程的時候,如果要跟某個IP建立連接,我們需要調用操作系統提供的 socket API。

socket 在操作系統層面,可以理解為一個文件

我們可以對這個文件進行一些方法操作

用listen方法,可以讓程序作為服務器監聽其他客戶端的連接。

用connect,可以作為客戶端連接服務器。

用send或write可以發送數據,recv或read可以接收數據。

在建立好連接之后,這個 socket 文件就像是遠端機器的 "代理人" 一樣。比如,如果我們想給遠端服務發點什么東西,那就只需要對這個文件執行寫操作就行了。

socket_api

那寫到了這個文件之后,剩下的發送工作自然就是由操作系統內核來完成了。

既然是寫給操作系統,那操作系統就需要提供一個地方給用戶寫。同理,接收消息也是一樣。

這個地方就是 socket 緩沖區

用戶發送消息的時候寫給 send buffer(發送緩沖區)

用戶接收消息的時候寫給 recv buffer(接收緩沖區)

也就是說一個socket ,會帶有兩個緩沖區,一個用于發送,一個用于接收。因為這是個先進先出的結構,有時候也叫它們發送、接收隊列

一個socket有兩個緩沖區

怎么觀察 socket 緩沖區

如果想要查看 socket 緩沖區,可以在linux環境下執行 netstat -nt 命令。

#?netstat?-nt Active?Internet?connections?(w/o?servers) Proto?Recv-Q?Send-Q?Local?Address???????????Foreign?Address?????????State?????? tcp????????0?????60?172.22.66.69:22?????????122.14.220.252:59889????ESTABLISHED

這上面表明了,這里有一個協議(Proto)類型為 TCP 的連接,同時還有本地(Local Address)和遠端(Foreign Address)的IP信息,狀態(State)是已連接。

還有Send-Q 是發送緩沖區,下面的數字60是指,當前還有60 Byte在發送緩沖區中未發送。而 Recv-Q 代表接收緩沖區,此時是空的,數據都被應用進程接收干凈了。

TCP部分

我們在使用TCP建立連接之后,一般會使用 send 發送數據。

int?main(int?argc,?char?*argv[]) {//?創建socketsockfd=socket(AF_INET,SOCK_STREAM,?0))//?建立連接??connect(sockfd,?服務器ip信息,?sizeof(server))??//?執行?send?發送消息send(sockfd,str,sizeof(str),0))??//?關閉?socketclose(sockfd);return?0; }

上面是一段偽代碼,僅用于展示大概邏輯,我們在建立好連接后,一般會在代碼中執行 send 方法。那么此時,消息就會被立刻發到對端機器嗎?

執行 send 發送的字節,會立馬發送嗎?

答案是不確定!執行 send 之后,數據只是拷貝到了socket 緩沖區。至 于什么時候會發數據,發多少數據,全聽操作系統安排。

tcp_sendmsg 邏輯

在用戶進程中,程序通過操作 socket 會從用戶態進入內核態,而 send方法會將數據一路傳到傳輸層。在識別到是 TCP協議后,會調用 tcp_sendmsg 方法。

//?net/ipv4/tcp.c //?以下省略了大量邏輯 int?tcp_sendmsg() {??//?如果還有可以放數據的空間if?(skb_availroom(skb)?>?0)?{//?嘗試拷貝待發送數據到發送緩沖區err?=?skb_add_data_nocache(sk,?skb,?from,?copy);}??//?下面是嘗試發送的邏輯代碼,先省略????? }

在 tcp_sendmsg 中, 核心工作就是將待發送的數據組織按照先后順序放入到發送緩沖區中, 然后根據實際情況(比如擁塞窗口等)判斷是否要發數據。如果不發送數據,那么此時直接返回。

如果緩沖區滿了會怎么辦

前面提到的情況里是,發送緩沖區有足夠的空間,可以用于拷貝待發送數據。

如果發送緩沖區空間不足,或者滿了,執行發送,會怎么樣?

這里分兩種情況。

首先,socket在創建的時候,是可以設置是阻塞的還是非阻塞的。

int?s?=?socket(AF_INET,?SOCK_STREAM?|?SOCK_NONBLOCK,?IPPROTO_TCP);

比如通過上面的代碼,就可以將 socket 設置為非阻塞 (SOCK_NONBLOCK)。

當發送緩沖區滿了,如果還向socket執行send

  • 如果此時 socket 是阻塞的,那么程序會在那干等、死等,直到釋放出新的緩存空間,就繼續把數據拷進去,然后返回

send阻塞
  • 如果此時 socket 是非阻塞的,程序就會立刻返回一個 EAGAIN 錯誤信息,意思是 ?Try again , 現在緩沖區滿了,你也別等了,待會再試一次。

send非阻塞

我們可以簡單看下源碼是怎么實現的。還是回到剛才的 tcp_sendmsg 發送方法中。

int?tcp_sendmsg() {??if?(skb_availroom(skb)?>?0)?{//?..如果有足夠緩沖區就執行balabla}?else?{//?如果發送緩沖區沒空間了,那就等到有空間,至于等的方式,分阻塞和非阻塞if?((err?=?sk_stream_wait_memory(sk,?&timeo))?!=?0)goto?do_error;}??? }????????

里面提到的 ?sk_stream_wait_memory 會根據socket是否阻塞來決定是一直等等一會就返回。

int?sk_stream_wait_memory(struct?sock?*sk,?long?*timeo_p) {while?(1)?{//?非阻塞模式時,會等到超時返回?EAGAINif?(等待超時))return?-EAGAIN;?????//?阻塞等待時,會等到發送緩沖區有足夠的空間了,才跳出if?(sk_stream_memory_free(sk)?&&?!vm_wait)break;}return?err; }

如果接收緩沖區為空,執行 recv 會怎么樣?

接收緩沖區也是類似的情況。

當接收緩沖區為空,如果還向socket執行 recv

  • 如果此時 socket 是阻塞的,那么程序會在那干等,直到接收緩沖區有數據,就會把數據從接收緩沖區拷貝到用戶緩沖區,然后返回

recv阻塞
  • 如果此時 socket 是非阻塞的,程序就會立刻返回一個 EAGAIN 錯誤信息。

recv非阻塞

下面用一張圖匯總一下,方便大家保存面試的時候用哈哈哈。

socket讀寫緩沖區滿了的情況匯總

如果socket緩沖區還有數據,執行close了,會怎么樣?

首先我們要知道,一般正常情況下,發送緩沖區和接收緩沖區 都應該是空的。

如果發送、接收緩沖區長時間非空,說明有數據堆積,這往往是由于一些網絡問題或用戶應用層問題,導致數據沒有正常處理。

那么正常情況下,如果 socket 緩沖區為空,執行 close。就會觸發四次揮手。

TCP四次揮手

這個也是面試老八股文內容了,這里我們只需要關注第一次揮手,發的是 FIN 就夠了

如果接收緩沖區有數據時,執行close了,會怎么樣?

socket close 時,主要的邏輯在 tcp_close() 里實現。

先說結論,關閉過程主要有兩種情況:

  • 如果接收緩沖區還有數據未讀,會先把接收緩沖區的數據清空,然后給對端發一個RST。

  • 如果接收緩沖區是空的,那么就調用 tcp_send_fin() 開始進行四次揮手過程的第一次揮手。

void?tcp_close(struct?sock?*sk,?long?timeout) {//?如果接收緩沖區有數據,那么清空數據while?((skb?=?__skb_dequeue(&sk->sk_receive_queue))?!=?NULL)?{u32?len?=?TCP_SKB_CB(skb)->end_seq?-?TCP_SKB_CB(skb)->seq?-tcp_hdr(skb)->fin;data_was_unread?+=?len;__kfree_skb(skb);}if?(data_was_unread)?{//?如果接收緩沖區的數據被清空了,發?RSTtcp_send_active_reset(sk,?sk->sk_allocation);}?else?if?(tcp_close_state(sk))?{//?正常四次揮手,?發?FINtcp_send_fin(sk);}//?等待關閉sk_stream_wait_close(sk,?timeout); } recvbuf非空

如果發送緩沖區有數據時,執行close了,會怎么樣?

以前以為在這種情況下內核會把發送緩沖區數據清空,然后四次揮手。

但是發現源碼并不是這樣的

void?tcp_send_fin(struct?sock?*sk) {//?獲得發送緩沖區的最后一塊數據struct?sk_buff?*skb,?*tskb?=?tcp_write_queue_tail(sk);struct?tcp_sock?*tp?=?tcp_sk(sk);//?如果發送緩沖區還有數據if?(tskb?&&?(tcp_send_head(sk)?||?sk_under_memory_pressure(sk)))?{TCP_SKB_CB(tskb)->tcp_flags?|=?TCPHDR_FIN;?//?把最后一塊數據值為?FIN?TCP_SKB_CB(tskb)->end_seq++;tp->write_seq++;}??else?{//?發送緩沖區沒有數據,就造一個FIN包}//?發送數據__tcp_push_pending_frames(sk,?tcp_current_mss(sk),?TCP_NAGLE_OFF); }

此時,還有些數據沒發出去,內核會把發送緩沖區最后一個數據塊拿出來。然后置為 FIN。

socket 緩沖區是個先進先出的隊列,這種情況是指內核會等待TCP層安靜把發送緩沖區數據都發完,最后再執行四次揮手的第一次揮手(FIN包)。

有一點需要注意的是,只有在接收緩沖區為空的前提下,我們才有可能走到 tcp_send_fin() 。而只有在進入了這個方法之后,我們才有可能考慮發送緩沖區是否為空的場景。

sendbuf非空

UDP部分

UDP也有緩沖區嗎

說完TCP了,我們聊聊UDP。這對好基友,同時都是傳輸層里的重要協議。既然前面提到TCP有發送、接收緩沖區,那UDP有嗎?

以前我以為:

"每個UDP socket都有一個接收緩沖區,沒有發送緩沖區,從概念上來說就是只要有數據就發,不管對方是否可以正確接收,所以不緩沖,不需要發送緩沖區。"

后來我發現我錯了。

UDP socket 也是 socket,一個socket 就是會有收和發兩個緩沖區,跟用什么協議關系不大。

有沒有是一回事,用不用又是一回事。

UDP不用發送緩沖區?

事實上,UDP不僅有發送緩沖區,也用發送緩沖區。

一般正常情況下,會把數據直接拷到發送緩沖區后直接發送。

還有一種情況,是在發送數據的時候,設置一個 MSG_MORE 的標記。

ssize_t?send(int?sock,?const?void?*buf,?size_t?len,?int?flags);?//?flag?置為?MSG_MORE

大概的意思是告訴內核,待會還有其他更多消息要一起發,先別著急發出去。此時內核就會把這份數據先用發送緩沖區緩存起來,待會應用層說ok了,再一起發。

我們可以看下源碼。

int?udp_sendmsg() {// corkreq 為 true 表示是 MSG_MORE 的方式,僅僅組織報文,不發送;int?corkreq = up->corkflag || msg->msg_flags&MSG_MORE;//??將要發送的數據,按照MTU大小分割,每個片段一個skb;并且這些//? skb會放入到套接字的發送緩沖區中;該函數只是組織數據包,并不執行發送動作。err?=?ip_append_data(sk,?fl4,?getfrag,?msg->msg_iov,?ulen,sizeof(struct?udphdr),?&ipc,?&rt,corkreq???msg->msg_flags|MSG_MORE?:?msg->msg_flags);//?沒有啟用 MSG_MORE 特性,那么直接將發送隊列中的數據發送給IP。?if?(!corkreq)err?=?udp_push_pending_frames(sk);}

因此,不管是不是 MSG_MORE, IP都會先把數據放到發送隊列中,然后根據實際情況再考慮是不是立刻發送。

而我們大部分情況下,都不會用 ?MSG_MORE,也就是來一個數據包就直接發一個數據包。從這個行為上來說,雖然UDP用上了發送緩沖區,但實際上并沒有起到"緩沖"的作用。

最后

這篇文章,我也就寫了20個小時吧。畫圖也就畫吐了而已,每天早上7點鐘爬起來寫一個多小時再去上班。

歡迎點贊、在看、關注【小白debug】

總結

以上是生活随笔為你收集整理的动画图解 socket 缓冲区的那些事儿的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。