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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

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

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

先上這篇文章的目錄。

目錄

代碼執(zhí)行send成功后,數(shù)據(jù)就發(fā)出去了嗎?

回答這個(gè)問題之前,需要了解什么是Socket 緩沖區(qū)

Socket 緩沖區(qū)

什么是 socket 緩沖區(qū)

編程的時(shí)候,如果要跟某個(gè)IP建立連接,我們需要調(diào)用操作系統(tǒng)提供的 socket API。

socket 在操作系統(tǒng)層面,可以理解為一個(gè)文件。

我們可以對這個(gè)文件進(jìn)行一些方法操作

用listen方法,可以讓程序作為服務(wù)器監(jiān)聽其他客戶端的連接。

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

用send或write可以發(fā)送數(shù)據(jù),recv或read可以接收數(shù)據(jù)。

在建立好連接之后,這個(gè) socket 文件就像是遠(yuǎn)端機(jī)器的 "代理人" 一樣。比如,如果我們想給遠(yuǎn)端服務(wù)發(fā)點(diǎn)什么東西,那就只需要對這個(gè)文件執(zhí)行寫操作就行了。

socket_api

那寫到了這個(gè)文件之后,剩下的發(fā)送工作自然就是由操作系統(tǒng)內(nèi)核來完成了。

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

這個(gè)地方就是 socket 緩沖區(qū)。

用戶發(fā)送消息的時(shí)候?qū)懡o send buffer(發(fā)送緩沖區(qū))

用戶接收消息的時(shí)候?qū)懡o recv buffer(接收緩沖區(qū))

也就是說一個(gè)socket ,會帶有兩個(gè)緩沖區(qū),一個(gè)用于發(fā)送,一個(gè)用于接收。因?yàn)檫@是個(gè)先進(jìn)先出的結(jié)構(gòu),有時(shí)候也叫它們發(fā)送、接收隊(duì)列。

一個(gè)socket有兩個(gè)緩沖區(qū)

怎么觀察 socket 緩沖區(qū)

如果想要查看 socket 緩沖區(qū),可以在linux環(huán)境下執(zhí)行 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

這上面表明了,這里有一個(gè)協(xié)議(Proto)類型為 TCP 的連接,同時(shí)還有本地(Local Address)和遠(yuǎn)端(Foreign Address)的IP信息,狀態(tài)(State)是已連接。

還有Send-Q 是發(fā)送緩沖區(qū),下面的數(shù)字60是指,當(dāng)前還有60 Byte在發(fā)送緩沖區(qū)中未發(fā)送。而 Recv-Q 代表接收緩沖區(qū),此時(shí)是空的,數(shù)據(jù)都被應(yīng)用進(jìn)程接收干凈了。

TCP部分

我們在使用TCP建立連接之后,一般會使用 send 發(fā)送數(shù)據(jù)。

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

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

執(zhí)行 send 發(fā)送的字節(jié),會立馬發(fā)送嗎?

答案是不確定!執(zhí)行 send 之后,數(shù)據(jù)只是拷貝到了socket 緩沖區(qū)。至 于什么時(shí)候會發(fā)數(shù)據(jù),發(fā)多少數(shù)據(jù),全聽操作系統(tǒng)安排。

tcp_sendmsg 邏輯

在用戶進(jìn)程中,程序通過操作 socket 會從用戶態(tài)進(jìn)入內(nèi)核態(tài),而 send方法會將數(shù)據(jù)一路傳到傳輸層。在識別到是 TCP協(xié)議后,會調(diào)用 tcp_sendmsg 方法。

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

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

如果緩沖區(qū)滿了會怎么辦

前面提到的情況里是,發(fā)送緩沖區(qū)有足夠的空間,可以用于拷貝待發(fā)送數(shù)據(jù)。

如果發(fā)送緩沖區(qū)空間不足,或者滿了,執(zhí)行發(fā)送,會怎么樣?

這里分兩種情況。

首先,socket在創(chuàng)建的時(shí)候,是可以設(shè)置是阻塞的還是非阻塞的。

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

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

當(dāng)發(fā)送緩沖區(qū)滿了,如果還向socket執(zhí)行send

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

send阻塞
  • 如果此時(shí) socket 是非阻塞的,程序就會立刻返回一個(gè) EAGAIN 錯(cuò)誤信息,意思是 ?Try again , 現(xiàn)在緩沖區(qū)滿了,你也別等了,待會再試一次。

send非阻塞

我們可以簡單看下源碼是怎么實(shí)現(xiàn)的。還是回到剛才的 tcp_sendmsg 發(fā)送方法中。

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

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

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

如果接收緩沖區(qū)為空,執(zhí)行 recv 會怎么樣?

接收緩沖區(qū)也是類似的情況。

當(dāng)接收緩沖區(qū)為空,如果還向socket執(zhí)行 recv

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

recv阻塞
  • 如果此時(shí) socket 是非阻塞的,程序就會立刻返回一個(gè) EAGAIN 錯(cuò)誤信息。

recv非阻塞

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

socket讀寫緩沖區(qū)滿了的情況匯總

如果socket緩沖區(qū)還有數(shù)據(jù),執(zhí)行close了,會怎么樣?

首先我們要知道,一般正常情況下,發(fā)送緩沖區(qū)和接收緩沖區(qū) 都應(yīng)該是空的。

如果發(fā)送、接收緩沖區(qū)長時(shí)間非空,說明有數(shù)據(jù)堆積,這往往是由于一些網(wǎng)絡(luò)問題或用戶應(yīng)用層問題,導(dǎo)致數(shù)據(jù)沒有正常處理。

那么正常情況下,如果 socket 緩沖區(qū)為空,執(zhí)行 close。就會觸發(fā)四次揮手。

TCP四次揮手

這個(gè)也是面試?yán)习斯晌膬?nèi)容了,這里我們只需要關(guān)注第一次揮手,發(fā)的是 FIN 就夠了。

如果接收緩沖區(qū)有數(shù)據(jù)時(shí),執(zhí)行close了,會怎么樣?

socket close 時(shí),主要的邏輯在 tcp_close() 里實(shí)現(xiàn)。

先說結(jié)論,關(guān)閉過程主要有兩種情況:

  • 如果接收緩沖區(qū)還有數(shù)據(jù)未讀,會先把接收緩沖區(qū)的數(shù)據(jù)清空,然后給對端發(fā)一個(gè)RST。

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

void?tcp_close(struct?sock?*sk,?long?timeout) {//?如果接收緩沖區(qū)有數(shù)據(jù),那么清空數(shù)據(jù)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)?{//?如果接收緩沖區(qū)的數(shù)據(jù)被清空了,發(fā)?RSTtcp_send_active_reset(sk,?sk->sk_allocation);}?else?if?(tcp_close_state(sk))?{//?正常四次揮手,?發(fā)?FINtcp_send_fin(sk);}//?等待關(guān)閉sk_stream_wait_close(sk,?timeout); } recvbuf非空

如果發(fā)送緩沖區(qū)有數(shù)據(jù)時(shí),執(zhí)行close了,會怎么樣?

以前以為在這種情況下內(nèi)核會把發(fā)送緩沖區(qū)數(shù)據(jù)清空,然后四次揮手。

但是發(fā)現(xiàn)源碼并不是這樣的。

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

此時(shí),還有些數(shù)據(jù)沒發(fā)出去,內(nèi)核會把發(fā)送緩沖區(qū)最后一個(gè)數(shù)據(jù)塊拿出來。然后置為 FIN。

socket 緩沖區(qū)是個(gè)先進(jìn)先出的隊(duì)列,這種情況是指內(nèi)核會等待TCP層安靜把發(fā)送緩沖區(qū)數(shù)據(jù)都發(fā)完,最后再執(zhí)行四次揮手的第一次揮手(FIN包)。

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

sendbuf非空

UDP部分

UDP也有緩沖區(qū)嗎

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

以前我以為:

"每個(gè)UDP socket都有一個(gè)接收緩沖區(qū),沒有發(fā)送緩沖區(qū),從概念上來說就是只要有數(shù)據(jù)就發(fā),不管對方是否可以正確接收,所以不緩沖,不需要發(fā)送緩沖區(qū)。"

后來我發(fā)現(xiàn)我錯(cuò)了。

UDP socket 也是 socket,一個(gè)socket 就是會有收和發(fā)兩個(gè)緩沖區(qū),跟用什么協(xié)議關(guān)系不大。

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

UDP不用發(fā)送緩沖區(qū)?

事實(shí)上,UDP不僅有發(fā)送緩沖區(qū),也用發(fā)送緩沖區(qū)。

一般正常情況下,會把數(shù)據(jù)直接拷到發(fā)送緩沖區(qū)后直接發(fā)送。

還有一種情況,是在發(fā)送數(shù)據(jù)的時(shí)候,設(shè)置一個(gè) MSG_MORE 的標(biāo)記。

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

大概的意思是告訴內(nèi)核,待會還有其他更多消息要一起發(fā),先別著急發(fā)出去。此時(shí)內(nèi)核就會把這份數(shù)據(jù)先用發(fā)送緩沖區(qū)緩存起來,待會應(yīng)用層說ok了,再一起發(fā)。

我們可以看下源碼。

int?udp_sendmsg() {// corkreq 為 true 表示是 MSG_MORE 的方式,僅僅組織報(bào)文,不發(fā)送;int?corkreq = up->corkflag || msg->msg_flags&MSG_MORE;//??將要發(fā)送的數(shù)據(jù),按照MTU大小分割,每個(gè)片段一個(gè)skb;并且這些//? skb會放入到套接字的發(fā)送緩沖區(qū)中;該函數(shù)只是組織數(shù)據(jù)包,并不執(zhí)行發(fā)送動作。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 特性,那么直接將發(fā)送隊(duì)列中的數(shù)據(jù)發(fā)送給IP。?if?(!corkreq)err?=?udp_push_pending_frames(sk);}

因此,不管是不是 MSG_MORE, IP都會先把數(shù)據(jù)放到發(fā)送隊(duì)列中,然后根據(jù)實(shí)際情況再考慮是不是立刻發(fā)送。

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

最后

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

歡迎點(diǎn)贊、在看、關(guān)注【小白debug】

總結(jié)

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

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。