1.2 TCP首部格式
TCP是一個通訊協議,所謂協議就是通訊各方約定好的通訊規則。大家必須全部嚴格遵守這些規則,通訊才能順利完成。TCP的通訊規則由一系列RFC來規範。首先是首部格式。
在Linux中對應的結構體為:
include/uapi/linux/tcp.h 24 struct tcphdr { 25 __be16 source; 26 __be16 dest; 27 __be32 seq; 28 __be32 ack_seq; 29 #if defined(__LITTLE_ENDIAN_BITFIELD) 30 __u16 res1:4, 31 doff:4, 32 fin:1, 33 syn:1, 34 rst:1, 35 psh:1, 36 ack:1, 37 urg:1, 38 ece:1, 39 cwr:1; 40 #elif defined(__BIG_ENDIAN_BITFIELD) 41 __u16 doff:4, 42 res1:4, 43 cwr:1, 44 ece:1, 45 urg:1, 46 ack:1, 47 psh:1, 48 rst:1, 49 syn:1, 50 fin:1; 51 #else 52 #error "Adjust your <asm/byteorder.h> defines" 53 #endif 54 __be16 window; 55 __sum16 check; 56 __be16 urg_ptr; 57 };
欄位說明:
source:源埠號,取值範圍1-65535;
dest:目的埠號,取值範圍1-65535;
seq:在SYN位元沒有設定的情況下,seq是報文段中第一個位元組的編號;
ack_seq:在ACK位元設定的情況下,ack_seq是這個報文的傳送者希望收到的下一個seq號;
下面的16bit使用了位域,由於機器由於“大端序”與“小端序”的不同,讀取和儲存每個位元的順序是相反的,故需要用條件編譯來區分。
doff:TCP頭的大小,其數值乘4就是TCP頭的位元組數;
Reserved: 留待將來使用,必須為0;
Control Bits:
CWR:Congestion Window Reduced,用於擁塞控制;
ECE:ECN-Echo,用於擁塞控制;
URG:緊急指標域有效;
ACK:確認號域有效;
PSH:推送功能,TCP收到此標識應該將資料立即提交接收者;如在GRO處理流程中,如果發現此標記則不快取包,直接交付協議棧;
RST:重置連線;
SYN:同步序列號,TCP通過設定此標誌位告知對端起始序列號;
FIN:送端不再有資料傳送;
視窗:報文的傳送者願意接收的從ack_seq開始的資料的位元組數;
檢驗和:用TCP首部、資料部分和偽首部計算的檢驗和。偽首部由源IP地址、目的IP地址、協議號、TCP首部+資料的位元組數構成;
緊急指標:表示緊急資料在此報文資料段中的偏移,即此報文的seq加上緊急指標的值就得到緊急資料的首序列號。這個欄位僅在URG控制位元設定時才有效。
選項:doff乘4再減去TCP基本首部長度20位元組即為選項欄位長度。選項用於擴充套件TCP的功能,不是必須。比較重要的選項有最大報文段、視窗擴大、時間戳、選擇確認。
使用struct tcphdr解析或構建TCP報文很簡單,只要定義一個struct tcphdr結構體指標,將其指向TCP報文段的首地址,然後就可以對各個欄位進行讀寫了。
要特別說明的是,seq和ack_seq都是無符號32位的整數。同一端的seq和ack_seq之間是獨立的,無任何聯絡,但接收端的ack_seq與傳送端的seq是同一空間。seq的取值範圍為0-4294967295,初始的值是隨機選取的,每傳送一個位元組的資料就消耗一個seq,SYN標誌位和FIN標誌位也會消耗一個seq。如果資料傳送很快,seq就會很快回繞,這時TCP可能無法區分舊資料與新資料,這樣就得通過時間戳選項等非法來解決這一問題(時間戳選項是後話)。
TCP協議自1981年由RFC 793提出到現在已經30多年了,其首部只是增加了CWR和ECE兩個標記,以及增加了若干選項欄位,現在仍然運轉良好。相比之下IP協議就不得不傷筋動骨地向IPv6過渡,就不得不佩服當初協議設計者的遠見卓識啊!
TCP首部欄位只是核心使用,通過socket API使用TCP的使用者是接觸不到的(除非使用AF_PACKET socket或RAW socket收發網路資料)。下面我們瞭解一下用socket API時TCP的基本使用方式。