網際網路筆記:TCP
TCP 內容太多了,單獨開一篇整理。
整個 TCP 的傳輸是由 RFC 793 定義,一般念書考試不會去讀、開發單個項目也不一定需要,根據我之前實作其他協議的經驗,大概做研究或從 0 開始做一個簡單的 TCP 連線才會需要好好讀完。
TCP 的託運單(header)
因為在下面條列讀起來太累了,我試著用 title 對每個欄位的意思做了註解,把滑鼠移上去就可以知道該欄位的用途。
此外,下面的 header 範例是一種 pseudo-header,屬於一種概念,開發不一定也長這樣。
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
| Source address (來源 address) | Destination address (目的地 address) | ||||||||||||||||||||||||||||||
| SN (序列號) | |||||||||||||||||||||||||||||||
| ACK (確認號) | |||||||||||||||||||||||||||||||
| Data offset | 保留 | N S |
C W R |
E C E |
U R G |
A C K |
P S H |
R S T |
S Y N |
F I N |
WIN | ||||||||||||||||||||
| Checksum | Urgent pointer | ||||||||||||||||||||||||||||||
接下來還有 Options ,改天再更新吧。
TCP header 一般會在 20~60 個 bytes (上面的表格中,一行為 32bits = 4 bytes),補上 option 後會洛在這個長度裡。
TCP 的 3 次握手 (建立連線)
這邊會用到上面 header 中 SYN、ACK (flag)、SN、ACK (確認號,下面用小寫表示) 等資訊,忘記的話先拉上去複習一下。
下面的表把滑鼠移到箭頭那格等一會會有說明。
| Client (狀態) | Server (狀態) | ||
| close | listen | ||
| SYN-sent | SYN=1, SN=x | ----------> | |
| <---------- | SYN=1, ACK=1, SN=y, ack=x+1 | SYN-RCVD | |
| ACK=1, SN=x+1, ack=y+1 | ----------> | ||
| established | 開始傳送資料 | established | |
Client 發起連線,送出 SYN 封包,要求建立連線
Server 收到 SYN 後回應,送出 SYN+ACK 封包,表示同意連線,並要求 Client 確認
Client 收到 SYN+ACK 後,送出 ACK 封包確認,連線正式建立
建立連線後,才能夠開始傳送資料。
中間如果封包丟失的情形(ex: Server 傳送了 SYN+ACK 封包,但 Client 卻沒送 ACK) ,通常會間隔一段時間再重送一次,間隔的時間通常會隨著重送次數指數成長(實際情形看發送方怎麼設定)。
SYN 洪水攻擊
發生原因:同時間有許多 Client 送 SYN 封包,Server 會回應 SYN+ACK 並用一個 queue 暫存狀態,等待客戶回 ACK。當今天 Client 是攻擊者的話,並不會回應 SYN+ACK,這就導致 Server 暫存的 queue 被佔滿,正常用戶無法連線。(和 DDoS 很像)
現行解法:
增加 queue
覆寫掉 queue 裡最舊的
使用 SYN Cookie
不在 Server 馬上存進 queue,而是把必要的資訊()編到 SYN-ACK 的 初始序號(ISN, Initial Sequence Number) 裡。Client 回傳的 ACK 中會包含「Server 給的 ISN + 1」,Server 只要解析這段資訊就知道 Client 是不是之前有送過 SYN。
TCP 的資料傳輸
在完成三次握手後,TCP 連線正式進入傳輸階段 (Data Transfer)。
TCP 之所以能在網路環境中保證資料的正確性與順序,主要仰賴以下幾個機制(會使用到 header 中的對應參數):
SN (Sequence Number,序號)
TCP 會為每個傳送的封包分配 SN。這讓接收端能根據序號將資料重新排序,即使封包在網路傳輸過程中發生亂序,也能組裝成正確的資料ACK (Acknowledgment)
每次收到封包,接收端會回傳一個 ACK 封包逾時重傳 (Timeout-based retransmission)
封包送出後,傳送端會等一段時間看有沒有收到接收端傳來 ACK,若是沒有收到,傳送端就會再送一次封包,間隔的時間通常會隨著重送次數指數成長checksum
計算內容:Pseudo Header、TCP Header、TCP Data
計算方法:把計算內容以兩個一組,做補數加法 (加法進位)
驗證方法: 接收端用同樣方法計算一次,再和 checksum 比對
Flow Control (流量控制)
接收端會使用 header 中 WIN 的位置來回報傳送端目前可接收的大小Congestion Control (擁塞控制)
整個擁塞控制的內容很多(維基百科可以另開一頁說明得多),這邊先不仔細說明如何實作、原理等,只列出機制:Slow Start (慢啟動)
Congestion Avoidance (擁塞避免)
Fast Retransmit (快重傳)
Fast Recovery (快恢復)
TCP 的 4 次揮手 (終止連線)
這邊會用到上面 header 中 FIN、ACK (flag)、SN、ACK (確認號,下面用小寫表示) 等資訊,忘記的話先拉上去複習一下。
| Client (狀態) | Server (狀態) | ||
| established | 傳送資料 | established | |
| FIN-WAIT-1 | FIN=1, SN=x | ----------> | |
| <---------- | ACK=1, SN=y, ack=x+1 | CLOSE-WAIT | |
| FIN-WAIT-2 | <---------- | FIN=1, SN=z | |
| TIME-WAIT | ACK=1, ack=z+1 | ----------> | LAST-ACK |
| CLOSED | |||
| CLOSED | |||
Client 發送結束訊息 FIN
Server 收到訊息後轉變狀態為等待關閉,送出「收到 FIN 訊息」的封包
Server 發送結束訊息 FIN,然後關閉
Client 送出「收到 FIN 訊息」的封包,原地等一段時間後關閉
比起箭頭上的備註,其實我更喜歡下面這種記法(?):
Client:我沒話要說了
Server:我知道了
Server:我也沒話要說了 (關掉連線)
Client: 我知道了 (等一段時間看 Server 有沒有再傳訊息過來,沒有就關掉連線)
為什麼建立連線只要 3 次,中斷連線卻要 4 次?
這好像是常常被提到的問題(?),但原因其實不複雜。
Client 要結束,不代表 Server 也要結束了,Server 可能還有其他訊息要傳送,所以這時還不能斷開連線,必須要 Server 也表達「我要結束」之後才能結束。
留言
張貼留言