1. 程式人生 > >《TCP/IP 詳解,卷1:協議》學習筆記、吐槽及其他

《TCP/IP 詳解,卷1:協議》學習筆記、吐槽及其他

《TCP/IP 詳解,卷1:協議》是經典,但不適合初學者。它更像是一本字典,適合學過網路的人溫習和查閱一些記不清的概念。 

這本書,我看的版本是機械工業出版社、範建華等譯的。這本書在我看來,翻譯得一般,甚至有明顯的錯誤。如果英文熟練,看原版更好:http://pcvr.nl/tcpip/

下面是我的一些筆記,包括我看書時有疑問的地方,也有對該書的吐槽,有不對的地方請指正: 

1. 
鏈路層資料報稱為 frame, IP 層資料報稱為 package, TCP 資料報稱為 segment, UDP 稱為 datagram。這些資料報都是包含了該層的首部。 

2. 
為什麼 IP 和 TCP 有“首部長度”這個欄位,而 UDP 沒有? 


因為 IP 首部和 TCP 首部都可能有“選項”,而 UDP 沒有。UDP 的首部固定為8位元組 


3. 
IP 有 “首部長度”和“總長度”, UDP 只有“總長度”,而 TCP 只有“首部長度”。這是為什麼? 

事實上,UDP 和 TCP 都不需要有“總長度”:IP 的“總長度”減去 IP 的“首部長度”,就是 TCP or UDP 的“總長度”了。書上也說了,UDP 的“總長度”是冗餘了。 

4. 
為什麼 IP 要有“總長度”? 

因為乙太網和802.3對資料幀的長度都有限制:乙太網要求最少為46位元組,802.3要求最少為38位元組。因此,IP 需要有“總長度”來表示,實際的資料(而不是為了達到最少位元組的要求而填充的資料)是多長。 



5. 
乙太網和 SLIP 都是鏈路層的協議。 SLIP 是序列鏈路上對 IP 層進行封裝的簡單形式。而 PPP 也是序列鏈路上封裝 IP 資料報的方法,且它修復了 SLIP 所有的缺陷。 

6. 
MTU 是什麼? 
乙太網和802.3對資料幀的長度都有限制。最大值稱為 MTU。 
乙太網要求資料幀的長度在46 ~ 1500;而802.3對應是38~1492。 
還有一點要注意的是,這個長度是鏈路層(乙太網和802.3都屬鏈路層)做出的限制,但它限制的卻是 IP 層傳給鏈路層的 IP packet 的長度,包括 IP 首部。 

7. 
章節:3.2 
IP 首部 
書中原文:“首部長度指的是首部佔 32 bit 字的數目,包括任何選項。由於它是一個 4位元欄位,因此 

首部最長為6 0個位元組”。 

這一句話第一次看時很困惑。4位元,最大值不是15(ox1111)麼,怎麼是60位元組呢?原來,它的單位是“4位元組”,而不是“位元組”。15個單位,就是15個“4位元組”,即60個位元組。 

8. 
書上第4章的4.5.1: 
Bsdi % telnet svr4 discard 
在 bsdi 上第一次 telnet svr4,svr4當然會向 bsdi 返回一個 arp 響應。Arp 響應是單播的,為什麼在 sun 上通過 tcpdump 也可以看到這個 arp 響應呢? 

Wiki 上關於 tcpdump 的定義: 
tcpdump is a common packet analyzer that runs under the command line. It allows the user to intercept and displayTCP/IP and other packets being transmitted or received over a network to which the computer is attached. 

原來它可以監控它所在區域網內的所有資料包。簡直就一黑客軟體。 

9. 
書中原文:“為什麼在 SLIP 鏈路的兩端只擁有一個 IP 地址,而在 bsdi 和 slip 之間的兩端卻分別有一個 IP 地址?”

書上對這個問題有解答,但我看了還是不明白。求指導。 

10. 
書上把 gratuitous ARP 翻譯為“免費 ARP”,我覺得翻譯成“無理由的ARP”更容易理解。gratuitous ARP是主機發送 ARP 查詢自己的 IP,它的作用有: 
a.檢視是否有其他主機與本主機配置了同樣的 IP(如果接收到了 arp 應答,則表明是其他主機配置了同樣的 IP) 
b.如果本機 mac 改變了,則 gratuitous ARP 可以通知其他主機更新 arp 快取 

11. 
RARP的作用 
主要是用在無盤工作站。因為無盤工作站不像“有盤”那樣,可以把 IP 地址儲存在本地磁碟上。那無盤工作站的 IP 儲存在哪裡?儲存在 RARP 伺服器上。RARP 伺服器的作用就是當無盤工作站傳送 RARP 請求時,返回後者的 IP 地址。(什麼是無盤工作站?沒接觸過。。) 


12. 
書中原文:“當傳送一份 ICMP 差錯報文時,報文始終包含 IP 的首部和產生 ICMP 差錯報文的 IP 資料報的前8個位元組”。 

包含引起ICMP 差錯報文的 IP package 的首部是為了知道是什麼協議(TCP 還是 UDP);而包含該 IP packege 的資料部分的前8個位元組是為了知道源埠和目的埠。但為什麼不是4位元組呢?不明白。這8個位元組也就是 TCP or UDP 的首部的前8個位元組,但它們前4個位元組就剛好就是源埠和目的埠 


13. 
章節:6.4.1: 
sun % icmptime bsdi 
orig = 83573336, recv = 83573330, xmit = 83573330, rtt = 2 ms 
difference = -6 ms 
sun % icmptime bsdi 
orig = 83577987, recv = 83577980, xmit = 83577980, rtt = 2 ms 
difference = -7 ms 

書中原文:“往返時間(rtt),它的值是收到應答時的時間值減去傳送請求時的時間值。 Differene 的值是接收時間戳減去發起時間戳值”。 

這個說法是沒問題,但計算一下, rtt=recv-orig=83573330-83573336=-6,怎麼會“rtt = 2 ms”呢?而difference 數值是-7沒錯了,但為什麼接收時間送去發起時間會是負數呢?難道接收時間在發起時間之前?不明白。。 

Update at 2014-06-20 感謝 Michael 同學: 
理解這點的關鍵是,不同主機的系統時間可能是不同的。 
回到例子當中的第一個輸出:orig = 83573336是 sun 傳送請求的時間(由 sun 填入的,它本機的時間),recv = 83573330是 bsdi 接收到請求的時間(由 bsdi 填入的,它本機的時間),而 rtt 是 sun 計算出來的,是 sun 接收到報文的時間減去 sun 傳送報文的時間——例子當中的輸出並沒有包括 sun 接收到報文的時間。 
因此,如果 sun 主機想要較準自己的系統時間、以 bsdi 的系統時間為準的話,那 sun 應該把自己的系統時間調慢7ms。 
怎麼計算呢? 
很簡單。 sun 傳送報文的時間是83573336,那麼報文傳送到 bsdi 時,時間應該是 83573336 + rtt/2 = 83573336 + 2/1 = 83573337;但實際上 bsdi 接收到報文的時間是83573330;由此可見, sun 的時間比 bsdi 快 83573337 - 83573330 = 7ms。 
如果以第二個輸出為準的話, sun 應該把自己的系統時間調慢8ms。 
這就是書上說的“在前面的例子中, b s d i的時鐘比s u n的時鐘要慢 7 ms 和8 ms ”。 

14. 
Ping 程式並不屬於應用層。它是直接傳送 ICMP 請求 


15. 
traceroute 屬於應用層,它傳送的是 UDP datagram。它收到的 ICMP 報文,如果是“超時報文”,則表明它探測到了一個路由器(路由器發現 TTL = 1則會丟棄並返回超時報文,超時報文中包含了該路由器的 IP);如果是“埠不可達”,則表明是成功到達目的地了(這可能會有混淆,“埠不可達”不是表示“不可達”?是這樣,因為到達目的地, UDP 才會嚮應用層提交資料,而traceroute 選擇了一個不可能的值作為目的埠,因此會返回“埠不可達”) 

16. 
章節:7.2.3 
Ping 程式預設情況為每秒鐘(linux 裡可通過-i設定)發一個 ICMP 回顯請求,如果 RTT 大於1秒,則可能會顯示 packet loss。實際上可能並沒有丟包,回顯應該可能仍在返回途中。 

17. 
理論上,路由器不應該接收到 TTL = 0的 IP 資料包。因為當某個路由器接收到 TTL = 1的 IP 資料包時,它就已經不再轉發 

18. 
IP 資料報(IP datagram)是指 IP 層端到端(傳送端到接收端)的傳輸單元,而 IP 分組(IP packet)是指在同一端的 IP 層與鏈路層之間傳送的資料單元。由於 IP 分片的存在,一個 IP 分組可能是一個完整的 IP 資料報,也可能是 IP 資料報一分片。 

19. 
IP packet 丟失與 TCP segment 丟失不一樣,前者必須重傳整個 IP datagram,而 TCP 只需重傳丟失的那一份 segment 就可以了。 

這是因為 IP 層沒有重傳機制。因此要儘量避免 IP 分片。 

20. 
DNS 的結構通常表現為樹狀。但很多時候人們用圖片來示意的時候,把不同層之間完全分隔開來了。事實上,上級節點是存放了所有它的直屬下級節點的資訊。例如根節點“.”它不應該是單獨的一個點,它包含了頂級域的資訊:“.com”、“.org”、“.net”等等。為什麼要這樣?原因很簡單,當你查詢“xx.com.”這個域名時,你會從根節點開始,首先你詢問根節點“.com”的資訊,如果根節點沒有包含,那該查詢就終止了,這顯然是不對的。 

21. 
TCP segment 的序號是從 ISN 開始的。第一次握手(SYN)的序號是 ISN,因此傳送資料的第一個位元組的序號是( ISN + 1) 

22. 
章節:18.3.1 
書中原文:“B S D版的T C P軟體採用一種500 ms 的定時器。這種500 ms 的定時器用於確定本章中所有的各種各樣的 T C P超時。當我們鍵入 t e l n e t 命令,將建立一個 6秒的定時器(1 2個時鐘滴答( t i c k ) ) ,但它可能在之後的 5 . 5秒~ 6秒內的任意時刻超時。圖 1 8 - 7顯示了這一發生過程。儘管定時器初始化為 1 2個時鐘滴答,但定時計數器會在設定後的第一個 0~500 ms 中的任意時刻減1。從那以後,定時計數器大約每隔 500 ms 減1,但在第1個500 ms內是可變的(我們使用限定詞“大約”是因為在 T C P每隔500 ms獲得系統控制的瞬間,系統核心可能會優先處理其他中斷) 。” 

這一段話令人費解。實際上並不複雜: 
a. 計時是通過時鐘滴答(clock tick)來實現的。也就是說,不是“時間過去500ms就滴答一下”,而是“滴答一下就表示500ms過去了”。 
b.為什麼第一次超時時間不是6秒呢? 
舉例,假如我們從某一次時鐘滴答之後開始(稱這個時刻為0ms,因此下一次時鐘滴答會在500ms後響起),在200ms時我們設定了一個6秒的定時器(實際上我們是設定了12個時鐘滴答),那麼在500ms時,時鐘滴答響起,定時器倒計時減少了500ms,但此時實際時間才過去了300ms。因此到最後,6秒的定時器只定時了300 + 500 * 11 = 5800ms,也就是5.8秒。一句話,是因為第一個時鐘滴答的定時是不準確的。 

23. 
為什麼 TCP 建立連線時只需3次握手,而斷開時需要4次揮手? 

這是因為,斷開連線時,被動關閉的一方收到 FIN 後,它只能傳送對對方 FIN 的 ACK,不能直接傳送它的 FIN,而要先向應用層報告,等應用層通知它關閉了,它才傳送自己的 FIN 

24. 
章節:18.6.1 
書中原文:“因為處於 2 M S L等待的、由該插口對(socket pair) 定義的連線在這段時間內不能被再用,因此當要建立一個有效的連線時,來自該連線的一個較早替身( i n c a r n a t i o n )的遲到報文段作為新連線的一部分不可能不被曲解”。 

我認為這個翻譯是錯的。應該是“不可能被曲解”。 
英文原文: 
Since the connection defined by the socket pair in the 2MSL wait cannot be reused during this time period, when we do establish a valid connection we know that delayed segments from an earlier incarnation of this connection cannot be misinterpreted as being part of the new connection.(A connection is defined by a socket pair. New instances of a connection are called incarnations of that connection.) 
我們知道 2MSL 的作用之一就是,避免舊連線的遲到報文被誤認為是新連線的一部分。因此,英文原文的意思是:當一個有效連線建立時,已經表明時間過去了 2MSL (否則不可能在該四元組上建立連線),舊連線的遲到報文不可能被曲解為新連線的報文。 

為什麼是 2MSL? 
假設 last-ack 丟失(此時時間過去了 MSL),重傳後,如果時間又過去了 MSL,此時 last-ack 已經過了生存期限了,因此 2MSL 後即使接收到舊連線的 last-ack,也會把它丟棄。 

25. 
章節:18.11.4 
書中原文:“積壓值說明的是 T C P 監聽的端點已被T C P接受而等待應用層接受的最大連線數。這個積壓值對系統所允許的最大連線數,或者併發伺服器所能併發處理的客戶數,並無影響” 

這段話我的理解是,應用程式會不斷地把積壓的 TCP 連線從“呼入連線請求佇列”中讀取走,這個讀取操作通常很快,因此積壓值並不影響速度,所以伺服器的併發數是由應用程式決定的。 

26. 
章節:19.2 
書中原文:“與字元 a有關的是第 4 ~ 6行,與字元t有關的是第 7 ~ 9行,第1 0 ~ 1 2行與字元 e有關。第 3 ~ 4、6 ~ 7、9 ~ 1 0和1 2 ~ 1 3行之間半秒左右的時間差是鍵入兩個字元之間的時延。” 

看圖19.2,這些行之間的間隔分別是0.3181,0.2746,0.2512,0.3407,單位是秒。 
那應該是300毫秒左右,怎麼會是“半秒”呢? 
再看英文原文: 
The fractional second delays between lines 3-4, 6-7, 9-10, and 12-13 are the human delays between typing each character. 
英文原文說的是“The fractional second delays”,少量的延遲。 
翻譯錯了。 

27. 
章節:19.3 
書中原文:“也就是說, T C P將以最大200 ms 的時延等待是否有資料一起傳送。如果觀察b s d i接收到資料和傳送 A C K之間的時間差,就會發現它們似乎是隨機的: 1 2 3 . 5、6 5 . 6、1 0 9 . 0、1 3 2 . 2、4 2 . 0、1 4 0 . 3和195.8 ms 。相反,觀察到傳送 A C K的實際時間(從 0開始)為:1 3 9 . 9、5 3 9 . 3、9 4 0 . 1、1 3 3 9 . 9、1 7 3 9 . 9、1 9 4 0 . 1和2140.1 ms (在圖 1 9 - 3中用星號標出) 。這些時間之間的差則是 200 ms 的整數倍” 

我的理解是,TCP 每隔200ms會檢視是否有 ACK 要傳送,如果有就傳送,如果沒有就什麼也不做。而在兩次檢視的間隔內產生的 ACK ,是不會立即傳送的。同時,如果某時刻有資料要傳送,那就順帶把“處於等待”中的 ACK 也一併傳送出去。 

也就是書中所說的“最大等待200ms”。 

28. 
章節:19.4 
書中原文:“報文段1 4和1 5看起來似乎是與 N a g l e演算法相違背的,但我們需要通過檢查序號來觀察其中的真相。因為確認序號是 5 4,因此報文段 1 4是報文段 1 2中確認的應答。但客戶在傳送該報文段之前,接收到了來自伺服器的報文段 1 3,報文段1 5中包含了對序號為 5 6的報文段 1 3的確認。因此即使我們看到從客戶到伺服器有兩個連續返回的報文段,客戶也是遵守了 N a g l e演算法的。” 

這個解釋我不理解:客戶是遵守 Nagel 演算法的,因為報文段14是對報文段12的應答,而報文段15是對報文段13的應答。 
這個邏輯不通,牛頭不對馬嘴:按照 Nagel 演算法,一個分組發出去,在未確認之前,不能傳送新的分組。因此報文段14發出後,就不應該緊接著發報文段15.這跟分組是誰的應答有什麼聯絡? 

看英文原文也是同樣的解釋。和同學討論了也沒結果。 
最後我找到一個可能可以解釋這個問題的根據,英文 wiki 關於 Nagel 演算法的虛擬碼: 
Java程式碼  收藏程式碼
  1. if there is new data to send  
  2.   if the window size >= MSS and available data is >= MSS  
  3.     send complete MSS segment now  
  4.   else  
  5.     if there is unconfirmed data still in the pipe  
  6.       enqueue data in the buffer until an acknowledge is received  
  7.     else  
  8.       send data immediately  
  9.     end if  
  10.   end if  
  11. end if  

看其中的“enqueue data in the buffer until an acknowledge is received”,如果有分組未確認,那就放入佇列中等待,除非收到一個 ACK! 
看這意思,是說只要收到一個 ACK,就可以發資料了,而不管它還有分組未確認? 
有時間看看 TCP 的原始碼可能會比較清楚。。 

29. 
章節:20.2 
書中原文:“傳送方首先傳送 3個數據報文段( 4 ~ 6) 。下一個報文段( 7)僅確認了前兩個資料報文段,這可以從其確認序號為 2 0 4 8而不是3 0 7 3看出來。” 
這是一個明顯的錯誤。 
英文原文: 
The sender transmits three data segments (4-6) first. The next segment (7) acknowledges the first two data segments only. We know this because the acknowledged sequence number is 2049, not 3073. 
是“2049”而不是“2048”,圖20-1顯示的也是2049 

30. 
章節:21.3 
書中原文:“[Karn and Partridge 1987] 規定,當一個超時和重傳發生時,在重傳資料的確認最後到達之前,不能更新 RT T估計器,因為我們並不知道 A C K對應哪次傳輸”。 
看這翻譯的意思是:發生了資料重傳也可以更新 RTT 估計器,只要“重傳資料的確認”最後到達了。 
這是不對的。 
發生了重傳就不能更新 RTT 估計器。 
英文原文: 
[Karn and Partridge 1987] specify that when a timeout and retransmission occur, we cannot update the RTT estimators when the acknowledgment for the retransmitted data finally arrives. This is because we don't know to which transmission the ACK corresponds. 

31. 
章節:21.4.2 
書中原文:“A C K在重傳後 4 6 7 m s到達。 A和D的值沒有被更新,這是因為 K a r n演算法對重傳的處理比較模糊。” 
這個翻譯是錯的。 
英文原文: 
The ACK arrives 467 ms after the retransmission. The values of A and D are not updated, because of Karn's algorithm dealing with the retransmission ambiguity. 
應該理解成: 
A C K在重傳後 4 6 7 m s到達。 A和D的值沒有被更新,這是因為Karn's algorithm 在起作用。因為Karn's algorithm 正是為了解決“重傳多義性”問題而生的。見第30條。 

32. 
章節:22.3 
圖22.3 

為什麼在可用快取是509時可以通告視窗大小為509,而快取是768時卻通告視窗大小為0。509和768這兩個值都達不到報文段大小,也達不到接收方快取的一半,按照避免“糊塗視窗綜合症”,理論上應該都通告視窗為0吧? 

這是因為滑動視窗的右邊框是不能左移的。上一視窗通告為1533,只接收了1024,視窗“沒用完”,所以1533的下一個視窗,至少是509,視窗的右邊框不能左移。而視窗通告509之後,來了資料,用完了509,因此可通告為0 (本來可通告為768的,但因為採取了“糊塗視窗避免”) 

33. 
章節:21.4.2