日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長提供免費收錄網(wǎng)站服務(wù),提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

搞了半天,終于弄懂了TCP Socket數(shù)據(jù)的接收和發(fā)送,太難

 

本文將從上層介紹linux上的TCP/IP棧是如何工作的,特別是socket系統(tǒng)調(diào)用和內(nèi)核數(shù)據(jù)結(jié)構(gòu)的交互、內(nèi)核和實際網(wǎng)絡(luò)的交互。寫這篇文章的部分原因是解釋監(jiān)聽隊列溢出(listen queue overflow)是如何工作的,因為它與我工作中一直在研究的一個問題相關(guān)。

建好的連接怎么工作

先從建好的連接開始介紹,稍后將解釋新建連接是如何工作的。

內(nèi)核管理的每一個TCP文件描述符都是一個struct, 它記錄TCP相關(guān)的信息(如序列號、當(dāng)前窗口大小等等),以及一個接收緩沖區(qū)(receive buffer,或者叫receive queue)和一個寫緩沖區(qū)(write buffer,或者叫write queue),后面我會交替使用術(shù)語buffer和queue。如果你對更多細節(jié)感興趣,可以在Linux內(nèi)核的net/sock.h中看到socket結(jié)構(gòu)的實現(xiàn)。

當(dāng)一個新的數(shù)據(jù)包進入網(wǎng)絡(luò)接口(NIC)時,通過被NIC中斷或通過輪詢NIC的方式通知內(nèi)核獲取數(shù)據(jù)。通常內(nèi)核是由中斷驅(qū)動還是處于輪詢模式取決于網(wǎng)絡(luò)通信量;當(dāng)NIC非常繁忙時,內(nèi)核輪詢效率更高,但如果NIC不繁忙,則可以使用中斷來節(jié)省CPU周期和電源。Linux稱這種技術(shù)為NAPI,字面意思是“新的api”。

當(dāng)內(nèi)核從NIC獲取數(shù)據(jù)包時,它會對數(shù)據(jù)包進行解碼,并根據(jù)源IP、源端口、目標(biāo)IP和目標(biāo)端口找出與該數(shù)據(jù)包相關(guān)聯(lián)的TCP連接。此信息用于查找與該連接關(guān)聯(lián)的內(nèi)存中的struct sock。假設(shè)數(shù)據(jù)包是按順序的到來的,那么數(shù)據(jù)有效負載就被復(fù)制到套接字的接收緩沖區(qū)中。此時,內(nèi)核將執(zhí)行read(2)或使用諸如select(2)或epoll_wait(2)等I/O多路復(fù)用方式系統(tǒng)調(diào)用,喚醒等待此套接字的進程。

當(dāng)用戶態(tài)的進程實際調(diào)用文件描述符上的read(2)時,它會導(dǎo)致內(nèi)核從其接收緩沖區(qū)中刪除數(shù)據(jù),并將該數(shù)據(jù)復(fù)制到此進程調(diào)用read(2)所提供的緩沖區(qū)中。

發(fā)送數(shù)據(jù)的工作原理類似。當(dāng)應(yīng)用程序調(diào)用write(2)時,它將數(shù)據(jù)從用戶提供的緩沖區(qū)復(fù)制到內(nèi)核寫入隊列中。隨后,內(nèi)核將把數(shù)據(jù)從寫隊列復(fù)制到NIC中,并實際發(fā)送數(shù)據(jù)。如果網(wǎng)絡(luò)繁忙,如果TCP發(fā)送窗口已滿,或者如果有流量整形策略等等,從用戶實際調(diào)用write(2)開始,到向NIC傳輸數(shù)據(jù)的實際時間可能會有所延遲。

這種設(shè)計的一個結(jié)果是,如果應(yīng)用程序讀取速度太慢或?qū)懭胨俣忍欤瑑?nèi)核的接收和寫入隊列可能會被填滿。因此,內(nèi)核為讀寫隊列設(shè)置最大大小。這樣可以確保行為不可控的應(yīng)用程序使用有限制的內(nèi)存量。例如,內(nèi)核可能會將每個接收和寫入隊列的大小限制在100KB。然后每個TCP套接字可以使用的最大內(nèi)核內(nèi)存量大約為200KB(因為與隊列的大小相比,其他TCP數(shù)據(jù)結(jié)構(gòu)的大小可以忽略不計)。

讀語義

如果接收緩沖區(qū)為空,并且用戶調(diào)用read(2),則系統(tǒng)調(diào)用將被阻塞,直到數(shù)據(jù)可用。

如果接收緩沖區(qū)是非空的,并且用戶調(diào)用read(2),系統(tǒng)調(diào)用將立即返回這些可用的數(shù)據(jù)。如果讀取隊列中準(zhǔn)備好的數(shù)據(jù)量小于用戶提供的緩沖區(qū)的大小,則可能發(fā)生部分讀取。調(diào)用方可以通過檢查read(2)的返回值來檢測到這一點。

如果接收緩沖區(qū)已滿,而TCP連接的另一端嘗試發(fā)送更多的數(shù)據(jù),內(nèi)核將拒絕對數(shù)據(jù)包進行ACK。這只是常規(guī)的TCP擁塞控制。

寫語義

如果寫入隊列未滿,并且用戶調(diào)用寫入,則系統(tǒng)調(diào)用將成功。如果寫入隊列有足夠的空間,則將復(fù)制所有數(shù)據(jù)。如果寫入隊列只有部分?jǐn)?shù)據(jù)的空間,那么將發(fā)生部分寫入,并且只有部分?jǐn)?shù)據(jù)將被復(fù)制到緩沖區(qū)。調(diào)用方通過檢查write(2)的返回值來檢查這一點。

如果寫入隊列已滿,并且用戶調(diào)用寫入write(2)),則系統(tǒng)調(diào)用將被阻塞。

新建連接的工作機制

在上一節(jié)中,我們看到了已建立的連接如何使用接收和寫入隊列來限制為每個連接分配的內(nèi)核內(nèi)存量。使用類似的技術(shù)也用來限制為新連接保留的內(nèi)核內(nèi)存量。

從用戶態(tài)的角度來看,新建立的TCP連接是通過在監(jiān)聽套接字上調(diào)用accept(2)來創(chuàng)建的。監(jiān)聽套接字是使用listen(2)系統(tǒng)調(diào)用的套接字。

accept(2)的原型采用一個套接字和兩個字段來存儲另一端套接字的信息。accept(2)返回的值是一個整數(shù),表示新建立連接的文件描述符:

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

listen(2)的原型采用了一個套接字文件描述符和一個backlog參數(shù):

int listen(int sockfd, int backlog);

backlog是一個參數(shù),當(dāng)用戶沒有足夠快地調(diào)用accept(2)時,它控制內(nèi)核將為新連接保留多少內(nèi)存。

例如,假設(shè)您有一個阻塞的單線程HTTP服務(wù)器,每個HTTP請求大約需要100毫秒。在這種情況下,HTTP服務(wù)器將花費100毫秒處理每個請求,然后才能再次調(diào)用accept(2)。這意味著在最多10個 rps 的情況下不會有排隊現(xiàn)象。如果內(nèi)核中有10個以上的 rps,則有兩個選擇。

內(nèi)核的第一個選擇是根本不接受連接。例如,內(nèi)核可以拒絕對傳入的SYN包進行ACK。更常見的情況是,內(nèi)核將完成TCP三次握手,然后使用RST終止連接。不管怎樣,結(jié)果都是一樣的:如果連接被拒絕,就不需要分配接收或?qū)懭刖彌_區(qū)。這樣做的理由是,如果用戶空間進程沒有足夠快地接受連接,那么正確的做法是使新請求失敗。反對這樣做的理由是,這太粗暴(aggressive),尤其是如果新的連接爆發(fā)(bursty)的時候。

內(nèi)核的第二個選擇是接受連接并為其分配一個套接字結(jié)構(gòu)(包括接收/寫入緩沖區(qū)),然后將套接字對象排隊以備以后使用。下次用戶調(diào)用accept(2)將立即獲得已分配的套接字, 而不是阻塞系統(tǒng)調(diào)用。

支持第二種方式的理由是,當(dāng)處理速率或連接速率趨向于爆發(fā)時,它過于“寬宏大量”。例如,在我們剛才描述的服務(wù)器中,假設(shè)有10個新連接同時出現(xiàn),然后這一秒中沒有更多的連接出現(xiàn)。如果內(nèi)核將新連接排隊,那么在第這一秒中所有的請求都會被處理。如果內(nèi)核采用拒絕新的連接的策略,那么即使進程本來能夠滿足請求速率的,也只有一個連接會成功。

不過有兩個反對排隊的論點。第一個問題是,過多的排隊會導(dǎo)致分配大量的內(nèi)核內(nèi)存。如果內(nèi)核正在分配帶有大接收緩沖區(qū)的數(shù)千個套接字,那么內(nèi)存使用量可能會快速增長,而用戶空間進程甚至可能無法處理所有這些請求。另一個反對排隊的論點是,它使應(yīng)用程序在連接的另一端(客戶機)看起來很慢。客戶機將看到它可以建立新的TCP連接,但是當(dāng)它嘗試使用它們時,服務(wù)器似乎響應(yīng)非常慢。所以建議在這種情況下,最好是讓新的連接失敗,因為這樣可以提供更明顯的服務(wù)器不正常的反饋。此外,如果服務(wù)器嚴(yán)重破壞了新的連接,客戶機就可以知道要退讓(back off);這是另一種擁塞控制形式。

監(jiān)聽隊列(listen queue)和溢出

正如您可能懷疑的那樣,內(nèi)核實際上結(jié)合了這兩種方法。內(nèi)核將會對新連接進行排隊,但只是一定數(shù)量的連接。內(nèi)核將排隊的連接數(shù)量由listen(2)的backlog參數(shù)控制。通常此值設(shè)置為相對較小的值。在Linux上,socket.h 將 somaxconn 的值設(shè)置為128,在kernel 2.4.25之前,這是允許的最大值。現(xiàn)在最大值是在/proc/sys/net/core/somaxconn中指定的,但是通常您會發(fā)現(xiàn)程序使用somaxconn(或更小的硬編碼值)。

當(dāng)監(jiān)聽隊列填滿時,新連接會被拒絕。這稱為監(jiān)聽隊列溢出。您可以通過讀取/proc/net/netstat并檢查ListenOverflows的值來觀察情況。這是整個內(nèi)核的全局計數(shù)器。據(jù)我所知,您無法獲得每個監(jiān)聽套接字的監(jiān)聽溢出統(tǒng)計信息。

在編寫網(wǎng)絡(luò)服務(wù)器時,監(jiān)控監(jiān)聽溢出非常重要,因為監(jiān)聽溢出不會從服務(wù)器的角度觸發(fā)任何用戶可見的行為。服務(wù)器將愉快地accept(2)每日的連接,而不返回任何連接被丟棄的跡象。例如,假設(shè)您為Python應(yīng)用程序使用Nginx作為代理服務(wù)器。

如果python應(yīng)用程序太慢,則可能導(dǎo)致nginx listen套接字溢出。當(dāng)發(fā)生這種情況時,您將在nginx日志中看不到任何關(guān)于這一點的指示,您將一直看到200狀態(tài)代碼,像往常一樣。因此,如果您只是監(jiān)視應(yīng)用程序的HTTP狀態(tài)代碼,您將無法看到阻止請求轉(zhuǎn)發(fā)到應(yīng)用程序的TCP錯誤。

分享到:
標(biāo)簽:TCP
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨大挑戰(zhàn)2018-06-03

數(shù)獨一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運動步數(shù)有氧達人2018-06-03

記錄運動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績評定2018-06-03

通用課目體育訓(xùn)練成績評定