同 CPU、內存以及 I/O 一樣,網絡也是 Linux 系統最核心的功能。網絡是一種把不同計算機或網絡設備連接到一起的技術,它本質上是一種進程間通信方式,特別是跨系統的進程間通信,必須要通過網絡才能進行。
網絡模型
多臺服務器通過網卡、交換機、路由器等網絡設備連接到一起,構成了相互連接的網絡。由于網絡設備的異構性和網絡協議的復雜性,國際標準化組織定義了一個七層的 OSI 網絡模型,但是這個模型過于復雜,實際工作中的事實標準,是更為實用的 TCP/IP 模型。
在計算機網絡時代初期,各大廠商推出了不同的網絡架構和標準,為統一標準,國際標準化組織 ISO 推出了統一的 OSI?開放式系統互聯通信參考模型(Open System Interconnection Reference Model)。
網絡分層解決了網絡復雜的問題,在網絡中傳輸數據中,我們對不同設備之間的傳輸數據的格式,需要定義一個數據標準,所以就有了網絡協議。
為了解決網絡互聯中異構設備的兼容性問題,并解耦復雜的網絡包處理流程,OSI 模型把網絡互聯的框架分為應用層、表示層、會話層、傳輸層、網絡層、數據鏈路層以及物理層等七層,每個層負責不同的功能。其中,
??應用層,負責為應用程序提供統一的接口。
??表示層,負責把數據轉換成兼容接收系統的格式。
??會話層,負責維護計算機之間的通信連接。
??傳輸層,負責為數據加上傳輸表頭,形成數據包。
??網絡層,負責數據的路由和轉發。
??數據鏈路層,負責 MAC 尋址、錯誤偵測和改錯。
??物理層,負責在物理網絡中傳輸數據幀。
但是 OSI 模型還是太復雜了,也沒能提供一個可實現的方法。所以,在 Linux 中,實際上使用的是另一個更實用的四層模型,即 TCP/IP 網絡模型。
TCP/IP 模型,把網絡互聯的框架分為應用層、傳輸層、網絡層、網絡接口層等四層,其中,
??應用層,負責向用戶提供一組應用程序,比如 HTTP、FTP、DNS 等。
??傳輸層,負責端到端的通信,比如 TCP、UDP 等。
??網絡層,負責網絡包的封裝、尋址和路由,比如 IP、ICMP 等。
??網絡接口層,負責網絡包在物理網絡中的傳輸,比如 MAC 尋址、錯誤偵測以及通過網卡傳輸網絡幀等。
TCP/IP 與 OSI 模型的關系如下圖:
雖說 Linux 實際按照 TCP/IP 模型,實現了網絡協議棧,但在平時的學習交流中,我們習慣上還是用 OSI 七層模型來描述。比如,說到七層和四層負載均衡,對應的分別是 OSI 模型中的應用層和傳輸層(而它們對應到 TCP/IP 模型中,實際上是四層和三層)。
Linux網絡棧
有了 TCP/IP 模型后,在進行網絡傳輸時,數據包就會按照協議棧,對上一層發來的數據進行逐層處理;然后封裝上該層的協議頭,再發送給下一層。
當然,網絡包在每一層的處理邏輯,都取決于各層采用的網絡協議。比如在應用層,一個提供 REST API 的應用,可以使用 HTTP 協議,把它需要傳輸的 JSON 數據封裝到 HTTP 協議中,然后向下傳遞給 TCP 層。
而封裝做的事情就很簡單了,只是在原來的負載前后,增加固定格式的元數據,原始的負載數據并不會被修改。
比如,以通過 TCP 協議通信的網絡包為例,通過下面這張圖,我們可以看到,應用程序數據在每個層的封裝格式。
其中:
??傳輸層在應用程序數據前面增加了 TCP 頭;
??網絡層在 TCP 數據包前增加了 IP 頭;
??而網絡接口層,又在 IP 數據包前后分別增加了幀頭和幀尾。
這些新增的頭部和尾部,增加了網絡包的大小,但我們都知道,物理鏈路中并不能傳輸任意大小的數據包。網絡接口配置的最大傳輸單元(MTU),就規定了最大的 IP 包大小。在我們最常用的以太網中,MTU 默認值是 1500bytes(這也是 Linux 的默認值)。
在Linux操作系統中執行 ifconfig 可以查看到每個網卡的mtu值,有1450、1500等不同的值。
[root@dev?~]#?ifconfig cni0:?flags=4163??mtu?1450 ????????inet?10.244.0.1??netmask?255.255.255.0??broadcast?10.244.0.255 ????????inet6?fe80:53ff638b??prefixlen?64??scopeid?0x20 ????????ether?665363:8b??txqueuelen?1000??(Ethernet) ????????RX?packets?124??bytes?12884?(12.5?KiB) ????????RX?errors?0??dropped?0??overruns?0??frame?0 ????????TX?packets?122??bytes?29636?(28.9?KiB) ????????TX?errors?0??dropped?0?overruns?0??carrier?0??collisions?0 docker0:?flags=4099 ??mtu?1500 ????????inet?172.17.0.1??netmask?255.255.0.0??broadcast?172.17.255.255 ????????ether?02129e:91??txqueuelen?0??(Ethernet) ????????RX?packets?0??bytes?0?(0.0?B) ????????RX?errors?0??dropped?0??overruns?0??frame?0 ????????TX?packets?0??bytes?0?(0.0?B) ????????TX?errors?0??dropped?0?overruns?0??carrier?0??collisions?0 ens33:?flags=4163 ??mtu?1500 ????????inet?192.168.2.129??netmask?255.255.255.0??broadcast?192.168.2.255 ????????inet6?fe80:989b8e3b??prefixlen?64??scopeid?0x20 ????????ether?00295e:32??txqueuelen?1000??(Ethernet) ????????RX?packets?131??bytes?13435?(13.1?KiB) ????????RX?errors?0??dropped?0??overruns?0??frame?0 ????????TX?packets?73??bytes?17977?(17.5?KiB) ????????TX?errors?0??dropped?0?overruns?0??carrier?0??collisions?0
一旦網絡包超過 MTU 的大小,就會在網絡層分片,以保證分片后的 IP 包不大于 MTU 值。顯然,MTU 越大,需要的分包也就越少,自然,網絡吞吐能力就越好。
理解了 TCP/IP 網絡模型和網絡包的封裝原理后,對Linux 內核中的網絡棧,其實也類似于 TCP/IP 的四層結構。如下圖所示,就是 Linux 通用 IP 網絡棧的示意圖:
從上到下來看這個網絡棧,你可以發現,
??最上層的應用程序,需要通過系統調用,來跟套接字接口進行交互;
??套接字的下面,就是我們前面提到的傳輸層、網絡層和網絡接口層;
??最底層,則是網卡驅動程序以及物理網卡設備。
網卡是發送和接收網絡包的基本設備。在系統啟動過程中,網卡通過內核中的網卡驅動程序注冊到系統中。而在網絡收發過程中,內核通過中斷跟網卡進行交互。
網絡包的處理非常復雜,所以,網卡硬中斷只處理最核心的網卡數據讀取或發送,而協議棧中的大部分邏輯,都會放到軟中斷中處理。
Linux網絡包收發流程
了解了 Linux 網絡棧后,我們再來看看, Linux 到底是怎么收發網絡包的。
PS:以下內容都以物理網卡為例。Linux 還支持眾多的虛擬網絡設備,而它們的網絡收發流程會有一些差別。
網絡包的接收流程
我們先來看網絡包的接收流程。
1. 當一個網絡幀到達網卡后,網卡會通過 DMA 方式,把這個網絡包放到收包隊列中;然后通過硬中斷,告訴中斷處理程序已經收到了網絡包。
2. 接著,網卡中斷處理程序會為網絡幀分配內核數據結構(sk_buff),并將其拷貝到 sk_buff 緩沖區中;然后再通過軟中斷,通知內核收到了新的網絡幀。
3. 接下來,內核協議棧從緩沖區中取出網絡幀,并通過網絡協議棧,從下到上逐層處理這個網絡幀。比如,
在鏈路層檢查報文的合法性,找出上層協議的類型( IPv4 還是 IPv6),再去掉幀頭、幀尾,然后交給網絡層。
1. 網絡層取出 IP 頭,判斷網絡包下一步的走向,比如是交給上層處理還是轉發。當網絡層確認這個包是要發送到本機后,就會取出上層協議的類型(TCP 還是 UDP),去掉 IP 頭,再交給傳輸層處理。
2. 傳輸層取出 TCP 頭或者 UDP 頭后,根據 < 源 IP、源端口、目的 IP、目的端口 > 四元組作為標識,找出對應的 Socket,并把數據拷貝到 Socket 的接收緩存中。
3. 最后,應用程序就可以使用 Socket 接口,讀取到新接收到的數據了。
具體過程如下圖所示,這張圖的左半部分表示接收流程,而圖中的粉色箭頭則表示網絡包的處理路徑。
網絡包的發送流程
網絡包的發送流程就是上圖的右半部分,很容易發現,網絡包的發送方向,正好跟接收方向相反。
首先,應用程序調用 Socket API(比如 sendmsg)發送網絡包。
由于這是一個系統調用,所以會陷入到內核態的套接字層中。套接字層會把數據包放到 Socket 發送緩沖區中。
接下來,網絡協議棧從 Socket 發送緩沖區中,取出數據包;再按照 TCP/IP 棧,從上到下逐層處理。比如,傳輸層和網絡層,分別為其增加 TCP 頭和 IP 頭,執行路由查找確認下一跳的 IP,并按照 MTU 大小進行分片。
分片后的網絡包,再送到網絡接口層,進行物理地址尋址,以找到下一跳的 MAC 地址。然后添加幀頭和幀尾,放到發包隊列中。這一切完成后,會有軟中斷通知驅動程序:發包隊列中有新的網絡幀需要發送。
最后,驅動程序通過 DMA ,從發包隊列中讀出網絡幀,并通過物理網卡把它發送出去。
在不同的網絡協議處理下,給我們的網絡數據包加上了各種頭部,這保證了網絡數據在各層物理設備的流轉下可以正確抵達目的地。收到處理后的網絡數據包后,接受端再通過網絡協議將頭部字段去除,得到原始的網絡數據。
下圖是客戶端與服務器之間用網絡協議連接通信的過程:
Linux 網絡根據 TCP/IP 模型,構建其網絡協議棧。TCP/IP 模型由應用層、傳輸層、網絡層、網絡接口層等四層組成,這也是 Linux 網絡棧最核心的構成部分。
應用程序通過套接字接口發送數據包時,先要在網絡協議棧中從上到下逐層處理,然后才最終送到網卡發送出去;而接收數據包時,也要先經過網絡棧從下到上的逐層處理,最后送到應用程序。
了解 Linux 網絡的基本原理和收發流程后,你肯定迫不及待想知道,如何去觀察網絡的性能情況。具體而言,哪些指標可以用來衡量 Linux 的網絡性能呢?
常用網絡相關命令
分析網絡問題的第一步,通常是查看網絡接口的配置和狀態。你可以使用 ifconfig 或者 ip 命令,來查看網絡的配置。
ifconfig 和 ip 分別屬于軟件包 net-tools 和 iproute2,iproute2 是 net-tools 的下一代,通常情況下它們會在發行版中默認安裝。
以網絡接口 ens33 為例,可以運行下面的兩個命令,查看它的配置和狀態:
?
?
[root@dev?~]#?ifconfig?ens33 ens33:?flags=4163??mtu?1500 ????????inet?192.168.2.129??netmask?255.255.255.0??broadcast?192.168.2.255 ????????inet6?fe80:989b8e3b??prefixlen?64??scopeid?0x20 ????????ether?00295e:32??txqueuelen?1000??(Ethernet) ????????RX?packets?249??bytes?22199?(21.6?KiB) ????????RX?errors?0??dropped?0??overruns?0??frame?0 ????????TX?packets?106??bytes?22636?(22.1?KiB) ????????TX?errors?0??dropped?0?overruns?0??carrier?0??collisions?0 [root@dev?~]# [root@dev?~]#?ip?-s?addr?show?ens33 2:?ens33:? ?mtu?1500?qdisc?pfifo_fast?state?UP?group?default?qlen?1000 ????link/ether?00295e:32?brd?ffffff:ff ????inet?192.168.2.129/24?brd?192.168.2.255?scope?global?noprefixroute?ens33 ???????valid_lft?forever?preferred_lft?forever ????inet6?fe80:989b8e3b/64?scope?link?noprefixroute ???????valid_lft?forever?preferred_lft?forever ????RX:?bytes??packets??errors??dropped?overrun?mcast ????24877??????279??????0???????0???????0???????0 ????TX:?bytes??packets??errors??dropped?carrier?collsns ????24616??????123??????0???????0???????0???????0
?
?
可以看到,ifconfig 和 ip 命令輸出的指標基本相同,只是顯示格式略微不同。比如,它們都包括了網絡接口的狀態標志、MTU 大小、IP、子網、MAC 地址以及網絡包收發的統計信息。
有幾個字段可以重點關注下:
第一,網絡接口的狀態標志。ifconfig 輸出中的 RUNNING ,或 ip 輸出中的 LOWER_UP ,都表示物理網絡是連通的,即網卡已經連接到了交換機或者路由器中。如果你看不到它們,通常表示網線被拔掉了。
第二,MTU 的大小。MTU 默認大小是 1500,根據網絡架構的不同(比如是否使用了 VXLAN 等疊加網絡),你可能需要調大或者調小 MTU 的數值。
第三,網絡接口的 IP 地址、子網以及 MAC 地址。這些都是保障網絡功能正常工作所必需的,你需要確保配置正確。
第四,網絡收發的字節數、包數、錯誤數以及丟包情況,特別是 TX ( Transmit發送 )和 RX(Receive接收 ) 部分的 errors、dropped、overruns、carrier 以及 collisions 等指標不為 0 時,通常表示出現了網絡問題。其中:
? errors 表示發生錯誤的數據包數,比如校驗錯誤、幀同步錯誤等;
? dropped 表示丟棄的數據包數,即數據包已經收到了 Ring Buffer,但因為內存不足等原因丟包;
? overruns 表示超限數據包數,即網絡 I/O 速度過快,導致 Ring Buffer 中的數據包來不及處理(隊列滿)而導致的丟包;
? carrier 表示發生 carrirer 錯誤的數據包數,比如雙工模式不匹配、物理電纜出現問題等;
? collisions 表示碰撞數據包數。
套接字信息
套接字接口在網絡程序功能中是內核與應用層之間的接口。TCP/IP 協議棧的所有數據和控制功能都來自于套接字接口,與 OSI 網絡分層模型相比,TCP/IP 協議棧本身在傳輸層以上就不包含任何其他協議。
在 Linux 操作系統中,替代傳輸層以上協議實體的標準接口,稱為套接字,它負責實現傳輸層以上所有的功能,可以說套接字是 TCP/IP 協議棧對外的窗口。
ifconfig 和 ip 只顯示了網絡接口收發數據包的統計信息,但在實際的性能問題中,網絡協議棧中的統計信息,我們也必須關注,可以用 netstat 或者 ss ,來查看套接字、網絡棧、網絡接口以及路由表的信息。
我個人更推薦,使用 ss 來查詢網絡的連接信息,因為它比 netstat 提供了更好的性能(速度更快)。
比如,你可以執行下面的命令,查詢套接字信息:
?
?
#?head?-n?4?表示只顯示前面4行 #?-l?表示只顯示監聽套接字 #?-n?表示顯示數字地址和端口(而不是名字) #?-p?表示顯示進程信息 [root@dev?~]#?netstat?-nlp?|?head?-n?4 Active?Internet?connections?(only?servers) Proto?Recv-Q?Send-Q?Local?Address????Foreign?Address?????????State???????PID/Program?name tcp????????0??????0?0.0.0.0:22??????????????0.0.0.0:*??????????LISTEN??????952/sshd tcp????????0??????0?127.0.0.1:25????????????0.0.0.0:*??????????LISTEN??????11/master #?-l?表示只顯示監聽套接字 #?-t?表示只顯示?TCP?套接字 #?-n?表示顯示數字地址和端口(而不是名字) #?-p?表示顯示進程信息 $?ss?-ltnp?|?head?-n?4
?
?
netstat 和 ss 的輸出也是類似的,都展示了套接字的狀態、接收隊列、發送隊列、本地地址、遠端地址、進程 PID 和進程名稱等。
其中,接收隊列(Recv-Q)和發送隊列(Send-Q)需要你特別關注,它們通常應該是 0。當你發現它們不是 0 時,說明有網絡包的堆積發生。當然還要注意,在不同套接字狀態下,它們的含義不同。
當套接字處于連接狀態(Established)時,
? Recv-Q 表示套接字緩沖還沒有被應用程序取走的字節數(即接收隊列長度)。
? Send-Q 表示還沒有被遠端主機確認的字節數(即發送隊列長度)。
當套接字處于監聽狀態(Listening)時,
? Recv-Q 表示全連接隊列的長度。
? Send-Q 表示全連接隊列的最大長度。
所謂全連接,是指服務器收到了客戶端的 ACK,完成了 TCP 三次握手,然后就會把這個連接挪到全連接隊列中。這些全連接中的套接字,還需要被 accept() 系統調用取走,服務器才可以開始真正處理客戶端的請求。
與全連接隊列相對應的,還有一個半連接隊列。所謂半連接是指還沒有完成 TCP 三次握手的連接,連接只進行了一半。服務器收到了客戶端的 SYN 包后,就會把這個連接放到半連接隊列中,然后再向客戶端發送 SYN+ACK 包。
連接統計信息
類似的,使用 netstat 或 ss ,也可以查看協議棧的信息:
?
?
[root@dev?~]#?netstat?-s ... Tcp: ????1898?active?connections?openings ????1502?passive?connection?openings ????24?failed?connection?attempts ????1304?connection?resets?received ????178?connections?established ????133459?segments?received ????133428?segments?send?out ????22?segments?retransmited ????0?bad?segments?received. ????1400?resets?sent ... [root@dev?~]#?ss?-s Total:?1700?(kernel?2499) TCP:???340?(estab?178,?closed?144,?orphaned?0,?synrecv?0,?timewait?134/0),?ports?0 Transport?Total?????IP????????IPv6 *?????????2499??????-?????????- RAW???????1?????????0?????????1 UDP???????5?????????3?????????2 TCP???????196???????179???????17 INET??????202???????182???????20 FRAG??????0?????????0?????????0
?
?
這些協議棧的統計信息都很直觀。ss 只顯示已經連接、關閉、孤兒套接字等簡要統計,而 netstat 則提供的是更詳細的網絡協議棧信息,展示了 TCP 協議的主動連接、被動連接、失敗重試、發送和接收的分段數量等各種信息。
連通性和延時
通常使用 ping ,來測試遠程主機的連通性和延時,而ping基于 ICMP 協議。比如,執行下面的命令,你就可以測試本機到 192.168.2.129 這個 IP 地址的連通性和延時:
?
?
[root@dev?~]#?ping?-c3?192.168.2.129 PING?192.168.2.129?(192.168.2.129)?56(84)?bytes?of?data. 64?bytes?from?192.168.2.129:?icmp_seq=1?ttl=64?time=0.026?ms 64?bytes?from?192.168.2.129:?icmp_seq=2?ttl=64?time=0.016?ms 64?bytes?from?192.168.2.129:?icmp_seq=3?ttl=64?time=0.015?ms ---?192.168.2.129?ping?statistics?--- 3?packets?transmitted,?3?received,?0%?packet?loss,?time?1998ms rtt?min/avg/max/mdev?=?0.015/0.019/0.026/0.005?ms
?
?
ping 的輸出,可以分為兩部分。
1. 第一部分,是每個 ICMP 請求的信息,包括 ICMP 序列號(icmp_seq)、TTL(生存時間,或者跳數)以及往返延時。
2. 第二部分,則是三次 ICMP 請求的匯總。
比如上面的示例顯示,發送了 3 個網絡包,并且接收到 3 個響應,沒有丟包發生,這說明測試主機到 192.168.2.129是連通的;平均往返延時(RTT)是 0.026 ms,也就是從發送 ICMP 開始,到接收到主機回復的確認,總共經歷的時間。
審核編輯:黃飛
?
評論