《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 算法的伪代码:
if there is new data to send
if the window size >= MSS and available data is >= MSS
send complete MSS segment now
else
if there is unconfirmed data still in the pipe
enqueue data in the buffer until an acknowledge is received
else
send data immediately
end if
end if
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
书中原文:“当超时在5 . 8 0 2秒后发生时,计算当前的 RTO 值为
RTO = A + 4 D = 0 + 4×3 = 12 s
因此,应用于 RTO 的指数退避取为 1 2。由于这是第 1次超时,我们使用倍数 2,因此下一个超时时间取值为 2 4秒。”
它指出,图4.5中SYN的第一次重传是在6秒后:
RTO = A + 2D = 0 + 2x3 = 6 seconds
这个还可以理解。
但接下来不是应该隔12秒就发生第二次重传么,为什么书上说是24秒(图4.5也是24秒)?
RTO = A + 4D = 0 + 4x3 = 12 seconds