網絡世界中的數據表交互咱們肉眼是看不見的,它們就好像隱形了同樣,咱們對着課本學習計算機網絡的時候就會以爲很是的抽象,加大了學習的難度。web
還別說,我本身在大學的時候,也是如此。算法
直到工做後,認識了兩大分析網絡的利器:tcpdump 和 Wireshark,這兩大利器把咱們「看不見」的數據表,呈如今咱們眼前,一目瞭然。apache
唉,當初大學學習計網的時候,要是能知道這兩個工具,就不會學的一臉懵逼。緩存
tcpdump 和 Wireshark 有什麼區別?
tcpdump 和 Wireshark 就是最經常使用的網絡抓包和分析工具,更是分析網絡性能必不可少的利器。服務器
因此,這二者其實是搭配使用的,先用 tcpdump 命令在 Linux 服務器上抓包,接着把抓包的文件拖出到 Windows 電腦後,用 Wireshark 可視化分析。網絡
固然,若是你是在 Windows 上抓包,只須要用 Wireshark 工具就能夠。ssh
tcpdump 在 Linux 下如何抓包?
tcpdump 提供了大量的選項以及各式各樣的過濾表達式,來幫助你抓取指定的數據包,不過不要擔憂,只須要掌握一些經常使用選項和過濾表達式,就能夠知足大部分場景的須要了。curl
假設咱們要抓取下面的 ping 的數據包:tcp
要抓取上面的 ping 命令數據包,首先咱們要知道 ping 的數據包是 icmp協議,接着在使用 tcpdump 抓包的時候,就能夠指定只抓 icmp 協議的數據表:工具
那麼當 tcpdump 抓取到 icmp 數據包後, 輸出格式以下:
從 tcpdump 抓取的 icmp 數據包,咱們很清楚的看到 icmp echo 的交互過程了,首先發送方發起了 ICMP echo request 請求報文,接收方收到後回了一個 ICMP echo reply 相應報文,以後 seq 是遞增的。
我在這裏也幫你整理了一些最多見的用法,而且繪製成了表格,你能夠參考使用。
首先,先來看看經常使用的選項類,在上面的 ping 例子中,咱們用過 -i 選項指定網口,用過 -nn 選項不對 IP 地址和端口名稱解析。其它經常使用的選項,以下表格:
tcpdump 經常使用選項類
接下來,咱們再來看看經常使用的過濾表用法,在上面的 ping 例子中,咱們用過的是 icmp and host 183.232.231.174,表示抓取 icmp 協議的數據包,以及源地址或目標地址爲 183.232.231.174 的包。其餘經常使用的過濾選項,我也整理成了下面這個表格。
tcpdump 經常使用過濾表達式類
說了這麼多,你應該也發現了,tcpdump 雖然功能強大,可是輸出的格式並不直觀。
因此,在工做中 tcpdump 只是用來抓取數據包,不用來分析數據包,而是把 tcpdump 抓取的數據包保存成 pcap 後綴的文件,接着用 Wireshark 工具進行數據包分析。
Wireshark 工具如何分析數據包?
Wireshark 除了能夠抓包外,還提供了可視化分析網絡包的圖形頁面,同時,還內置了一系列的彙總分析工具。
好比,拿上面的 ping 例子來講,咱們可使用下面的命令,把抓取的數據包保存到 ping.pcap 文件
接着把 ping.pcap 文件拖到電腦,再用 Wireshark 打開它。打開後,你就能夠看到下面這個界面:
是吧?在 Wireshark 的頁面裏,能夠更加直觀的分析數據包,不只展現各個網絡包的頭部信息,還會用不一樣的顏色來區分不一樣的協議,因爲此次抓包只有 ICMP 協議,因此只有紫色的條目。
接着,在網絡包列表中選擇某一個網絡包後,在其下面的網絡包詳情中,能夠更清楚的看到,這個網絡包在協議棧各層的詳細信息。好比,以編號 1 的網絡包爲例子:
ping 網絡包
Wireshark 用了分層的方式,展現了各個層的包頭信息,把「不可見」的數據包,清清楚楚的展現了給咱們,還有理由學很差計算機網絡嗎?是不是相見恨晚?
從 ping 的例子中,咱們能夠看到網絡分層就像有序的分工,每一層都有本身的責任範圍和信息,上層協議完成工做後就交給下一層,最終造成一個完整的網絡包。
既然學會了 tcpdump 和 Wireshark 兩大網絡分析利器,那咱們馬不停蹄,接下用它倆抓取和分析 HTTP 協議網絡包,並理解 TCP 三次握手和四次揮手的工做原理。
本次例子,咱們將要訪問的 http://192.168.3.200 服務端。在終端一用 tcpdump 命令抓取數據包:
接着,在終端二執行下面的 curl 命令:
最後,回到終端一,按下 Ctrl+C 中止 tcpdump,並把獲得的 http.pcap 取出到電腦。
使用 Wireshark 打開 http.pcap 後,你就能夠在 Wireshark 中,看到以下的界面:
HTTP 網絡包
咱們都知道 HTTP 是基於 TCP 協議進行傳輸的,那麼:
Wireshark 能夠用時序圖的方式顯示數據包交互的過程,從菜單欄中,點擊 統計 (Statistics) -> 流量圖 (Flow Graph),而後,在彈出的界面中的「流量類型」選擇 「TCP Flows」,你能夠更清晰的看到,整個過程當中 TCP 流的執行過程:
TCP 流量圖
你可能會好奇,爲何三次握手鍊接過程的 Seq 是 0 ?
其實是由於 Wireshark 工具幫咱們作了優化,它默認顯示的是序列號 seq 是相對值,而不是真實值。
若是你想看到實際的序列號的值,能夠右鍵菜單, 而後找到「協議首選項」,接着找到「Relative Seq」後,把它給取消,操做以下:
取消序列號相對值顯示
取消後,Seq 顯示的就是真實值了:
TCP 流量圖
可見,客戶端和服務端的序列號其實是不一樣的,序列號是一個隨機值。
這其實跟咱們書上看到的 TCP 三次握手和四次揮手很相似,做爲對比,你一般看到的 TCP 三次握手和四次揮手的流程,基本是這樣的:
TCP 三次握手和四次揮手的流程
爲何抓到的 TCP 揮手是三次,而不是書上說的四次?
由於服務器端收到客戶端的 FIN 後,服務器端同時也要關閉鏈接,這樣就能夠把 ACK 和 FIN 合併到一塊兒發送,節省了一個包,變成了「三次揮手」。
而一般狀況下,服務器端收到客戶端的 FIN 後,極可能還沒發送完數據,因此就會先回復客戶端一個 ACK 包,稍等一下子,完成全部數據包的發送後,纔會發送 FIN 包,這也就是四次揮手了。
以下圖,就是四次揮手的過程:
四次揮手
TCP 三次握手的過程相信你們都背的倒背如流,那麼你有沒有想過這三個異常狀況:
有的小夥伴可能說:「很簡單呀,包丟了就會重傳嘛。」
那我在繼續問你:
是否是啞口無言,沒法回答?
不知道不要緊,接下里我用三個實驗案例,帶你們一塊兒探究探究這三種異常。
本次實驗用了兩臺虛擬機,一臺做爲服務端,一臺做爲客戶端,它們的關係以下:
實驗環境
爲了模擬 TCP 第一次握手 SYN 丟包的狀況,我是在拔掉服務器的網線後,馬上在客戶端執行 curl 命令:
其間 tcpdump 抓包的命令以下:
過了一會, curl 返回了超時鏈接的錯誤:
從 date 返回的時間,能夠發如今超時接近 1 分鐘的時間後,curl 返回了錯誤。
接着,把 tcp_sys_timeout.pcap 文件用 Wireshark 打開分析,顯示以下圖:
SYN 超時重傳五次
從上圖能夠發現, 客戶端發起了 SYN 包後,一直沒有收到服務端的 ACK ,因此一直超時重傳了 5 次,而且每次 RTO 超時時間是不一樣的:
能夠發現,每次超時時間 RTO 是指數(翻倍)上漲的,當超過最大重傳次數後,客戶端再也不發送 SYN 包。
在 Linux 中,第一次握手的 SYN 超時重傳次數,是以下內核參數指定的:
$ cat /proc/sys/net/ipv4/tcp_syn_retries
5
tcp_syn_retries 默認值爲 5,也就是 SYN 最大重傳次數是 5 次。
接下來,咱們繼續作實驗,把 tcp_syn_retries 設置爲 2 次:
$ echo 2 > /proc/sys/net/ipv4/tcp_syn_retries
重傳抓包後,用 Wireshark 打開分析,顯示以下圖:
SYN 超時重傳兩次
實驗一的實驗小結
經過實驗一的實驗結果,咱們能夠得知,當客戶端發起的 TCP 第一次握手 SYN 包,在超時時間內沒收到服務端的 ACK,就會在超時重傳 SYN 數據包,每次超時重傳的 RTO 是翻倍上漲的,直到 SYN 包的重傳次數到達tcp_syn_retries 值後,客戶端再也不發送 SYN 包。
SYN 超時重傳
爲了模擬客戶端收不到服務端第二次握手 SYN、ACK 包,個人作法是在客戶端加上防火牆限制,直接粗暴的把來自服務端的數據都丟棄,防火牆的配置以下:
接着,在客戶端執行 curl 命令:
從 date 返回的時間先後,能夠算出大概 1 分鐘後,curl 報錯退出了。
客戶端在這其間抓取的數據包,用 Wireshark 打開分析,顯示的時序圖以下:
從圖中能夠發現:
因此,咱們能夠發現,當第二次握手的 SYN、ACK 丟包時,客戶端會超時重發 SYN 包,服務端也會超時重傳 SYN、ACK 包。
咦?客戶端設置了防火牆,屏蔽了服務端的網絡包,爲何 tcpdump 還能抓到服務端的網絡包?
添加 iptables 限制後, tcpdump 是否能抓到包 ,這要看添加的 iptables 限制條件:
網絡包進入主機後的順序以下:
tcp_syn_retries 是限制 SYN 重傳次數,那第二次握手 SYN、ACK 限制最大重傳次數是多少?
TCP 第二次握手 SYN、ACK 包的最大重傳次數是經過tcp_synack_retries 內核參數限制的,其默認值以下:
$ cat /proc/sys/net/ipv4/tcp_synack_retries
5
是的,TCP 第二次握手 SYN、ACK 包的最大重傳次數默認值是 5 次。
爲了驗證 SYN、ACK 包最大重傳次數是 5 次,咱們繼續作下實驗,咱們先把客戶端的 tcp_syn_retries 設置爲 1,表示客戶端 SYN 最大超時次數是 1 次,目的是爲了防止屢次重傳 SYN,把服務端 SYN、ACK 超時定時器重置。
接着,仍是如上面的步驟:
把抓取的數據包,用 Wireshark 打開分析,顯示的時序圖以下:
從上圖,咱們能夠分析出:
接着,我把 tcp_synack_retries 設置爲 2,tcp_syn_retries 依然設置爲 1:
$ echo 2 > /proc/sys/net/ipv4/tcp_synack_retries
$ echo 1 > /proc/sys/net/ipv4/tcp_syn_retries
依然保持同樣的實驗步驟進行操做,接着把抓取的數據包,用 Wireshark 打開分析,顯示的時序圖以下:
可見:
實驗二的實驗小結
經過實驗二的實驗結果,咱們能夠得知,當 TCP 第二次握手 SYN、ACK 包丟了後,客戶端 SYN 包會發生超時重傳,服務端 SYN、ACK 也會發生超時重傳。
客戶端 SYN 包超時重傳的最大次數,是由 tcp_syn_retries 決定的,默認值是 5 次;服務端 SYN、ACK 包時重傳的最大次數,是由 tcp_synack_retries 決定的,默認值是 5 次。
爲了模擬 TCP 第三次握手 ACK 包丟,個人實驗方法是在服務端配置防火牆,屏蔽客戶端 TCP 報文中標誌位是 ACK 的包,也就是當服務端收到客戶端的 TCP ACK 的報文時就會丟棄,iptables 配置命令以下:
接着,在客戶端執行以下 tcpdump 命令:
而後,客戶端向服務端發起 telnet,由於 telnet 命令是會發起 TCP 鏈接,因此用此命令作測試:
此時,因爲服務端收不到第三次握手的 ACK 包,因此一直處於SYN_RECV 狀態:
而客戶端是已完成 TCP 鏈接創建,處於 ESTABLISHED 狀態:
過了 1 分鐘後,觀察發現服務端的 TCP 鏈接不見了:
過了 30 分別,客戶端依然仍是處於 ESTABLISHED 狀態:
接着,在剛纔客戶端創建的 telnet 會話,輸入 123456 字符,進行發送:
持續「好長」一段時間,客戶端的 telnet 才斷開鏈接:
以上就是本次的實現三的現象,這裏存在兩個疑點:
不着急,咱們把剛抓的數據包,用 Wireshark 打開分析,顯示的時序圖以下:
上圖的流程:
經過這一波分析,剛纔的兩個疑點已經解除了:
TCP 第一次握手的 SYN 包超時重傳最大次數是由 tcp_syn_retries 指定,TCP 第二次握手的 SYN、ACK 包超時重傳最大次數是由 tcp_synack_retries 指定,那 TCP 創建鏈接後的數據包最大超時重傳次數是由什麼參數指定呢?
TCP 創建鏈接後的數據包傳輸,最大超時重傳次數是由 tcp_retries2 指定,默認值是 15 次,以下:
$ cat /proc/sys/net/ipv4/tcp_retries2
15
若是 15 次重傳都作完了,TCP 就會告訴應用層說:「搞不定了,包怎麼都傳不過去!」
那若是客戶端不發送數據,何時纔會斷開處於 ESTABLISHED 狀態的鏈接?
這裏就須要提到 TCP 的 保護機制。這個機制的原理是這樣的:
定義一個時間段,在這個時間段內,若是沒有任何鏈接相關的活動,TCP 保活機制會開始做用,每隔一個時間間隔,發送一個「探測報文」,該探測報文包含的數據很是少,若是連續幾個探測報文都沒有獲得響應,則認爲當前的 TCP 鏈接已經死亡,系統內核將錯誤信息通知給上層應用程序。
在 Linux 內核能夠有對應的參數能夠設置保活時間、保活探測的次數、保活探測的時間間隔,如下都爲默認值:
net.ipv4.tcp_keepalive_time=7200
net.ipv4.tcp_keepalive_intvl=75
net.ipv4.tcp_keepalive_probes=9
也就是說在 Linux 系統中,最少須要通過 2 小時 11 分 15 秒才能夠發現一個「死亡」鏈接。
這個時間是有點長的,因此若是我抓包足夠久,或許能抓到探測報文。
實驗三的實驗小結
在創建 TCP 鏈接時,若是第三次握手的 ACK,服務端沒法收到,則服務端就會短暫處於 SYN_RECV 狀態,而客戶端會處於 ESTABLISHED 狀態。
因爲服務端一直收不到 TCP 第三次握手的 ACK,則會一直重傳 SYN、ACK 包,直到重傳次數超過 tcp_synack_retries 值(默認值 5 次)後,服務端就會斷開 TCP 鏈接。
而客戶端則會有兩種狀況:
客戶端在向服務端發起 HTTP GET 請求時,一個完整的交互過程,須要 2.5 個 RTT 的時延。
因爲第三次握手是能夠攜帶數據的,這時若是在第三次握手發起 HTTP GET 請求,須要 2 個 RTT 的時延。
可是在下一次(不是同個 TCP 鏈接的下一次)發起 HTTP GET 請求時,經歷的 RTT 也是同樣,以下圖:
常規 HTTP 請求
在 Linux 3.7 內核版本中,提供了 TCP Fast Open 功能,這個功能能夠減小 TCP 鏈接創建的時延。
常規 HTTP 請求 與 Fast Open HTTP 請求
注:客戶端在請求並存儲了 Fast Open Cookie 以後,能夠不斷重複 TCP Fast Open 直至服務器認爲 Cookie 無效(一般爲過時)
在 Linux 上如何打開 Fast Open 功能?
能夠經過設置 net.ipv4.tcp_fastopn 內核參數,來打開 Fast Open 功能。
net.ipv4.tcp_fastopn 各個值的意義:
TCP Fast Open 抓包分析
在下圖,數據包 7 號,客戶端發起了第二次 TCP 鏈接時,SYN 包會攜帶 Cooike,而且有長度爲 5 的數據。
服務端收到後,校驗 Cooike 合法,因而就回了 SYN、ACK 包,而且確認應答收到了客戶端的數據包,ACK = 5 + 1 = 6
TCP Fast Open 抓包分析
當接收方收到亂序數據包時,會發送重複的 ACK,以使告知發送方要重發該數據包,當發送方收到 3 個重複 ACK 時,就會觸發快速重傳,立該重發丟失數據包。
快速重傳機制
TCP 重複確認和快速重傳的一個案例,用 Wireshark 分析,顯示以下:
注意:快速重傳和重複 ACK 標記信息是 Wireshark 的功能,非數據包自己的信息。
以上案例在 TCP 三次握手時協商開啓了選擇性確認 SACK,所以一旦數據包丟失並收到重複 ACK ,即便在丟失數據包以後還成功接收了其餘數據包,也只須要重傳丟失的數據包。若是不啓用 SACK,就必須重傳丟失包以後的每一個數據包。
若是要支持 SACK,必須雙方都要支持。在 Linux 下,能夠經過net.ipv4.tcp_sack 參數打開這個功能(Linux 2.4 後默認打開)。
TCP 爲了防止發送方無腦的發送數據,致使接收方緩衝區被填滿,因此就有了滑動窗口的機制,它可利用接收方的接收窗口來控制發送方要發送的數據量,也就是流量控制。
接收窗口是由接收方指定的值,存儲在 TCP 頭部中,它能夠告訴發送方本身的 TCP 緩衝空間區大小,這個緩衝區是給應用程序讀取數據的空間:
接收窗口的大小,是在 TCP 三次握手中協商好的,後續數據傳輸時,接收方發送確認應答 ACK 報文時,會攜帶當前的接收窗口的大小,以此來告知發送方。
假設接收方接收到數據後,應用層能很快的從緩衝區裏讀取數據,那麼窗口大小會一直保持不變,過程以下:
理想狀態下的窗口變化
可是現實中服務器會出現繁忙的狀況,當應用程序讀取速度慢,那麼緩存空間會慢慢被佔滿,因而爲了保證發送方發送的數據不會超過緩衝區大小,則服務器會調整窗口大小的值,接着經過 ACK 報文通知給對方,告知如今的接收窗口大小,從而控制發送方發送的數據大小。
服務端繁忙狀態下的窗口變化
假設接收方處理數據的速度跟不上接收數據的速度,緩存就會被佔滿,從而致使接收窗口爲 0,當發送方接收到零窗口通知時,就會中止發送數據。
以下圖,能夠接收方的窗口大小在不斷的收縮至 0:
窗口大小在收縮
接着,發送方會定時發送窗口大小探測報文,以便及時知道接收方窗口大小的變化。
如下圖 Wireshark 分析圖做爲例子說明:
零窗口 與 窗口探測
能夠發現,這些窗口探測報文以 3.4s、6.5s、13.5s 的間隔出現,說明超時時間會翻倍遞增。
這鏈接暫停了 25s,想象一下你在打王者的時候,25s 的延遲你還能上王者嗎?
在 Wireshark 看到的 Windows size 也就是 " win = ",這個值表示發送窗口嗎?
這不是發送窗口,而是在向對方聲明本身的接收窗口。
你可能會好奇,抓包文件裏有「Window size scaling factor」,它實際上是算出實際窗口大小的乘法因子,「Windos size value」實際上並非真實的窗口大小,真實窗口大小的計算公式以下:
「Windos size value」 * 「Window size scaling factor」 = 「Caculated window size 」
對應的下圖案例,也就是 32 * 2048 = 65536。
其實是 Caculated window size 的值是 Wireshark 工具幫咱們算好的,Window size scaling factor 和 Windos size value 的值是在 TCP 頭部中,其中 Window size scaling factor 是在三次握手過程當中肯定的,若是你抓包的數據沒有 TCP 三次握手,那可能就沒法算出真實的窗口大小的值,以下圖:
如何在包裏看出發送窗口的大小?
很遺憾,沒有簡單的辦法,發送窗口雖然是由接收窗口決定,可是它又能夠被網絡因素影響,也就是擁塞窗口,實際上發送窗口是值是 min(擁塞窗口,接收窗口)。
發送窗口和 MSS 有什麼關係?
發送窗口決定了一口氣能發多少字節,而 MSS 決定了這些字節要分多少包才能發完。
舉個例子,若是發送窗口爲 16000 字節的狀況下,若是 MSS 是 1000 字節,那就須要發送 1600/1000 = 16 個包。
發送方在一個窗口發出 n 個包,是否是須要 n 個 ACK 確認報文?
不必定,由於 TCP 有累計確認機制,因此當收到多個數據包時,只須要應答最後一個數據包的 ACK 報文就能夠了。
當咱們 TCP 報文的承載的數據很是小的時候,例如幾個字節,那麼整個網絡的效率是很低的,由於每一個 TCP 報文中都有會 20 個字節的 TCP 頭部,也會有 20 個字節的 IP 頭部,而數據只有幾個字節,因此在整個報文中有效數據佔有的比重就會很是低。
這就好像快遞員開着大貨車送一個小包裹同樣浪費。
那麼就出現了常見的兩種策略,來減小小報文的傳輸,分別是:
Nagle 算法是如何避免大量 TCP 小數據報文的傳輸?
Nagle 算法作了一些策略來避免過多的小數據報文發送,這可提升傳輸效率。
Nagle 算法的策略:
只要沒知足上面條件中的一條,發送方一直在囤積數據,直到知足上面的發送條件。
禁用 Nagle 算法 與 啓用 Nagle 算法
上圖右側啓用了 Nagle 算法,它的發送數據的過程:
能夠看出,Nagle 算法必定會有一個小報文,也就是在最開始的時候。
另外,Nagle 算法默認是打開的,若是對於一些須要小數據包交互的場景的程序,好比,telnet 或 ssh 這樣的交互性比較強的程序,則須要關閉 Nagle 算法。
能夠在 Socket 設置 TCP_NODELAY 選項來關閉這個算法(關閉 Nagle 算法沒有全局參數,須要根據每一個應用本身的特色來關閉)。
關閉 Nagle 算法
那延遲確認又是什麼?
事實上當沒有攜帶數據的 ACK,它的網絡效率也是很低的,由於它也有 40 個字節的 IP 頭 和 TCP 頭,但沒有攜帶數據。
爲了解決 ACK 傳輸效率低問題,因此就衍生出了 TCP 延遲確認。
TCP 延遲確認的策略:
TCP 延遲確認
延遲等待的時間是在 Linux 內核中的定義的,以下圖:
關鍵就須要 HZ 這個數值大小,HZ 是跟系統的時鐘頻率有關,每一個操做系統都不同,在個人 Linux 系統中 HZ 大小是 1000,以下圖:
知道了 HZ 的大小,那麼就能夠算出:
TCP 延遲確承認以在 Socket 設置 TCP_QUICKACK 選項來關閉這個算法。
關閉 TCP 延遲確認
延遲確認 和 Nagle 算法混合使用時,會產生新的問題
當 TCP 延遲確認 和 Nagle 算法混合使用時,會致使時耗增加,以下圖:
TCP 延遲確認 和 Nagle 算法混合使用
發送方使用了 Nagle 算法,接收方使用了 TCP 延遲確認會發生以下的過程:
很明顯,這兩個同時使用會形成額外的時延,這就會使得網絡"很慢"的感受。
要解決這個問題,只有兩個辦法: