TCP
原理 互聯網由一整套協議構成。TCP
只是其中的一層,有着本身的分工。 瀏覽器
來來來,咱們先複習一下TCP/IP
五層結構,詳細請參考筆者以前分享的網絡原理篇緩存
以太網協議(Ethernet
)
位於數據鏈路層,規定了電子信號如何組成數據包(packet
),解決了 子網內部的 點對點 通訊。 服務器
IP
協議
IP 協議位於 網絡層 ,定義了一套本身的 地址規則,稱爲 IP
地址。它實現了 路由 功能,容許某個局域網的A
主機,向另外一個局域網的B
主機發送消息。 網絡
路由的原理很簡單。市場上全部的 路由器,背後都有不少 網口,要接入多根 網線。路由器內部有一張 路由表,規定了A
段IP
地址走出口 一,B
段地址走出口二,......經過這套 指路牌,實現了數據包的 轉發。 併發
IP 協議只是一個 地址協議,並不保證數據包的 完整。若是路由器 丟包,就須要發現 丟了哪個包,以及 如何從新發送 這個包。這就要依靠TCP
協議。tcp
源端口和目的端口
分別佔用16bit,表示 源端口號 和 目的端口號 ;用於區別主機中的 不一樣進程,而 IP地址 是用來 區分不一樣的主機的,源端口號和目的端口號 配合上IP
首部中的源IP地址
和目的IP地址
就能惟一的肯定一個TCP
鏈接函數
序號
Seq 序號,32
bit,用來標識從TCP
源端向目的端發送的字節流,發起方發送數據時對此進行標記。spa
確認序號
Ack 序號,32
bit,只有ACK
標誌位爲1
時,確認序號字段纔有效。【確認方Ack=發起方Req+1,兩端配對】操作系統
數據偏移
給出首部中32
bit的數目,須要這個值是由於任選字段的長度是可變的。這個字段佔4
bit(最多能表示15
個32
bit的的字,即4*15=60
個字節的首部長度),所以TCP最多有60字節的首部。然而,沒有任選字段,正常的長度是20
字節;計算機網絡
標誌位
共6
個,即URG
、ACK
、PSH
、RST
、SYN
、FIN
等,具體含義以下:
URG
:緊急指針(urgent pointer
)有效ACK
:確認序號有效PSH
:接收方 應該儘快將這個報文交給 應用層RST
:重置鏈接SYN
:發起一個 新鏈接FIN
:釋放一個鏈接窗口
窗口大小,也就是有名的滑動窗口,用來進行流量控制
簡單說,TCP
協議的做用是,保證數據通訊的 完整性 和 可靠性,防止丟包。 以太網 數據包(packet
)的大小是固定的,最初是1518
字節,後來增長到1522
字節。其中, 1500
字節是負載(payload
),22
字節是 頭信息(head
)。
IP 數據包在以太網數據包的負載裏面,它也有本身的 頭信息,最少須要20
字節,因此IP
數據包的負載最多爲1480
(1500-20
)字節。
TCP 數據包在 IP
數據包的負載
裏面。它的 頭信息 最少也須要20
字節,所以TCP
數據包的最大負載是 1480 - 20 = 1460
字節。因爲 IP
和 TCP
協議每每有 額外的頭信息,因此TCP
負載實際爲1400
字節左右。
所以,一條1500
字節的信息須要 兩個 TCP
數據包。HTTP/2
協議的一大改進, 就是壓縮 HTTP
協議的頭信息,使得一個 HTTP
請求能夠放在 一個 TCP
數據包裏面,而不是分紅多個,這樣就提升了速度。
SEQ
) 一個包1400
字節,那麼一次性發送 大量數據,就必須分紅多個包。好比,一個 10MB
的文件,須要發送7100
多個包。
發送的時候,TCP
協議爲每一個包 編號(sequence number
,簡稱 SEQ
),以便接收的一方 按照順序還原。萬一發生 丟包,也能夠知道丟失的是哪個包。
第一個包 的編號是一個 隨機數。爲了便於理解,這裏就把它稱爲 1 號包。假定這個包的負載長度是 100 字節,那麼能夠推算出下一個包的編號應該是 101。這就是說,每一個數據包均可以獲得 兩個 編號:自身的編號,以及 下一個包 的編號。接收方由此知道,應該按照什麼 順序 將它們 還原 成原始文件。
(ps :當前包的編號是45943
,下一個數據包的編號是46183
,由此可知,這個包的負載是240
字節。)
收到TCP
數據包之後,組裝還原 是操做系統內核完成的。應用程序 不會 直接處理TCP
數據包。
對於 應用程序 來講,不用關心 數據通訊 的細節。除非線路異常,收到的老是完整的數據。應用程序須要的數據放在TCP
數據包裏面,有本身的 格式(好比HTTP
協議)。
TCP 並無提供任何 機制,表示 原始文件 的大小,這由 應用層 的協議來規定。好比,HTTP
協議就有一個頭信息 Content-Length,表示 信息體 的大小。對於 操做系統 來講,就是持續地 接收 TCP
數據包,將它們按照順序 組裝好,一個包都很多。
操做系統 不會去處理 TCP
數據包裏面的數據。一旦組裝好 TCP
數據包,就把它們轉交給 應用程序。TCP
數據包裏面有一個 端口(port
)參數,就是用來指定轉交給 監聽該端口 的應用程序。
(PS:系統根據 TCP
數據包裏面的端口,將組裝好的數據轉交給相應的應用程序。上圖中,21
端口是 FTP
服務器,25
端口是 SMTP
服務,80
端口是 Web
服務器。)
應用程序 收到組裝好的 原始數據,以 瀏覽器 爲例,就會根據 HTTP
協議的Content-Length
字段正確讀出一段段的數據。這也意味着,一次 TCP
通訊能夠包括 多個 HTTP
通訊。
第一次握手
創建鏈接, 客戶端 發送鏈接 請求報文段,將SYN
位置爲1
,Sequence Number
爲x
;
而後,客戶端進入SYN_SEND
狀態,等待 服務器 的確認;
第二次握手
服務器 收到SYN
報文段。服務器 收到 客戶端 的SYN
報文段,須要對這個SYN
報文段進行 確認,設置 Acknowledgment Number 爲x+1
(Sequence Number+1
);
同時,本身本身還要發送SYN
請求信息,將SYN
位置爲1
,Sequence Number
爲y
;
** 服務器端** 將上述全部信息放到一個 報文段(即SYN+ACK
報文段)中,一併發送給 客戶端,此時服務器進入SYN_RECV
狀態;
第三次握手
客戶端 收到 服務器 的SYN+ACK
報文段。而後將Acknowledgment Number
設置爲y+1
,向服務器發送ACK
報文段,這個報文段發送完畢之後,客戶端和服務器端 都進入ESTABLISHED
狀態,完成TCP
三次握手。
當 客戶端 和 服務器 經過 三次握手 創建了 TCP 鏈接之後,當數據 傳送完畢,確定是要 斷開TCP鏈接 的啊。那對於TCP的斷開鏈接,這裏就有了神祕的 四次分手。(繼續參考上圖)
第一次分手
主機A(可使客戶端,也能夠是服務器端),設置Sequence Number
和Acknowledgment Number
,向 主機B 發送一個FIN
報文段;
此時,主機A 進入FIN_WAIT_1
狀態;
這表示主機1沒有數據要發送給主機2了;
第二次分手 主機B 收到了 主機A 發送的FIN
報文段,向 主機A 回一個ACK
報文段,Acknowledgment Number
爲Sequence Number
加1;
主機A 進入FIN_WAIT_2
狀態;
主機B告訴主機A,我贊成你的關閉請求
第三次分手
主機B 向 主機A 發送FIN
報文段,請求關閉鏈接,同時 主機B 進入 LAST_ACK 狀態;
第四次分手
主機A 收到 主機B 發送的FIN
報文段,向 主機B 發送ACK
報文段,而後主機A
進入TIME_WAIT
狀態;
主機B 收到 主機A 的ACK
報文段之後,就 關閉鏈接;
此時,主機A 等待2MSL
後依然沒有收到回覆,則證實 Server端 已正常關閉,那好,主機A 也能夠關閉鏈接了。
至此,TCP的四次分手就這麼愉快的完成了。
爲何要三次握手 在謝希仁著《計算機網絡》第四版中講 三次握手 的目的是 爲了防止已失效的鏈接請求報文段忽然又傳送到了服務端,於是產生錯誤 。在另外一部經典的《計算機網絡》一書中講 三次握手 的目的是爲了解決 網絡中存在延遲的重複分組 的問題。
在謝希仁著《計算機網絡》書中同時舉了一個例子,以下:
「已失效的鏈接請求報文段」的產生在這樣一種狀況下:client發出的第一個鏈接請求報文段並無丟失,而是在某個網絡結點長時間的滯留了,以至延誤到鏈接釋放之後的某個時間纔到達server。原本這是一個早已失效的報文段。但server收到此失效的鏈接請求報文段後,就誤認爲是client再次發出的一個新的鏈接請求。因而就向client發出確認報文段,贊成創建鏈接。假設不採用「三次握手」,那麼只要server發出確認,新的鏈接就創建了。因爲如今client並無發出創建鏈接的請求,所以不會理睬server的確認,也不會向server發送數據。但server卻覺得新的運輸鏈接已經創建,並一直等待client發來數據。這樣,server的不少資源就白白浪費掉了。採用「三次握手」的辦法能夠防止上述現象發生。例如剛纔那種狀況,client不會向server的確認發出確認。server因爲收不到確認,就知道client並無要求創建鏈接。」
這就很明白了,防止了服務器端的一直等待而浪費資源。
爲何要四次分手
那四次分手又是爲什麼呢?
TCP 協議是一種面向 鏈接的、可靠的、基於字節流 的運輸層通訊協議。
TCP 是 全雙工 模式,這就意味着,當主機A 發出 FIN 報文段時,只是表示 主機A 已經沒有數據要發送了,主機A 告訴 主機B,它的數據已經 所有發送完畢了;
可是,這個時候 主機A 仍是能夠 接受 來自 主機B 的數據;
當 主機B 返回ACK
報文段時,表示它已經知道 主機A 沒有數據發送了,可是 主機B 仍是能夠 發送 數據到 主機A 的;
當 主機B 也發送了FIN
報文段時,這個時候就表示主機B
也沒有數據要 發送了,就會告訴 主機B,我也 沒有 數據要發送了。
以後彼此就會愉快的 中斷 此次TCP鏈接。
FIN_WAIT_1
這個狀態要好好解釋一下,其實FIN_WAIT_1
和FIN_WAIT_2
狀態的真正含義都是表示 等待對方的FIN報文。
而這兩種狀態的區別是:FIN_WAIT_1
狀態其實是當SOCKET
在ESTABLISHED
狀態時,它想 主動 關閉鏈接,向對方發送了FIN
報文,此時該SOCKET
即進入到FIN_WAIT_1
狀態。
而當 對方 迴應ACK
報文後,則進入到FIN_WAIT_2
狀態。
固然在實際的正常狀況下,不管對方何種狀況下,都應該 立刻 迴應ACK
報文,因此FIN_WAIT_1
狀態通常是比較難見到的,而FIN_WAIT_2
狀態還有時經常能夠用netstat
看到。
FIN_WAIT_2
上面已經詳細解釋了這種狀態,實際上FIN_WAIT_2
狀態下的SOCKET,表示 半鏈接,也即 有一方 要求 close 鏈接,但另外還告訴對方,我暫時還有點數據須要傳送給你(ACK信息),稍後再關閉鏈接。
CLOSE_WAIT
這種狀態的含義實際上是表示在 等待關閉。怎麼理解呢?
當對方close
一個SOCKET
後發送FIN
報文給本身,你係統毫無疑問地會迴應一個ACK
報文給對方,此時則進入到CLOSE_WAIT
狀態。接下來呢,實際上你真正須要考慮的事情是察看你是否還有數據發送給對方,若是 沒有 的話,那麼你也就能夠 close
這個SOCKET
,發送FIN
報文給對方,也即 關閉鏈接。因此你在CLOSE_WAIT狀態下,須要完成的事情是 等待你去關閉鏈接。
LAST_ACK
這個狀態仍是比較容易好理解的,它是 被動關閉一方 在發送FIN
報文後,最後等待對方的ACK
報文。
當收到ACK
報文後,也便可以進入到CLOSED
可用狀態了。
TIME_WAIT
表示收到了對方的FIN
報文,併發送出了ACK
報文,就等2MSL
後便可回到CLOSED
可用狀態了。若是FINWAIT1
狀態下,收到了對方同時帶FIN
標誌和ACK
標誌的報文時,能夠直接進入到TIME_WAIT
狀態,而無須通過FIN_WAIT_2
狀態。
CLOSED 表示鏈接中斷。
服務器 發送數據包,固然越快越好,最好 一次性 全發出去。可是,發得 太快,就有可能 丟包。帶寬小、路由器過熱、緩存溢出 等許多因素都會致使 丟包。線路很差的話,發得越快,丟得越多。
最 理想 的狀態是,在線路 容許 的狀況下,達到 最高速率。可是咱們怎麼知道,對方線路的 理想速率 是多少呢?答案就是 慢慢試。
TCP 協議爲了作到 效率 與 可靠性 的統一,設計了一個 慢啓動(slow start
)機制。開始 的時候,發送得 較慢,而後根據 丟包 的狀況,調整速率;若是 不丟包,就 加快 發送速度;若是 丟包,就 下降 發送速度。
Linux 內核裏面設定了(常量TCP_INIT_CWND
),剛開始通訊的時候,發送方一次性發送10
個數據包,即 發送窗口 的大小爲10。而後停下來,等待 接收方的確認,再 繼續 發送。
默認狀況下,接收方每收到 兩個 TCP
數據包,就要發送 一個 確認消息。確認 的英語是 acknowledgement
,因此這個確認消息就簡稱 ACK
。
ACK 攜帶 兩個 信息。
發送方有了這 兩個 信息,再加上本身 已經發出的數據包的最新編號,就會推測出 接收方大概的接收速度,從而 下降或增長 發送速率。這被稱爲 發送窗口,這個窗口的大小是 可變的。
(PS:每一個 ACK 都帶有下一個數據包的編號,以及接收窗口的剩餘容量。雙方都會發送 ACK。)
注意,因爲TCP
通訊是 雙向 的,因此 雙方 都須要發送 ACK
。兩方的 窗口 大小,極可能是 不同 的。並且 ACK
只是很簡單的幾個字段,一般與 數據 合併在一個 數據包 裏面發送。
(PS:上圖一共 4 次通訊。第一次 通訊,A
主機發給B
主機的數據包編號是1
,長度是100
字節。所以 第二次 通訊B
主機的 ACK
編號是 1 + 100 = 101
,第三次通訊 A
主機的數據包編號也是 101
。同理,第二次 通訊 B
主機發給 A
主機的數據包編號是1
,長度是200
字節,所以 第三次通訊 A
主機的 ACK
是201
,第四次通訊 B
主機的數據包編號也是201
。)
即便對於帶寬 很大、線路很好 的鏈接,TCP
也老是從10
個數據包開始慢慢試,過了一段時間之後,才達到 最高的傳輸速率。這就是 TCP
的 慢啓動。
TCP 協議能夠保證 數據通訊 的 完整性,這是 怎麼作到的?
前面說過,每個數據包都帶有 下一個數據包的編號。若是下一個數據包 沒有收到,那麼 ACK 的 編號 就不會發生變化。
EG,如今收到了4
號包,可是沒有收到5
號包。ACK
就會記錄,期待收到5
號包。過了一段時間,5
號包收到了,那麼下一輪 ACK
會更新編號。若是5
號包仍是沒收到,可是收到了6
號包或7
號包,那麼 ACK
裏面的編號不會變化,老是顯示5
號包。這會致使 大量重複內容 的 ACK
。
若是 發送方 發現收到 三個 連續的重複 ACK,或者 超時 了還 沒有 收到任何 ACK
,就會確認 丟包,即5號包遺失了,從而 再次發送 這個包。經過這種機制,TCP
保證了 不會 有數據包丟失。
(PS:Host B
沒有收到100
號數據包,會連續發出相同的 ACK
,觸發 Host A
重發100
號數據包。)
明白了TCP
原理,咱們就能夠對TCP
監聽的端口進行端口探測。
其實很簡單,咱們構造一個第一次握手的SYN
包給探測主機,若是探測主機回覆咱們一個SYN+ACK
的回覆包,咱們就能夠斷定 此端口 爲目標主機開放端口:
經過以前咱們完成的ping
探測主機腳本,判斷目標主機是否可達
構造一系列待探測端口的SYN
TCP網絡數據包
TCP(dport=(int(lport),int(hport)),flags=2)
複製代碼
解釋一下:
lport: 起始探測端口
hport: 截止探測端口
flags:2表示SYN
包,來源於SYN
位置1,等於2
將TCP
包封裝到IP
包中
經過sr
函數將這一系列包所有發出去,並將結果保存在一個List
中
遍歷返回的結果,若是結果中TCP
包是SYN+ACK
就代表 該端口開放
連串起來:
掃描結果:
發現咱們的虛擬主機對外開放了22端口,那麼咱們就能夠想點歪心思啦
關注筆者公衆帳號[mindev],並回復tcp,就能獲得tcp掃描源碼喲~~
願意與你們分享交流各類技術,我的公衆帳號[mindev],以及 知識星球[ 極客世界 ]