公司門戶網(wǎng)站該怎么做ciliba最佳磁力搜索引擎
文章目錄
- 1.前言
- 2.Linux內(nèi)核中連接的組織形式
- 2.1套接字和文件描述符
- 2.2創(chuàng)建連接 & 獲取連接
- 3.全連接隊(duì)列
- 3.1為什么有全連接隊(duì)列?
- 3.2全連接隊(duì)列的長度
1.前言
TCP是面向連接的,TCP的各種可靠性機(jī)制實(shí)際都不是從主機(jī)到主機(jī)的,而是基于連接的。
比如一臺(tái)服務(wù)器啟動(dòng)后可能有多個(gè)客戶端前來訪問,如果TCP不是基于連接的,也就意味著服務(wù)器端只有一個(gè)接收緩沖區(qū),此時(shí)各個(gè)客戶端發(fā)來的數(shù)據(jù)都會(huì)拷貝到這個(gè)接收緩沖區(qū)當(dāng)中,此時(shí)這些數(shù)據(jù)就可能會(huì)互相干擾。
而我們?cè)谶M(jìn)行TCP通信之前需要先建立連接,就是因?yàn)門CP的各種可靠性保證都是基于連接的,要保證傳輸數(shù)據(jù)的可靠性的前提就是先建立好連接。
而一臺(tái)機(jī)器上可能會(huì)存在大量的連接,此時(shí)操作系統(tǒng)就不得不對(duì)這些連接進(jìn)行管理。
- 操作系統(tǒng)在管理這些連接時(shí)需要“先描述,再組織”,在操作系統(tǒng)中一定有一個(gè)描述連接的結(jié)構(gòu)體,該結(jié)構(gòu)體當(dāng)中包含了連接的各種屬性字段,所有定義出來的連接結(jié)構(gòu)體最終都會(huì)以某種數(shù)據(jù)結(jié)構(gòu)組織起來,此時(shí)操作系統(tǒng)對(duì)連接的管理就變成了對(duì)該數(shù)據(jù)結(jié)構(gòu)的增刪查改。
- 建立連接,實(shí)際就是在操作系統(tǒng)中用該結(jié)構(gòu)體定義一個(gè)結(jié)構(gòu)體變量,然后填充連接的各種屬性字段,最后將其插入到管理連接的數(shù)據(jù)結(jié)構(gòu)當(dāng)中即可。
- 斷開連接,實(shí)際就是將某個(gè)連接從管理連接的數(shù)據(jù)結(jié)構(gòu)當(dāng)中刪除,釋放該連接曾經(jīng)占用的各種資源。
以上都是理論層次的理解,那我們今天就具體地探究Linux源碼中連接是如何組織起來的:
2.Linux內(nèi)核中連接的組織形式
2.1套接字和文件描述符
網(wǎng)絡(luò)通信本質(zhì)上也是IO的過程,而且之前我們也說過調(diào)用send、recv等函數(shù)本質(zhì)上是向Tcp維護(hù)的發(fā)送緩沖區(qū)、接收緩沖區(qū)寫入和讀出數(shù)據(jù),既然是IO操作,所以一個(gè)套接字的本質(zhì)其實(shí)就是一個(gè)文件描述符對(duì)應(yīng)的文件。Linux下一切皆文件。
一個(gè)服務(wù)器本質(zhì)上就是一個(gè)進(jìn)程,而進(jìn)程到文件的關(guān)系我們?cè)缫呀?jīng)在系統(tǒng)部分學(xué)習(xí)過:
當(dāng)我們創(chuàng)建套接字時(shí),Linux系統(tǒng)還會(huì)為我們創(chuàng)建一個(gè)新的結(jié)構(gòu)體對(duì)象struct socket:

我們觀察到這個(gè)結(jié)構(gòu)體內(nèi)部包含了一個(gè)struct file類型的指針,該指針指向的就是該套接字對(duì)應(yīng)的文件對(duì)象,所以我們此時(shí)可以通過該套接字找到對(duì)應(yīng)的文件了,但是更重要的是我們需要通過文件描述符找到對(duì)應(yīng)的套接字呢呀,現(xiàn)在只有一個(gè)單向的指針,即我現(xiàn)在需要從文件找到對(duì)應(yīng)的套接字對(duì)象。
所以在struct file結(jié)構(gòu)體中還包含一個(gè)指針:

這個(gè)指針指向的就是套接字socket結(jié)構(gòu),所以我們現(xiàn)在就可以通過該文件描述符完成對(duì)套接字的操作了(讀取數(shù)據(jù)、獲取連接等)。
2.2創(chuàng)建連接 & 獲取連接
我們上面提到過連接本質(zhì)上是內(nèi)核中的一種數(shù)據(jù)結(jié)構(gòu),在Linux中實(shí)際就是struct tcp_sock
結(jié)構(gòu)體,該結(jié)構(gòu)體專門用于TCP協(xié)議。
它包含了TCP協(xié)議特有的字段和方法,如TCP頭部長度(tcp_header_len)、滑動(dòng)窗口(rcv_wnd、snd_wnd)、擁塞控制算法相關(guān)字段(如srtt_us、mdev_us等)以及發(fā)送和接收隊(duì)列等。
這個(gè)結(jié)構(gòu)體是TCP連接在內(nèi)核中的完整表示,包含了TCP協(xié)議運(yùn)行所需的所有狀態(tài)信息和控制邏輯。
struct tcp_sock
結(jié)構(gòu)體的第一個(gè)字段是struct inet_connection_sock
結(jié)構(gòu)體,該結(jié)構(gòu)體增加了與連接管理相關(guān)的字段,如連接狀態(tài)(icsk_state)、重傳機(jī)制等。這個(gè)結(jié)構(gòu)體為TCP連接提供了必要的狀態(tài)管理和控制機(jī)制。
struct inet_connection_sock
結(jié)構(gòu)體的第一個(gè)字段是struct inet_sock
結(jié)構(gòu)體,該結(jié)構(gòu)體增加了與IP層相關(guān)的字段和方法,如IP地址(sin_addr或sin6_addr)、端口號(hào)(sin_port或sin6_port)等。這個(gè)結(jié)構(gòu)體為TCP和UDP等基于IP的協(xié)議提供了更具體的支持。
struct inet_sock
結(jié)構(gòu)體的第一個(gè)字段是struct sock
結(jié)構(gòu)體,該結(jié)構(gòu)體包含了如套接字狀態(tài)(state)、接收和發(fā)送緩沖區(qū)(sk_buff)隊(duì)列、定時(shí)器(timer)等通用字段。
更重要的是你會(huì)發(fā)現(xiàn)與文件描述符相關(guān)的struct socket結(jié)構(gòu)體中有一個(gè)字段就是struct sock
類型的指針 sk:

所以socket套接字可以通過這個(gè) sk指針 獲取tcp_sock
結(jié)構(gòu)體中的所有字段內(nèi)容(通過類型轉(zhuǎn)換)。
比如想要獲取
tcp_sock
結(jié)構(gòu)體中inet_connection_sock
結(jié)構(gòu)體中的字段內(nèi)容,就可以將sk指針轉(zhuǎn)換成inet_connection_sock
類型獲取。這種通過一個(gè)指針獲取不同結(jié)構(gòu)體中屬性的方式被稱為“C風(fēng)格的多態(tài)”。
以上是Tcp連接,如果是Udp連接呢?我們說Udp是無連接的通信協(xié)議,所以對(duì)于Udp來說沒有struct inet_connection_sock
結(jié)構(gòu)體,因?yàn)樵摻Y(jié)構(gòu)體內(nèi)部維護(hù)的是與連接管理的相關(guān)字段,但是同樣的根據(jù)socket
結(jié)構(gòu)體中的 sk指針 指向udp_sock
結(jié)構(gòu)體來獲取udp連接的各種屬性內(nèi)容。所以 該socket結(jié)構(gòu)體 被稱為 “BSD socket ”— 通用socket接口。
既然socket結(jié)構(gòu)體既可以指向Tcp套接字又可以指向Udp套接字,那是如何區(qū)分不同套接字類型的呢?
int socket(int domain, int type, int protocol);
參數(shù)type對(duì)應(yīng)著socket結(jié)構(gòu)體中的type字段:
所以創(chuàng)建一個(gè)listen套接字的流程就是申請(qǐng)文件描述符獲得文件結(jié)構(gòu)體,創(chuàng)建套接字socket和連接tcp_sock,然后將他們關(guān)聯(lián)起來。
那么調(diào)用accept()函數(shù)是從listen套接字監(jiān)聽的套接字中獲取普通連接并返回,這個(gè)過程又是怎樣的呢?
實(shí)際上在struct inet_connection_sock
結(jié)構(gòu)體中維護(hù)一個(gè)全連接隊(duì)列,當(dāng)經(jīng)歷過三次握手后,系統(tǒng)會(huì)自動(dòng)創(chuàng)建一個(gè)連接tcp_sock,然后將該連接加入到全連接隊(duì)列中,當(dāng)調(diào)用accept()函數(shù)時(shí),操作系統(tǒng)會(huì)申請(qǐng)新的文件描述符和套接字socket,然后從全連接隊(duì)列中取出一個(gè)連接tcp_sock,之后普通套接字socket中的 sk指針 指向該連接tcp_sock,就完成了獲取連接的操作。
3.全連接隊(duì)列
實(shí)際TCP在進(jìn)行連接管理時(shí)會(huì)用到兩個(gè)連接隊(duì)列:
- 全連接隊(duì)列(accept隊(duì)列)。全連接隊(duì)列用于保存處于ESTABLISHED狀態(tài),但沒有被上層調(diào)用accept取走的連接。
- 半連接隊(duì)列。半連接隊(duì)列用于保存處于SYN_SENT和SYN_RCVD狀態(tài)的連接,也就是還未完成三次握手的連接,維護(hù)時(shí)間比較短。
而全連接隊(duì)列的長度實(shí)際會(huì)受到listen第二個(gè)參數(shù)的影響,一般TCP全連接隊(duì)列的長度就等于listen第二個(gè)參數(shù)backlog
的值加一。
int listen(int sockfd, int backlog);
如果將listen的第二個(gè)參數(shù)值設(shè)置為3,此時(shí)服務(wù)器端最多就允許存在4個(gè)處于ESTABLISHED狀態(tài)的連接。
在服務(wù)器端已經(jīng)有4個(gè)ESTABLISHED狀態(tài)的連接的情況下,再有客戶端發(fā)來建立連接請(qǐng)求,此時(shí)服務(wù)器端就會(huì)新增狀態(tài)為SYN_RCVD的連接,該連接實(shí)際就是放在半連接隊(duì)列當(dāng)中的。
3.1為什么有全連接隊(duì)列?
一般當(dāng)服務(wù)器壓力較大時(shí)連接隊(duì)列的作用才會(huì)體現(xiàn)出來,如果服務(wù)器壓力本身就不大,那么一旦底層有連接建立成功,上層就會(huì)立馬將該連接讀走并進(jìn)行處理。
服務(wù)器端啟動(dòng)時(shí)一般會(huì)預(yù)先創(chuàng)建多個(gè)服務(wù)線程為客戶端提供服務(wù),主線程從底層accept上來連接后就可以將其交給這些服務(wù)線程進(jìn)行處理。
如果向服務(wù)器發(fā)起連接請(qǐng)求的客戶端很少,那么連接一旦在底層建立好就被主線程立馬accept上來并交給服務(wù)線程處理了。
但如果向服務(wù)器發(fā)起連接請(qǐng)求的客戶端非常多并且業(yè)務(wù)處理非常繁忙,即當(dāng)每個(gè)服務(wù)線程都在為某個(gè)連接提供服務(wù)時(shí),底層再建立好連接主線程就不能獲取上來了,此時(shí)底層這些已經(jīng)建立好的連接就會(huì)被放到連接隊(duì)列當(dāng)中,只有等某個(gè)服務(wù)線程空閑時(shí),主線程就會(huì)從這個(gè)連接隊(duì)列當(dāng)中獲取建立好的連接。
如果沒有這個(gè)連接隊(duì)列,那么當(dāng)服務(wù)器端的服務(wù)線程都在提供服務(wù)時(shí),其他客戶端發(fā)來的連接請(qǐng)求就會(huì)直接被拒絕。
但有可能正當(dāng)這個(gè)連接請(qǐng)求被拒絕時(shí),某個(gè)服務(wù)線程提供服務(wù)完畢,此時(shí)這個(gè)服務(wù)線程就無法立馬得到一個(gè)連接為之提供服務(wù),所以一定有一段時(shí)間內(nèi)這個(gè)服務(wù)線程是處于閑置狀態(tài)的,直到再有客戶端發(fā)來連接請(qǐng)求。
而如果設(shè)置了連接隊(duì)列,當(dāng)某個(gè)服務(wù)線程提供完服務(wù)后,如果連接隊(duì)列當(dāng)中有建立好的連接,那么主線程就可以立馬從連接隊(duì)列當(dāng)中獲取一個(gè)連接交給該服務(wù)線程進(jìn)行處理,此時(shí)就可以保證服務(wù)器幾乎是滿載工作的,降低了服務(wù)器的閑置率。
3.2全連接隊(duì)列的長度
雖然維護(hù)連接隊(duì)列能讓服務(wù)器處于幾乎滿載工作的狀態(tài),但連接隊(duì)列也不能設(shè)置得太長。
- 如果隊(duì)列太長,也就意味著在隊(duì)列較尾部的連接需要等待較長時(shí)間才能得到服務(wù),此時(shí)客戶端的請(qǐng)求也就遲遲得不到響應(yīng)。
- 此外,服務(wù)器維護(hù)連接也是需要成本的,連接隊(duì)列設(shè)置的越長,系統(tǒng)就要花費(fèi)越多的成本去維護(hù)這個(gè)隊(duì)列。
- 但與其與其維護(hù)一個(gè)長連接,造成客戶端等待過久,并且占用大量暫時(shí)用不到的資源,還不如將部分資源節(jié)省出來給服務(wù)器使用,讓服務(wù)器更快的為客戶端提供服務(wù)。
所以全連接隊(duì)列要取一個(gè)合適的長度,系統(tǒng)一般設(shè)置為5。
全連接隊(duì)列的長度=min(backlog,net.core.somaxconn)+1:
- 用戶層調(diào)用listen時(shí)傳入的第二個(gè)參數(shù)backlog。
- 系統(tǒng)變量net.core.somaxconn,在 Linux 系統(tǒng)中,這個(gè)值默認(rèn)可能因不同的發(fā)行版和內(nèi)核版本而異,但常見的默認(rèn)值可能是 128。然而,對(duì)于高負(fù)載的服務(wù)器,特別是在處理大量并發(fā)連接時(shí),這個(gè)默認(rèn)值可能太低,導(dǎo)致新的連接被拒絕(因?yàn)楸O(jiān)聽隊(duì)列已滿)。
通過以下命令可以查看系統(tǒng)變量net.core.somaxconn的值。
sudo sysctl -a | grep net.core.somaxconn
Stay hungry, Stay foolish. —史蒂夫-喬布斯