今天又是被傾盆的需求淹沒的一天。 有沒有人知道,那種“
我用3句話,就讓產(chǎn)品為我砍了18個需求 ”的雞湯課在哪報名,想報。"
聽懂掌聲 "的那種課就算了,太費手了。
扯遠了,回到我們今天的正題,我們了解下這篇文的目錄。
目錄 代碼 執(zhí)行
se nd成功后,數(shù)據(jù)就發(fā)出去了嗎?回答這個問題之前,需要了解什么是
Socket 緩沖區(qū) 。
Socket 緩沖區(qū) 什么是 socket 緩沖區(qū) 編程的時候,如果要跟某個IP建立連接,我們需要調(diào)用操作系統(tǒng)提供的
socket API。
socket 在操作系統(tǒng)層面,可以理解為一個
文件 。我們可以對這個文件進行一些
方法操作 。用
listen方法,可以讓程序作為服務(wù)器
監(jiān)聽 其他客戶端的連接。用
connect,可以作為客戶端
連接 服務(wù)器。用
send或
write可以
發(fā)送 數(shù)據(jù),
recv或
read可以
接收 數(shù)據(jù)。在建立好連接之后,這個
socket 文件就像是遠端機器的
"代理人" 一樣。比如,如果我們想給遠端服務(wù)發(fā)點什么東西,那就只需要對這個文件執(zhí)行寫操作就行了。
socket_api 那寫到了這個文件之后,剩下的發(fā)送工作自然就是由操作系統(tǒng)
內(nèi)核 來完成了。既然是寫給操作系統(tǒng),那操作系統(tǒng)就需要
提供一個地方給用戶寫 。同理,接收消息也是一樣。這個地方就是
socket 緩沖區(qū) 。用戶
發(fā)送 消息的時候?qū)懡o send buffer(發(fā)送緩沖區(qū))用戶
接收 消息的時候?qū)懡o recv buffer(接收緩沖區(qū))也就是說
一個socket ,會帶有兩個緩沖區(qū) ,一個用于發(fā)送,一個用于接收。因為這是個先進先出的結(jié)構(gòu),有時候也叫它們
發(fā)送、接收隊列 。
一個socket有兩個緩沖區(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這上面表明了,這里有一個協(xié)議(Proto)類型為 TCP 的連接,同時還有本地(Local Address)和遠端(Foreign Address)的IP信息,狀態(tài)(State)是已連接。還有
Send-Q 是發(fā)送緩沖區(qū) ,下面的數(shù)字60是指,當前還有60 Byte在發(fā)送緩沖區(qū)中未發(fā)送。而
Recv-Q 代表接收緩沖區(qū) , 此時是空的,數(shù)據(jù)都被應(yīng)用進程接收干凈了。
TCP部分 【動圖緩沖區(qū)的收發(fā)流程,TCP執(zhí)行發(fā)收的流程】我們在使用TCP建立連接之后,一般會使用 send 發(fā)送數(shù)據(jù)。
int ?main (int ?argc,?char ?*argv[]) { ????//?創(chuàng)建socket ????sockfd=socket(AF_INET,SOCK_STREAM,?0 )) ????//?建立連接?? ????connect(sockfd,?服務(wù)器ip信息,?sizeof (server))?? ????//?執(zhí)行?send?發(fā)送消息 ????send(sockfd,str,sizeof (str),0 ))?? ????//?關(guān)閉?socket ????close(sockfd); ????return ?0 ; }上面是一段偽代碼,僅用于展示大概邏輯,我們在建立好連接后,一般會在代碼中執(zhí)行
send 方法。那么此時,消息就會被立刻發(fā)到對端機器嗎?
執(zhí)行 send 發(fā)送的字節(jié),會立馬發(fā)送嗎? 答案是不確定!執(zhí)行 send 之后,數(shù)據(jù)只是拷貝到了socket 緩沖區(qū)。至 什么時候會發(fā)數(shù)據(jù),發(fā)多少數(shù)據(jù),全聽操作系統(tǒng)安排。
tcp_sendmsg 邏輯 在用戶進程中,程序通過操作 socket 會從用戶態(tài)進入內(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ù)實際情況(比如擁塞窗口等)判斷是否要發(fā)數(shù)據(jù)。如果不發(fā)送數(shù)據(jù),那么此時直接返回。
如果緩沖區(qū)滿了會怎么辦 前面提到的情況里是,發(fā)送緩沖區(qū)有足夠的空間,可以用于拷貝待發(fā)送數(shù)據(jù)。
如果發(fā)送緩沖區(qū)空間不足,或者滿了,執(zhí)行發(fā)送,會怎么樣? 這里分兩種情況。首先,socket在創(chuàng)建的時候,是可以設(shè)置是
阻塞 的還是
非阻塞 的。
int ?s?=?socket(AF_INET,?SOCK_STREAM?|?SOCK_NONBLOCK,?IPPROTO_TCP);比如通過上面的
代碼 ,就可以將
socket 設(shè)置為
非阻塞 (
SOCK_NONBLOCK)。當發(fā)送緩沖區(qū)
滿了 ,如果還向socket執(zhí)行send
如果此時 socket 是阻塞的,那么程序會在那干等、死等 ,直到釋放出新的緩存空間,就繼續(xù)把數(shù)據(jù)拷進去,然后返回 。 send阻塞 如果此時 socket 是非阻塞的,程序就會立刻返回 一個 EAGAIN 錯誤信息,意思是 ?Try again , 現(xiàn)在緩沖區(qū)滿了,你也別等了,待會再試一次。 send非阻塞 我們可以簡單看下源碼是怎么實現(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,?