記得剛畢業找工做面試的時候,常常會被問到:你知道「3次握手,4次揮手」嗎?這時候我會「成竹在胸」地「背誦」前期準備好的「答案」,第一次怎麼怎麼,第二次……答完就沒有下文了,面試官貌似也沒有深刻下去的意思,深刻下去我也不懂,皆大歡喜!html
做爲程序員,要有「刨根問底」的精神。知其然,更要知其因此然。這篇文章但願能抽絲剝繭,還原背後的原理。linux
TCP是一種面向鏈接的單播協議,在發送數據前,通訊雙方必須在彼此間創建一條鏈接。所謂的「鏈接」,實際上是客戶端和服務器的內存裏保存的一份關於對方的信息,如ip地址、端口號等。git
TCP能夠當作是一種字節流,它會處理IP層或如下的層的丟包、重複以及錯誤問題。在鏈接的創建過程當中,雙方須要交換一些鏈接的參數。這些參數能夠放在TCP頭部。程序員
TCP提供了一種可靠、面向鏈接、字節流、傳輸層的服務,採用三次握手創建一個鏈接。採用4次揮手來關閉一個鏈接。github
在瞭解了創建鏈接、關閉鏈接的「三次握手和四次揮手」後,咱們再來看下TCP相關的東西。面試
一個TCP鏈接由一個4元組構成,分別是兩個IP地址和兩個端口號。一個TCP鏈接一般分爲三個階段:啓動、數據傳輸、退出(關閉)。redis
當TCP接收到另外一端的數據時,它會發送一個確認,但這個確認不會當即發送,通常會延遲一下子。ACK是累積的,一個確認字節號N的ACK表示全部直到N的字節(不包括N)已經成功被接收了。這樣的好處是若是一個ACK丟失,極可能後續的ACK就足以確認前面的報文段了。算法
一個完整的TCP鏈接是雙向和對稱的,數據能夠在兩個方向上平等地流動。給上層應用程序提供一種雙工服務
。一旦創建了一個鏈接,這個鏈接的一個方向上的每一個TCP報文段都包含了相反方向上的報文段的一個ACK。shell
序列號的做用是使得一個TCP接收端可丟棄重複的報文段,記錄以雜亂次序到達的報文段。由於TCP使用IP來傳輸報文段,而IP不提供重複消除或者保證次序正確的功能。另外一方面,TCP是一個字節流協議,毫不會以雜亂的次序給上層程序發送數據。所以TCP接收端會被迫先保持大序列號的數據不交給應用程序,直到缺失的小序列號的報文段被填滿。vim
源端口和目的端口在TCP層肯定雙方進程,序列號表示的是報文段數據中的第一個字節號,ACK表示確認號,該確認號的發送方期待接收的下一個序列號,即最後被成功接收的數據字節序列號加1,這個字段只有在ACK位被啓用的時候纔有效。
當新建一個鏈接時,從客戶端發送到服務端的第一個報文段的SYN位被啓用,這稱爲SYN報文段,這時序列號字段包含了在本次鏈接的這個方向上要使用的第一個序列號,即初始序列號ISN
,以後發送的數據是ISN加1,所以SYN位字段會消耗
一個序列號,這意味着使用重傳進行可靠傳輸。而不消耗序列號的ACK則不是。
頭部長度(圖中的數據偏移)以32位字爲單位,也就是以4bytes爲單位,它只有4位,最大爲15,所以頭部最大長度爲60字節,而其最小爲5,也就是頭部最小爲20字節(可變選項爲空)。
ACK —— 確認,使得確認號有效。
RST —— 重置鏈接(常常看到的reset by peer)就是此字段搞的鬼。
SYN —— 用於初如化一個鏈接的序列號。
FIN —— 該報文段的發送方已經結束向對方發送數據。
當一個鏈接被創建或被終止時,交換的報文段只包含TCP頭部,而沒有數據。
三次握手和四次揮手的狀態轉換以下圖。
換個易於理解的視角來看爲何要3次握手。
客戶端和服務端通訊前要進行鏈接,「3次握手」的做用就是雙方都能明確本身和對方的收、發能力是正常的
。
第一次握手
:客戶端發送網絡包,服務端收到了。這樣服務端就能得出結論:客戶端的發送能力、服務端的接收能力是正常的。
第二次握手
:服務端發包,客戶端收到了。這樣客戶端就能得出結論:服務端的接收、發送能力,客戶端的接收、發送能力是正常的。
從客戶端的視角來看,我接到了服務端發送過來的響應數據包,說明服務端接收到了我在第一次握手時發送的網絡包,而且成功發送了響應數據包,這就說明,服務端的接收、發送能力正常。而另外一方面,我收到了服務端的響應數據包,說明我第一次發送的網絡包成功到達服務端,這樣,我本身的發送和接收能力也是正常的。
第三次握手
:客戶端發包,服務端收到了。這樣服務端就能得出結論:客戶端的接收、發送能力,服務端的發送、接收能力是正常的。
第1、二次握手後,服務端並不知道客戶端的接收能力以及本身的發送能力是否正常。而在第三次握手時,服務端收到了客戶端對第二次握手做的迴應。從服務端的角度,我在第二次握手時的響應數據發送出去了,客戶端接收到了。因此,個人發送能力是正常的。而客戶端的接收能力也是正常的。
經歷了上面的三次握手過程,客戶端和服務端都確認了本身的接收、發送能力是正常的。以後就能夠正常通訊了。
每次都是接收到數據包的一方能夠獲得一些結論,發送的一方其實沒有任何頭緒。我雖然有發包的動做,可是我怎麼知道我有沒有發出去,而對方有沒有接收到呢?
而從上面的過程能夠看到,最少是須要三次握手過程的。兩次達不到讓雙方都得出本身、對方的接收、發送能力都正常的結論。其實每次收到網絡包的一方至少是能夠獲得:對方的發送、我方的接收是正常的。而每一步都是有關聯的,下一次的「響應」是因爲第一次的「請求」觸發,所以每次握手實際上是能夠獲得額外的結論的。好比第三次握手時,服務端收到數據包,代表看服務端只能獲得客戶端的發送能力、服務端的接收能力是正常的,可是結合第二次,說明服務端在第二次發送的響應包,客戶端接收到了,而且做出了響應,從而獲得額外的結論:客戶端的接收、服務端的發送是正常的。
用表格總結一下:
視角 | 客收 | 客發 | 服收 | 服發 |
---|---|---|---|---|
客視角 | 二 | 一 + 二 | 一 + 二 | 二 |
服視角 | 二 + 三 | 一 | 一 | 二 + 三 |
TCP鏈接是雙向傳輸的對等的模式,就是說雙方均可以同時向對方發送或接收數據。當有一方要關閉鏈接時,會發送指令告知對方,我要關閉鏈接了。這時對方會回一個ACK,此時一個方向的鏈接關閉。可是另外一個方向仍然能夠繼續傳輸數據,等到發送完了全部的數據後,會發送一個FIN段來關閉此方向上的鏈接。接收方發送ACK確認關閉鏈接。注意,接收到FIN報文的一方只能回覆一個ACK, 它是沒法立刻返回對方一個FIN報文段的,由於結束數據傳輸的「指令」是上層應用層給出的,我只是一個「搬運工」,我沒法瞭解「上層的意志」
。
其實3次握手的目的並不僅是讓通訊雙方都瞭解到一個鏈接正在創建,還在於利用數據包的選項來傳輸特殊的信息,交換初始序列號ISN。
3次握手是指發送了3個報文段,4次揮手是指發送了4個報文段。注意,SYN和FIN段都是會利用重傳進行可靠傳輸的。
這是由於服務端在LISTEN狀態下,收到創建鏈接請求的SYN報文後,把ACK和SYN放在一個報文裏發送給客戶端。而關閉鏈接時,當收到對方的FIN報文時,僅僅表示對方再也不發送數據了可是還能接收數據,己方是否如今關閉發送數據通道,須要上層應用來決定,所以,己方ACK和FIN通常都會分開發送。
三次握手的一個重要功能是客戶端和服務端交換ISN(Initial Sequence Number), 以便讓對方知道接下來接收數據的時候如何按序列號組裝數據。
若是ISN是固定的,攻擊者很容易猜出後續的確認號。
ISN = M + F(localhost, localport, remotehost, remoteport)
M是一個計時器,每隔4微秒加1。
F是一個Hash算法,根據源IP、目的IP、源端口、目的端口生成一個隨機數值。要保證hash算法不能被外部輕易推算得出。
由於ISN是隨機的,因此序列號容易就會超過2^31-1. 而tcp對於丟包和亂序等問題的判斷都是依賴於序列號大小比較的。此時就出現了所謂的tcp序列號迴繞(sequence wraparound)問題。怎麼解決?
/* * The next routines deal with comparing 32 bit unsigned ints * and worry about wraparound (automatic with unsigned arithmetic). */ static inline int before(__u32 seq1, __u32 seq2) { return (__s32)(seq1-seq2) < 0; } #define after(seq2, seq1) before(seq1, seq2)
上述代碼是內核中的解決迴繞問題代碼。__s32是有符號整型的意思,而__u32則是無符號整型。序列號發生迴繞後,序列號變小,相減以後,把結果變成有符號數了,所以結果成了負數。
假設seq1=255, seq2=1(發生了迴繞)。 seq1 = 1111 1111 seq2 = 0000 0001 咱們但願比較結果是 seq1 - seq2= 1111 1111 -0000 0001 ----------- 1111 1110 因爲咱們將結果轉化成了有符號數,因爲最高位是1,所以結果是一個負數,負數的絕對值爲 0000 0001 + 1 = 0000 0010 = 2 所以seq1 - seq2 < 0
最基本的DoS攻擊就是利用合理的服務請求來佔用過多的服務資源,從而使合法用戶沒法獲得服務的響應。syn flood屬於Dos攻擊的一種。
若是惡意的向某個服務器端口發送大量的SYN包,則可使服務器打開大量的半開鏈接,分配TCB(Transmission Control Block), 從而消耗大量的服務器資源,同時也使得正常的鏈接請求沒法被相應。當開放了一個TCP端口後,該端口就處於Listening狀態,不停地監視發到該端口的Syn報文,一 旦接收到Client發來的Syn報文,就須要爲該請求分配一個TCB,一般一個TCB至少須要280個字節,在某些操做系統中TCB甚至須要1300個字節,並返回一個SYN ACK命令,當即轉爲SYN-RECEIVED即半開鏈接狀態。系統會爲此耗盡資源。
常見的防攻擊方法有:
監視系統的半開鏈接和不活動鏈接,當達到必定閾值時拆除這些鏈接,從而釋放系統資源。這種方法對於全部的鏈接一視同仁,並且因爲SYN Flood形成的半開鏈接數量很大,正常鏈接請求也被淹沒在其中被這種方式誤釋放掉,所以這種方法屬於入門級的SYN Flood方法。
消耗服務器資源主要是由於當SYN數據報文一到達,系統當即分配TCB,從而佔用了資源。而SYN Flood因爲很難創建起正常鏈接,所以,當正常鏈接創建起來後再分配TCB則能夠有效地減輕服務器資源的消耗。常見的方法是使用Syn Cache和Syn Cookie技術。
系統在收到一個SYN報文時,在一個專用HASH表中保存這種半鏈接信息,直到收到正確的迴應ACK報文再分配TCB。這個開銷遠小於TCB的開銷。固然還須要保存序列號。
Syn Cookie技術則徹底不使用任何存儲資源,這種方法比較巧妙,它使用一種特殊的算法生成Sequence Number,這種算法考慮到了對方的IP、端口、己方IP、端口的固定信息,以及對方沒法知道而己方比較固定的一些信息,如MSS(Maximum Segment Size,最大報文段大小,指的是TCP報文的最大數據報長度,其中不包括TCP首部長度。)、時間等,在收到對方 的ACK報文後,從新計算一遍,看其是否與對方迴應報文中的(Sequence Number-1)相同,從而決定是否分配TCB資源。
一種方式是防止牆dqywb鏈接的有效性後,防火牆纔會向內部服務器發起SYN請求。防火牆代服務器發出的SYN ACK包使用的序列號爲c, 而真正的服務器迴應的序列號爲c', 這樣,在每一個數據報文通過防火牆的時候進行序列號的修改。另外一種方式是防火牆肯定了鏈接的安全後,會發出一個safe reset命令,client會進行從新鏈接,這時出現的syn報文會直接放行。這樣不須要修改序列號了。可是,client須要發起兩次握手過程,所以創建鏈接的時間將會延長。
在外部請求到達時,被服務程序最終感知到前,鏈接可能處於SYN_RCVD狀態或是ESTABLISHED狀態,但還未被應用程序接受。
對應地,服務器端也會維護兩種隊列,處於SYN_RCVD狀態的半鏈接隊列,而處於ESTABLISHED狀態但仍未被應用程序accept的爲全鏈接隊列。若是這兩個隊列滿了以後,就會出現各類丟包的情形。
查看是否有鏈接溢出 netstat -s | grep LISTEN
在三次握手協議中,服務器維護一個半鏈接隊列,該隊列爲每一個客戶端的SYN包開設一個條目(服務端在接收到SYN包的時候,就已經建立了request_sock結構,存儲在半鏈接隊列中),該條目代表服務器已收到SYN包,並向客戶發出確認,正在等待客戶的確認包。這些條目所標識的鏈接在服務器處於Syn_RECV狀態,當服務器收到客戶的確認包時,刪除該條目,服務器進入ESTABLISHED狀態。
目前,Linux下默認會進行5次重發SYN-ACK包,重試的間隔時間從1s開始,下次的重試間隔時間是前一次的雙倍,5次的重試時間間隔爲1s, 2s, 4s, 8s, 16s, 總共31s, 稱爲
指數退避
,第5次發出後還要等32s才知道第5次也超時了,因此,總共須要 1s + 2s + 4s+ 8s+ 16s + 32s = 63s, TCP纔會把斷開這個鏈接。因爲,SYN超時須要63秒,那麼就給攻擊者一個攻擊服務器的機會,攻擊者在短期內發送大量的SYN包給Server(俗稱SYN flood攻擊),用於耗盡Server的SYN隊列。對於應對SYN 過多的問題,linux提供了幾個TCP參數:tcp_syncookies、tcp_synack_retries、tcp_max_syn_backlog、tcp_abort_on_overflow 來調整應對。
參數 | 做用 |
---|---|
tcp_syncookies | SYNcookie將鏈接信息編碼在ISN(initialsequencenumber)中返回給客戶端,這時server不須要將半鏈接保存在隊列中,而是利用客戶端隨後發來的ACK帶回的ISN還原鏈接信息,以完成鏈接的創建,避免了半鏈接隊列被攻擊SYN包填滿。 |
tcp_syncookies | 內核放棄創建鏈接以前發送SYN包的數量。 |
tcp_synack_retries | 內核放棄鏈接以前發送SYN+ACK包的數量 |
tcp_max_syn_backlog | 默認爲1000. 這表示半鏈接隊列的長度,若是超過則放棄當前鏈接。 |
tcp_abort_on_overflow | 若是設置了此項,則直接reset. 不然,不作任何操做,這樣當服務器半鏈接隊列有空了以後,會從新接受鏈接。Linux堅持在能力許可範圍內不忽略進入的鏈接 。客戶端在這期間會重複發送sys包,當重試次數到達上限以後,會獲得connection time out 響應。 |
當第三次握手時,當server接收到ACK包以後,會進入一個新的叫 accept 的隊列。
當accept隊列滿了以後,即便client繼續向server發送ACK的包,也會不被響應,此時ListenOverflows+1,同時server經過tcp_abort_on_overflow來決定如何返回,0表示直接丟棄該ACK,1表示發送RST通知client;相應的,client則會分別返回read timeout
或者 connection reset by peer
。另外,tcp_abort_on_overflow是0的話,server過一段時間再次發送syn+ack給client(也就是從新走握手的第二步),若是client超時等待比較短,就很容易異常了。而客戶端收到多個 SYN ACK 包,則會認爲以前的 ACK 丟包了。因而促使客戶端再次發送 ACK ,在 accept隊列有空閒的時候最終完成鏈接。若 accept隊列始終滿員,則最終客戶端收到 RST 包(此時服務端發送syn+ack的次數超出了tcp_synack_retries)。
服務端僅僅只是建立一個定時器,以固定間隔重傳syn和ack到服務端
參數 | 做用 |
---|---|
tcp_abort_on_overflow | 若是設置了此項,則直接reset. 不然,不作任何操做,這樣當服務器半鏈接隊列有空了以後,會從新接受鏈接。Linux堅持在能力許可範圍內不忽略進入的鏈接 。客戶端在這期間會重複發送sys包,當重試次數到達上限以後,會獲得connection time out 響應。 |
min(backlog, somaxconn) | 全鏈接隊列的長度。 |
netstat -s命令
[root@server ~]# netstat -s | egrep "listen|LISTEN" 667399 times the listen queue of a socket overflowed 667399 SYNs to LISTEN sockets ignored
上面看到的 667399 times ,表示全鏈接隊列溢出的次數,隔幾秒鐘執行下,若是這個數字一直在增長的話確定全鏈接隊列偶爾滿了。
[root@server ~]# netstat -s | grep TCPBacklogDrop
查看 Accept queue 是否有溢出
ss命令
[root@server ~]# ss -lnt State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 128 *:6379 *:* LISTEN 0 128 *:22 *:*
若是State是listen狀態,Send-Q 表示第三列的listen端口上的全鏈接隊列最大爲50,第一列Recv-Q爲全鏈接隊列當前使用了多少。
非 LISTEN 狀態中 Recv-Q 表示 receive queue 中的 bytes 數量;Send-Q 表示 send queue 中的 bytes 數值。
當外部鏈接請求到來時,TCP模塊會首先查看max_syn_backlog,若是處於SYN_RCVD狀態的鏈接數目超過這一閾值,進入的鏈接會被拒絕。根據tcp_abort_on_overflow字段來決定是直接丟棄,仍是直接reset.
從服務端來講,三次握手中,第一步server接受到client的syn後,把相關信息放到半鏈接隊列中,同時回覆syn+ack給client. 第三步當收到客戶端的ack, 將鏈接加入到全鏈接隊列。
通常,全鏈接隊列比較小,會先滿,此時半鏈接隊列還沒滿。若是這時收到syn報文,則會進入半鏈接隊列,沒有問題。可是若是收到了三次握手中的第3步(ACK),則會根據tcp_abort_on_overflow字段來決定是直接丟棄,仍是直接reset.此時,客戶端發送了ACK, 那麼客戶端認爲三次握手完成,它認爲服務端已經準備好了接收數據的準備。但此時服務端可能由於全鏈接隊列滿了而沒法將鏈接放入,會從新發送第2步的syn+ack, 若是這時有數據到來,服務器TCP模塊會將數據存入隊列中。一段時間後,client端沒收到回覆,超時,鏈接異常,client會主動關閉鏈接。
tcpdump -w /tmp/a.cap port 6379 -s0 -w把數據寫入文件,-s0設置每一個數據包的大小默認爲68字節,若是用-S 0則會抓到完整數據包
tcpdump -r /tmp/a.cap -n -nn -A -x| vim - (-x 以16進制形式展現,便於後面分析)
共收到了7個包。
抓到的是IP數據包,IP數據包分爲IP頭部和IP數據部分,IP數據部分是TCP頭部加TCP數據部分。
IP的數據格式爲:
它由固定長度20B+可變長度構成。
10:55:45.662077 IP dev2.39070 > dev.6379: Flags [S], seq 4133153791, win 29200, options [mss 1460,sackOK,TS val 2959270704 ecr 0,nop,wscale 7], length 0 0x0000: 4500 003c 08cf 4000 3606 14a5 0ab3 b561 0x0010: 0a60 5cd4 989e 18eb f65a ebff 0000 0000 0x0020: a002 7210 872f 0000 0204 05b4 0402 080a 0x0030: b062 e330 0000 0000 0103 0307
對着IP頭部格式,來拆解數據包的具體含義。
字節值 | 字節含義 |
---|---|
0x4 | IP版本爲ipv4 |
0x5 | 首部長度爲5 * 4字節=20B |
0x00 | 服務類型,如今基本都置爲0 |
0x003c | 總長度爲3*16+12=60字節,上面全部的長度就是60字節 |
0x08cf | 標識。同一個數據報的惟一標識。當IP數據報被拆分時,會複製到每個數據中。 |
0x4000 | 3bit 標誌 + 13bit 片偏移 。3bit 標誌對應 R、DF、MF。目前只有後兩位有效,DF位:爲1表示不分片,爲0表示分片。MF:爲1表示「更多的片」,爲0表示這是最後一片。13bit 片位移:本分片在原先數據報文中相對首位的偏移位。(須要再乘以8 ) |
0x36 | 生存時間TTL。IP報文所容許經過的路由器的最大數量。每通過一個路由器,TTL減1,當爲 0 時,路由器將該數據報丟棄。TTL 字段是由發送端初始設置一個 8 bit字段.推薦的初始值由分配數字 RFC 指定。發送 ICMP 回顯應答時常常把 TTL 設爲最大值 255。TTL能夠防止數據報陷入路由循環。 此處爲54. |
0x06 | 協議類型。指出IP報文攜帶的數據使用的是哪一種協議,以便目的主機的IP層能知道要將數據報上交到哪一個進程。TCP 的協議號爲6,UDP 的協議號爲17。ICMP 的協議號爲1,IGMP 的協議號爲2。該 IP 報文攜帶的數據使用 TCP 協議,獲得了驗證。 |
0x14a5 | 16bitIP首部校驗和。 |
0x0ab3 b561 | 32bit源ip地址。 |
0x0a60 5cd4 | 32bit目的ip地址。 |
剩餘的數據部分即爲TCP協議相關的。TCP也是20B固定長度+可變長度部分。
字節值 | 字節含義 |
---|---|
0x989e | 16bit源端口。1161616+81616+1416+11=39070 |
0x18eb | 16bit目的端口6379 |
0xf65a ebff | 32bit序列號。4133153791 |
0x0000 0000 | 32bit確認號。 |
0xa | 4bit首部長度,以4byte爲單位。共10*4=40字節。所以TCP報文的可選長度爲40-20=20 |
0b000000 | 6bit保留位。目前置爲0. |
0b000010 | 6bitTCP標誌位。從左到右依次是緊急 URG、確認 ACK、推送 PSH、復位 RST、同步 SYN 、終止 FIN。 |
0x7210 | 滑動窗口大小,滑動窗口即tcp接收緩衝區的大小,用於tcp擁塞控制。29200 |
0x872f | 16bit校驗和。 |
0x0000 | 緊急指針。僅在 URG = 1時纔有意義,它指出本報文段中的緊急數據的字節數。當 URG = 1 時,發送方 TCP 就把緊急數據插入到本報文段數據的最前面,而在緊急數據後面的數據還是普通數據。 |
可變長度部分,協議以下:
字節值 | 字節含義 |
---|---|
0x0204 05b4 | 最大報文長度爲,05b4=1460. 便可接收的最大包長度,一般爲MTU減40字節,IP頭和TCP頭各20字節 |
0x0402 | 表示支持SACK |
0x080a b062 e330 0000 0000 | 時間戳。Ts val=b062 e330=2959270704, ecr=0 |
0x01 | 無操做 |
0x03 0307 | 窗口擴大因子爲7. 移位7, 乘以128 |
這樣第一個包分析完了。dev2向dev發送SYN請求。也就是三次握手中的第一次了。
SYN seq(c)=4133153791
第二個包,dev響應鏈接,ack=4133153792. 代表dev下次準備接收這個序號的包,用於tcp字節注的順序控制。dev(也就是server端)的初始序號爲seq=4264776963, syn=1.SYN ack=seq(c)+1 seq(s)=4264776963
第三個包,client包確認,這裏使用了相對值應答。seq=4133153792, 等於第二個包的ack. ack=4264776964.ack=seq(s)+1, seq=seq(c)+1
至此,三次握手完成。接下來就是發送ping和pong的數據了。
接着第四個包。
10:55:48.090073 IP dev2.39070 > dev.6379: Flags [P.], seq 1:15, ack 1, win 229, options [nop,nop,TS val 2959273132 ecr 3132256230], length 14 0x0000: 4500 0042 08d1 4000 3606 149d 0ab3 b561 0x0010: 0a60 5cd4 989e 18eb f65a ec00 fe33 5504 0x0020: 8018 00e5 4b5f 0000 0101 080a b062 ecac 0x0030: bab2 6fe6 2a31 0d0a 2434 0d0a 7069 6e67 0x0040: 0d0a
tcp首部長度爲32B, 可選長度爲12B. IP報文的總長度爲66B, 首部長度爲20B, 所以TCP數據部分長度爲14B. seq=0xf65a ec00=4133153792
ACK, PSH. 數據部分爲2a31 0d0a 2434 0d0a 7069 6e67 0d0a
0x2a31 -> *1 0x0d0a -> \r\n 0x2434 -> $4 0x0d0a -> \r\n 0x7069 0x6e67 -> ping 0x0d0a -> \r\n
dev2向dev發送了ping數據,第四個包完畢。
第五個包,dev2向dev發送ack響應。
序列號爲0xfe33 5504=4264776964, ack確認號爲0xf65a ec0e=4133153806=(4133153792+14).
第六個包,dev向dev2響應pong消息。序列號fe33 5504,確認號f65a ec0e, TCP頭部可選長度爲12B, IP數據報總長度爲59B, 首部長度爲20B, 所以TCP數據長度爲7B.
數據部分2b50 4f4e 470d 0a, 翻譯過來就是+PONG\r\n
.
至此,Redis客戶端和Server端的三次握手過程分析完畢。
「三次握手,四次揮手」看似簡單,可是深究進去,仍是能夠延伸出不少知識點的。好比半鏈接隊列、全鏈接隊列等等。之前關於TCP創建鏈接、關閉鏈接的過程很容易就會忘記,多是由於只是死記硬背了幾個過程,沒有深刻研究背後的原理。
因此,「三次握手,四次揮手」你真的懂了嗎?歡迎一塊兒交流~~
【redis】https://segmentfault.com/a/11...
【tcp option】https://blog.csdn.net/wdscq12...
【滑動窗口】https://www.zhihu.com/questio...
【全鏈接隊列】http://jm.taobao.org/2017/05/...
【client fooling】 https://github.com/torvalds/l...
【backlog RECV_Q】http://blog.51cto.com/5909093...
【定時器】https://www.cnblogs.com/mengh...
【隊列圖示】https://www.itcodemonkey.com/...
【tcp flood攻擊】https://www.cnblogs.com/hubav...
【MSS MTU】https://blog.csdn.net/LoseInV...