如何解析Socket和TCP連接過程-創(chuàng)新互聯(lián)

如何解析Socket和TCP連接過程,很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細(xì)講解,有這方面需求的人可以來學(xué)習(xí)下,希望你能有所收獲。

成都創(chuàng)新互聯(lián)堅持“要么做到,要么別承諾”的工作理念,服務(wù)領(lǐng)域包括:成都做網(wǎng)站、網(wǎng)站設(shè)計、企業(yè)官網(wǎng)、英文網(wǎng)站、手機端網(wǎng)站、網(wǎng)站推廣等服務(wù),滿足客戶于互聯(lián)網(wǎng)時代的鹽田網(wǎng)站設(shè)計、移動媒體設(shè)計的需求,幫助企業(yè)找到有效的互聯(lián)網(wǎng)解決方案。努力成為您成熟可靠的網(wǎng)絡(luò)建設(shè)合作伙伴!

一. 背景

1.完整的套接字格式{protocol,src_addr,src_port,dest_addr,dest_port}。

這常被稱為套接字的五元組。其中protocol指定了是TCP還是UDP連接,其余的分別指定了源地址、源端口、目標(biāo)地址、目標(biāo)端口。但是這些內(nèi)容是怎么來的呢?

2.TCP協(xié)議棧維護(hù)著兩個socket緩沖區(qū):send buffer和recv buffer。

要通過TCP連接發(fā)送出去的數(shù)據(jù)都先拷貝到send buffer,可能是從用戶空間進(jìn)程的app buffer拷入的,也可能是從內(nèi)核的kernel buffer拷入的,拷入的過程是通過send()函數(shù)完成的,由于也可以使用write()函數(shù)寫入數(shù)據(jù),所以也把這個過程稱為寫數(shù)據(jù),相應(yīng)的send buffer也就有了別稱write buffer。不過send()函數(shù)比write()函數(shù)更有效率。

最終數(shù)據(jù)是通過網(wǎng)卡流出去的,所以send buffer中的數(shù)據(jù)需要拷貝到網(wǎng)卡中。由于一端是內(nèi)存,一端是網(wǎng)卡設(shè)備,可以直接使用DMA的方式進(jìn)行拷貝,無需CPU的參與。也就是說,send buffer中的數(shù)據(jù)通過DMA的方式拷貝到網(wǎng)卡中并通過網(wǎng)絡(luò)傳輸給TCP連接的另一端:接收端。

當(dāng)通過TCP連接接收數(shù)據(jù)時,數(shù)據(jù)肯定是先通過網(wǎng)卡流入的,然后同樣通過DMA的方式拷貝到recv buffer中,再通過recv()函數(shù)將數(shù)據(jù)從recv buffer拷入到用戶空間進(jìn)程的app buffer中。

3.兩種套接字:監(jiān)聽套接字和已連接套接字。

監(jiān)聽套接字是在服務(wù)進(jìn)程讀取配置文件時,從配置文件中解析出要監(jiān)聽的地址、端口,然后通過socket()函數(shù)創(chuàng)建的,然后再通過bind()函數(shù)將這個監(jiān)聽套接字綁定到對應(yīng)的地址和端口上。隨后,進(jìn)程/線程就可以通過listen()函數(shù)來監(jiān)聽這個端口(嚴(yán)格地說是監(jiān)控這個監(jiān)聽套接字)。

已連接套接字是在監(jiān)聽到TCP連接請求并三次握手后,通過accept()函數(shù)返回的套接字,后續(xù)進(jìn)程/線程就可以通過這個已連接套接字和客戶端進(jìn)行TCP通信。

為了區(qū)分socket()函數(shù)和accept()函數(shù)返回的兩個套接字描述符,有些人使用listenfd和connfd分別表示監(jiān)聽套接字和已連接套接字,挺形象的,下文偶爾也這么使用。

下面就來說明各種函數(shù)的作用,分析這些函數(shù),也是在連接、斷開連接的過程。

二. 連接的具體過程分析

2.1 socket()函數(shù)

socket()函數(shù)的作用就是生成一個用于通信的套接字文件描述符sockfd(socket() creates an endpoint for communication and returns a descriptor)。這個套接字描述符可以作為稍后bind()函數(shù)的綁定對象。

2.2 bind()函數(shù)

服務(wù)程序通過分析配置文件,從中解析出想要監(jiān)聽的地址和端口,再加上可以通過socket()函數(shù)生成的套接字sockfd,就可以使用bind()函數(shù)將這個套接字綁定到要監(jiān)聽的地址和端口組合"addr:port"上。綁定了端口的套接字可以作為listen()函數(shù)的監(jiān)聽對象。

綁定了地址和端口的套接字就有了源地址和源端口(對服務(wù)器自身來說是源),再加上通過配置文件中指定的協(xié)議類型,五元組中就有了其中3個元組。即:

{protocal,src_addr,src_port}

但是,常見到有些服務(wù)程序可以配置監(jiān)聽多個地址、端口實現(xiàn)多實例。這實際上就是通過多次socket()+bind()系統(tǒng)調(diào)用生成并綁定多個套接字實現(xiàn)的。

2.3 listen()函數(shù)和connect()函數(shù)

顧名思義,listen()函數(shù)就是監(jiān)聽已經(jīng)通過bind()綁定了addr+port的套接字的。監(jiān)聽之后,套接字就從CLOSE狀態(tài)轉(zhuǎn)變?yōu)長ISTEN狀態(tài),于是這個套接字就可以對外提供TCP連接的窗口了。

而connect()函數(shù)則用于向某個已監(jiān)聽的套接字發(fā)起連接請求,也就是發(fā)起TCP的三次握手過程。從這里可以看出,連接請求方(如客戶端)才會使用connect()函數(shù),當(dāng)然,在發(fā)起connect()之前,連接發(fā)起方也需要生成一個sockfd,且使用的很可能是綁定了隨機端口的套接字。既然connect()函數(shù)是向某個套接字發(fā)起連接的,自然在使用connect()函數(shù)時需要帶上連接的目的地,即目標(biāo)地址和目標(biāo)端口,這正是服務(wù)端的監(jiān)聽套接字上綁定的地址和端口。同時,它還要帶上自己的地址和端口,對于服務(wù)端來說,這就是連接請求的源地址和源端口。于是,TCP連接的兩端的套接字都已經(jīng)成了五元組的完整格式。

2.3.1 深入分析listen()

再來細(xì)說listen()函數(shù)。如果監(jiān)聽了多個地址+端口,即需要監(jiān)聽多個套接字,那么此刻負(fù)責(zé)監(jiān)聽的進(jìn)程/線程會采用select()、poll()的方式去輪詢這些套接字(當(dāng)然,也可以使用epoll()模式),其實只監(jiān)控一個套接字時,也是使用這些模式去輪詢的,只不過select()或poll()所感興趣的套接字描述符只有一個而已。

不管使用select()還是poll()模式(至于epoll的不同監(jiān)控方式就無需多言了),在進(jìn)程/線程(監(jiān)聽者)監(jiān)聽的過程中,它阻塞在select()或poll()上。直到有數(shù)據(jù)(SYN信息)寫入到它所監(jiān)聽的sockfd中(即recv buffer),監(jiān)聽者被喚醒并將SYN數(shù)據(jù)拷貝到用戶空間中自己管理的app buffer中進(jìn)行一番處理,并發(fā)送SYN+ACK,這個數(shù)據(jù)同樣需要從app buffer中拷入send buffer(使用send()函數(shù))中,再拷入網(wǎng)卡傳送出去。這時會在連接未完成隊列中為這個連接創(chuàng)建一個新項目,并設(shè)置為SYN_RECV狀態(tài)。然后再次使用select()/poll()方式監(jiān)控著套接字listenfd,直到再次有數(shù)據(jù)寫入這個listenfd中監(jiān)聽者才被喚醒,如果這次寫入的數(shù)據(jù)是ACK信息,則將數(shù)據(jù)拷入到app buffer中進(jìn)行一番處理后,把連接未完成隊列中對應(yīng)的項目移入連接已完成隊列,并設(shè)置為ESTABLISHED狀態(tài),如果這次接收的不是ACK,則肯定是SYN,也就是新的連接請求,于是和上面的處理過程一樣,放入連接未完成隊列。這就是監(jiān)聽者處理整個TCP連接的循環(huán)過程。

也就是說,listen()函數(shù)還維護(hù)了兩個隊列:連接未完成隊列和連接已完成隊列。當(dāng)監(jiān)聽者接收到某個客戶端發(fā)來的SYN并回復(fù)了SYN+ACK之后,就會在未完成連接隊列的尾部創(chuàng)建一個關(guān)于這個客戶端的條目,并設(shè)置它的狀態(tài)為SYN_RECV。顯然,這個條目中必須包含客戶端的地址和端口相關(guān)信息(可能是hash過的,我不太確定)。當(dāng)服務(wù)端再次收到這個客戶端發(fā)送的ACK信息之后,監(jiān)聽者線程通過分析數(shù)據(jù)就知道這個消息是回復(fù)給未完成連接隊列中的哪一項的,于是將這一項移入到已完成連接隊列,并設(shè)置它的狀態(tài)為ESTABLISHED。

當(dāng)未完成連接隊列滿了,監(jiān)聽者被阻塞不再接收新的連接請求,并通過select()/poll()等待兩個隊列觸發(fā)可寫事件。當(dāng)已完成連接隊列滿了,則監(jiān)聽者也不會接收新的連接請求,同時,正準(zhǔn)備移入到已完成連接隊列的動作被阻塞。在 Linux 2.2以前,listen()函數(shù)有一個backlog的參數(shù),用于設(shè)置這兩個隊列的大總長度,從Linux 2.2開始,這個參數(shù)只表示已完成隊列的大長度,而/proc/sys/net/ipv4/tcp_max_syn_backlog則用于設(shè)置未完成隊列的大長度。/proc/sys/net/core/somaxconn則是硬限制已完成隊列的大長度,默認(rèn)為128,如果backlog大于somaxconn,則backlog會被截斷為等于該值。

當(dāng)連接已完成隊列中的某個連接被accept()后,表示TCP連接已經(jīng)建立完成,這個連接將采用自己的socket buffer和客戶端進(jìn)行數(shù)據(jù)傳輸。這個socket buffer和監(jiān)聽套接字的socket buffer都是用來存儲TCP收、發(fā)的數(shù)據(jù),但它們的意義已經(jīng)不再一樣:監(jiān)聽套接字的socket buffer只接受TCP連接請求過程中的syn和ack數(shù)據(jù);而已建立的TCP連接的socket buffer主要存儲的內(nèi)容是兩端傳輸?shù)?quot;正式"數(shù)據(jù),例如服務(wù)端構(gòu)建的響應(yīng)數(shù)據(jù),客戶端發(fā)起的Http請求數(shù)據(jù)。

netstat 命令的Send-Q和Recv-Q列表示的就是socket buffer相關(guān)的內(nèi)容,以下是man netstat的解釋:
Recv-Q Established: The count of bytes not copied by the user program connected to this socket. Listening: Since Kernel 2.6.18 this column contains the current syn backlog.Send-Q Established: The count of bytes not acknowledged by the remote host. Listening: Since Kernel 2.6.18 this column contains the maximum size of the syn backlog.

對于監(jiān)聽狀態(tài)的套接字,Recv-Q表示的是當(dāng)前syn backlog,即已完成隊列中當(dāng)前的連接個數(shù),Send-Q表示的是syn backlog的大值,即已完成連接隊列的大連接限制個數(shù);

對于已經(jīng)建立的tcp連接,Recv-Q列表示的是recv buffer中還未被用戶進(jìn)程拷貝走的數(shù)據(jù)大小,Send-Q列表示的是遠(yuǎn)程主機還未返回ACK消息的數(shù)據(jù)大小。之所以區(qū)分已建立TCP連接的套接字和監(jiān)聽狀態(tài)的套接字,就是因為這兩種狀態(tài)的套接字采用不同的socket buffer,其中監(jiān)聽套接字更注重隊列的長度,而已建立TCP連接的套接字更注重收、發(fā)的數(shù)據(jù)大小。

[root@xuexi ~]# netstat -tnl
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.1:25            0.0.0.0:*               LISTEN     
tcp6       0      0 :::80                   :::*                    LISTEN     
tcp6       0      0 :::22                   :::*                    LISTEN     
tcp6       0      0 ::1:25                  :::*                    LISTEN
[root@xuexi ~]# ss -tnl
State      Recv-Q Send-Q                    Local Address:Port      Peer Address:Port
LISTEN     0      128                                   *:22                   *:*   
LISTEN     0      100                           127.0.0.1:25                   *:*   
LISTEN     0      128                                  :::80                  :::*   
LISTEN     0      128                                  :::22                  :::*   
LISTEN     0      100                                 ::1:25                  :::*

注意,Listen狀態(tài)下的套接字,netstat的Send-Q和ss 命令的Send-Q列的值不一樣,因為netstat根本就沒寫上已完成隊列的大長度。因此,判斷隊列中是否還有空閑位置接收新的tcp連接請求時,應(yīng)該盡可能地使用ss命令而不是netstat。

2.3.2 syn flood的影響

此外,如果監(jiān)聽者發(fā)送SYN+ACK后,遲遲收不到客戶端返回的ACK消息,監(jiān)聽者將被select()/poll()設(shè)置的超時時間喚醒,并對該客戶端重新發(fā)送SYN+ACK消息,防止這個消息遺失在茫茫網(wǎng)絡(luò)中。但是,這一重發(fā)就出問題了,如果客戶端調(diào)用connect()時偽造源地址,那么監(jiān)聽者回復(fù)的SYN+ACK消息是一定到不了對方的主機的,也就是說,監(jiān)聽者會遲遲收不到ACK消息,于是重新發(fā)送SYN+ACK。但無論是監(jiān)聽者因為select()/poll()設(shè)置的超時時間一次次地被喚醒,還是一次次地將數(shù)據(jù)拷入send buffer,這期間都是需要CPU參與的,而且send buffer中的SYN+ACK還要再拷入網(wǎng)卡(這次是DMA拷貝,不需要CPU)。如果,這個客戶端是個攻擊者,源源不斷地發(fā)送了數(shù)以千、萬計的SYN,監(jiān)聽者幾乎直接就崩潰了,網(wǎng)卡也會被阻塞的很嚴(yán)重。這就是所謂的syn flood攻擊。

解決syn flood的方法有多種,例如,縮小listen()維護(hù)的兩個隊列的大長度,減少重發(fā)syn+ack的次數(shù),增大重發(fā)的時間間隔,減少收到ack的等待超時時間,使用syncookie等,但直接修改tcp選項的任何一種方法都不能很好兼顧性能和效率。所以在連接到達(dá)監(jiān)聽者線程之前對數(shù)據(jù)包進(jìn)行過濾是極其重要的手段。

2.4 accept()函數(shù)

accpet()函數(shù)的作用是讀取已完成連接隊列中的第一項(讀完就從隊列中移除),并對此項生成一個用于后續(xù)連接的套接字描述符,假設(shè)使用connfd來表示。有了新的連接套接字,工作進(jìn)程/線程(稱其為工作者)就可以通過這個連接套接字和客戶端進(jìn)行數(shù)據(jù)傳輸,而前文所說的監(jiān)聽套接字(sockfd)則仍然被監(jiān)聽者監(jiān)聽。

例如,prefork模式的httpd,每個子進(jìn)程既是監(jiān)聽者,又是工作者,每個客戶端發(fā)起連接請求時,子進(jìn)程在監(jiān)聽時將它接收進(jìn)來,并釋放對監(jiān)聽套接字的監(jiān)聽,使得其他子進(jìn)程可以去監(jiān)聽這個套接字。多個來回后,終于是通過accpet()函數(shù)生成了新的連接套接字,于是這個子進(jìn)程就可以通過這個套接字專心地和客戶端建立交互,當(dāng)然,中途可能會因為各種io等待而多次被阻塞或睡眠。這種效率真的很低,僅僅考慮從子進(jìn)程收到SYN消息開始到最后生成新的連接套接字這幾個階段,這個子進(jìn)程一次又一次地被阻塞。當(dāng)然,可以將監(jiān)聽套接字設(shè)置為非阻塞IO模式,只是即使是非阻塞模式,它也要不斷地去檢查狀態(tài)。

再考慮worker/event處理模式,每個子進(jìn)程中都使用了一個專門的監(jiān)聽線程和N個工作線程。監(jiān)聽線程專門負(fù)責(zé)監(jiān)聽并建立新的連接套接字描述符,放入apache的套接字隊列中。這樣監(jiān)聽者和工作者就分開了,在監(jiān)聽的過程中,工作者可以仍然可以自由地工作。如果只從監(jiān)聽這一個角度來說,worker/event模式比prefork模式性能高的不是一點半點。

當(dāng)監(jiān)聽者發(fā)起accept()系統(tǒng)調(diào)用的時候,如果已完成連接隊列中沒有任何數(shù)據(jù),那么監(jiān)聽者會被阻塞。當(dāng)然,可將套接字設(shè)置為非阻塞模式,這時accept()在得不到數(shù)據(jù)時會返回EWOULDBLOCK或EAGAIN的錯誤??梢允褂胹elect()或poll()或epoll來等待已完成連接隊列的可讀事件。還可以將套接字設(shè)置為信號驅(qū)動IO模式,讓已完成連接隊列中新加入的數(shù)據(jù)通知監(jiān)聽者將數(shù)據(jù)復(fù)制到app buffer中并使用accept()進(jìn)行處理。

常聽到同步連接和異步連接的概念,它們到底是怎么區(qū)分的?同步連接的意思是,從監(jiān)聽者監(jiān)聽到某個客戶端發(fā)送的SYN數(shù)據(jù)開始,它必須一直等待直到建立連接套接字、并和客戶端數(shù)據(jù)交互結(jié)束,在和這個客戶端的連接關(guān)閉之前,中間不會接收任何其他客戶端的連接請求。細(xì)致一點解釋,那就是同步連接時需要保證socket buffer和app buffer數(shù)據(jù)保持一致。通常以同步連接的方式處理時,監(jiān)聽者和工作者是同一個進(jìn)程,例如httpd的prefork模型。而異步連接則可以在建立連接和數(shù)據(jù)交互的任何一個階段接收、處理其他連接請求。通常,監(jiān)聽者和工作者不是同一個進(jìn)程時使用異步連接的方式,例如httpd的event模型,盡管worker模型中監(jiān)聽者和工作者分開了,但是仍采用同步連接,監(jiān)聽者將連接請求接入并創(chuàng)建了連接套接字后,立即交給工作線程,工作線程處理的過程中一直只服務(wù)于該客戶端直到連接斷開,而event模式的異步也僅僅是在工作線程處理特殊的連接(如處于長連接狀態(tài)的連接)時,可以將它交給監(jiān)聽線程保管而已,對于正常的連接,它仍等價于同步連接的方式,因此httpd的event所謂異步,其實是偽異步。通俗而不嚴(yán)謹(jǐn)?shù)卣f,同步連接是一個進(jìn)程/線程處理一個連接,異步連接是一個進(jìn)程/線程處理多個連接。

2.5 send()和recv()函數(shù)

send()函數(shù)是將數(shù)據(jù)從app buffer復(fù)制到send buffer中(當(dāng)然,也可能直接從內(nèi)核的kernel buffer中復(fù)制),recv()函數(shù)則是將recv buffer中的數(shù)據(jù)復(fù)制到app buffer中。當(dāng)然,使用write()和read()函數(shù)替代它們并沒有什么不可以,只是send()/recv()的針對性更強而已。

這兩個函數(shù)都涉及到了socket buffer,但是在調(diào)用send()或recv()時,復(fù)制的源buffer中是否有數(shù)據(jù)、復(fù)制的目標(biāo)buffer中是否已滿而導(dǎo)致不可寫是需要考慮的問題。不管哪一方,只要不滿足條件,調(diào)用send()/recv()時進(jìn)程/線程會被阻塞(假設(shè)套接字設(shè)置為阻塞式IO模型)。當(dāng)然,可以將套接字設(shè)置為非阻塞IO模型,這時在buffer不滿足條件時調(diào)用send()/recv()函數(shù),調(diào)用函數(shù)的進(jìn)程/線程將返回錯誤狀態(tài)信息EWOULDBLOCK或EAGAIN。buffer中是否有數(shù)據(jù)、是否已滿而導(dǎo)致不可寫,其實可以使用select()/poll()/epoll去監(jiān)控對應(yīng)的文件描述符(對應(yīng)socket buffer則監(jiān)控該socket描述符),當(dāng)滿足條件時,再去調(diào)用send()/recv()就可以正常操作了。還可以將套接字設(shè)置為信號驅(qū)動IO或異步IO模型,這樣數(shù)據(jù)準(zhǔn)備好、復(fù)制好之前就不用再做無用功去調(diào)用send()/recv()了。

2.6 close()、shutdown()函數(shù)

通用的close()函數(shù)可以關(guān)閉一個文件描述符,當(dāng)然也包括面向連接的網(wǎng)絡(luò)套接字描述符。當(dāng)調(diào)用close()時,將會嘗試發(fā)送send buffer中的所有數(shù)據(jù)。但是close()函數(shù)只是將這個套接字引用計數(shù)減1,就像rm一樣,刪除一個文件時只是移除一個硬鏈接數(shù),只有這個套接字的所有引用計數(shù)都被刪除,套接字描述符才會真的被關(guān)閉,才會開始后續(xù)的四次揮手中。對于父子進(jìn)程共享套接字的并發(fā)服務(wù)程序,調(diào)用close()關(guān)閉子進(jìn)程的套接字并不會真的關(guān)閉套接字,因為父進(jìn)程的套接字還處于打開狀態(tài),如果父進(jìn)程一直不調(diào)用close()函數(shù),那么這個套接字將一直處于打開狀態(tài),見一直進(jìn)入不了四次揮手過程。

而shutdown()函數(shù)專門用于關(guān)閉網(wǎng)絡(luò)套接字的連接,和close()對引用計數(shù)減一不同的是,它直接掐斷套接字的所有連接,從而引發(fā)四次揮手的過程。可以指定3種關(guān)閉方式:

1.關(guān)閉寫。此時將無法向send buffer中再寫數(shù)據(jù),send buffer中已有的數(shù)據(jù)會一直發(fā)送直到完畢。

2.關(guān)閉讀。此時將無法從recv buffer中再讀數(shù)據(jù),recv buffer中已有的數(shù)據(jù)只能被丟棄。

3.關(guān)閉讀和寫。此時無法讀、無法寫,send buffer中已有的數(shù)據(jù)會發(fā)送直到完畢,但recv buffer中已有的數(shù)據(jù)將被丟棄。

無論是shutdown()還是close(),每次調(diào)用它們,在真正進(jìn)入四次揮手的過程中,它們都會發(fā)送一個FIN。

三. 地址/端口重用技術(shù)

正常情況下,一個addr+port只能被一個套接字綁定,換句話說,addr+port不能被重用,不同套接字只能綁定到不同的addr+port上。舉個例子,如果想要開啟兩個sshd實例,先后啟動的sshd實例配置文件中,必須不能配置同樣的addr+port。同理,配置web虛擬主機時,除非是基于域名,否則兩個虛擬主機必須不能配置同一個addr+port,而基于域名的虛擬主機能綁定同一個addr+port的原因是http的請求報文中包含主機名信息,實際上在這類連接請求到達(dá)的時候,仍是通過同一個套接字進(jìn)行監(jiān)聽的,只不過監(jiān)聽到之后,httpd的工作進(jìn)程/線程可以將這個連接分配到對應(yīng)的主機上。

既然上面說的是正常情況下,當(dāng)然就有非正常情況,也就是地址重用和端口重用技術(shù),組合起來就是套接字重用。在現(xiàn)在的Linux內(nèi)核中,已經(jīng)有支持地址重用的socket選項SO_REUSEADDR和支持端口重用的socket選項SO_REUSEPORT。設(shè)置了端口重用選項后,再去綁定套接字,就不會再有錯誤了。而且,一個實例綁定了兩個addr+port之后(可以綁定多個,此處以兩個為例),就可以同一時刻使用兩個監(jiān)聽進(jìn)程/線程分別去監(jiān)聽它們,客戶端發(fā)來的連接也就可以通過round-robin的均衡算流地被接待。

對于監(jiān)聽進(jìn)程/線程來說,每次重用的套接字被稱為監(jiān)聽桶(listener bucket),即每個監(jiān)聽套接字都是一個監(jiān)聽桶。

以httpd的worker或event模型為例,假設(shè)目前有3個子進(jìn)程,每個子進(jìn)程中都有一個監(jiān)聽線程和N個工作線程。

那么,在沒有地址重用的情況下,各個監(jiān)聽線程是爭搶式監(jiān)聽的。在某一時刻,這個監(jiān)聽套接字上只能有一個監(jiān)聽線程在監(jiān)聽(通過獲取互斥鎖mutex方式獲取監(jiān)聽資格),當(dāng)這個監(jiān)聽線程接收到請求后,讓出監(jiān)聽的資格,于是其他監(jiān)聽線程去搶這個監(jiān)聽資格,并只有一個線程可以搶的到。
當(dāng)使用了地址重用和端口重用技術(shù),就可以為同一個addr+port綁定多個套接字。例如下圖中是多使用一個監(jiān)聽桶時,有兩個套接字,于是有兩個監(jiān)聽線程可以同時進(jìn)行監(jiān)聽,當(dāng)某個監(jiān)聽線程接收到請求后,讓出資格,讓其他監(jiān)聽線程去爭搶資格。
如果再多綁定一個套接字,那么這三個監(jiān)聽線程都不用讓出監(jiān)聽資格,可以無限監(jiān)聽。
似乎感覺上去,性能很好,不僅減輕了監(jiān)聽資格(互斥鎖)的爭搶,避免"饑餓問題",還能更高效地監(jiān)聽,并因為可以負(fù)載均衡,從而可以減輕監(jiān)聽線程的壓力。但實際上,每個監(jiān)聽線程的監(jiān)聽過程都是需要消耗CPU的,如果只有一核CPU,即使重用了也體現(xiàn)不出重用的優(yōu)勢,反而因為切換監(jiān)聽線程而降低性能。因此,要使用端口重用,必須考慮是否已將各監(jiān)聽進(jìn)程/線程隔離在各自的cpu中,也就是說是否重用、重用幾次都需考慮cpu的核數(shù)以及是否將進(jìn)程與cpu相互綁定。

看完上述內(nèi)容是否對您有幫助呢?如果還想對相關(guān)知識有進(jìn)一步的了解或閱讀更多相關(guān)文章,請關(guān)注創(chuàng)新互聯(lián)-成都網(wǎng)站建設(shè)公司行業(yè)資訊頻道,感謝您對創(chuàng)新互聯(lián)的支持。

本文題目:如何解析Socket和TCP連接過程-創(chuàng)新互聯(lián)
網(wǎng)站地址:http://muchs.cn/article28/csjjcp.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站導(dǎo)航、網(wǎng)站制作、網(wǎng)站收錄做網(wǎng)站、用戶體驗、面包屑導(dǎo)航

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

成都網(wǎng)站建設(shè)公司