来自我的公众号 『 YongHao 写东西的 Cache 』 打个小广告,还是希望写的东西有人看🙊
分享一下见解,权当抛砖引玉
关于 TCP 三次握手几乎是应届毕业生面试常见的问题了,然而网上还很多比比皆是的错误,以知乎 TCP 为什么是三次握手,而不是两次或四次? 上的热门答案为例子,第一个 3.6K 次赞同的类比就是错误的:
三次握手:
“喂,你听得到吗?”
“我听得到呀,你听得到我吗?”
“我能听到你,今天 balabala ……”
同样这个 107 次赞同的类比也是错误的:
握手和敬军礼一样,源自「敌我双方互相确认对方手里没有武器、无恶意」的仪式。(虽然双方互相请求确认需要四步,但由于中间的确认和请求是由同一个人执行的,所以合并成了一步)
正恩伸出手说:你看,我手里没有武器。( SYN )
朗普看了看说:嗯,确实没有。( ACK )
于是也伸出手说:你看,我手里也没有武器。( SYN )
正恩看了看说:嗯,看来你确实有诚意。( ACK )
这两个类比就是想当然的错误,为什么会错误,看完全文相信你便了然于心。
另外还有一个就是在谢希仁著《计算机网络》第四版中,讲 “三次握手” 的目的是 “为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误”,这个只能算是表因,并不涉及本质。因为就算是三次握手,也会遇到他提到旧报文姗姗来迟的情况,此情况与 SYN FLOOD
大同小异。
谢希仁版《计算机网络》中的例子是这样的,“已失效的连接请求报文段” 的产生在这样一种情况下:client 发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达 server。本来这是一个早已失效的报文段。但 server 收到此失效的连接请求报文段后,就误认为是 client 再次发出的一个新的连接请求。于是就向 client 发出确认报文段,同意建立连接。假设不采用 “三次握手”,那么只要 server 发出确认,新的连接就建立了。由于现在 client 并没有发出建立连接的请求,因此不会理睬 server 的确认,也不会向 server 发送数据。但 server 却以为新的运输连接已经建立,并一直等待 client 发来数据。这样,server 的很多资源就白白浪费掉了。采用 “三次握手” 的办法可以防止上述现象发生。例如刚才那种情况,client 不会向 server 的确认发出确认。server 由于收不到确认,就知道 client 并没有要求建立连接。”
如果你细读RFC793,也就是 TCP 的协议 RFC,你就会发现里面就讲到了为什么三次握手是必须的—— TCP 需要 seq 序列号来做可靠重传或接收,而避免连接复用时无法分辨出 seq 是延迟或者是旧链接的 seq,因此需要三次握手来约定确定双方的 ISN (初始 seq 序列号)。
下面给出详细的 RFC 解读说明:(数据分组称为分段( Segment ),国内通常用包来称呼)
我们首先要知道到一点就是,TCP 的可靠连接是靠 seq ( sequence numbers 序列号)来达成的。
A fundamental notion in the design is that every octet of data sent over a TCP connection has a sequence number. Since every octet is sequenced, each of them can be acknowledged.
The acknowledgment mechanism employed is cumulative so that an acknowledgment of sequence number X indicates that all octets up to but not including X have been received.
TCP 设计中一个基本设定就是,通过 TCP 连接发送的每一个包,都有一个 sequence number。而因为每个包都是有序列号的,所以都能被确认收到这些包。
确认机制是累计的,所以一个对 sequence number X 的确认,意味着 X 序列号之前(不包括 X) 包都是被确认接收到的。
The protocol places no restriction on a particular connection being used over and over again.
The problem that arises from this is -- "how does the TCP identify duplicate segments from previous incarnations of the connection?" This problem becomes apparent if the connection is being opened and closed in quick succession, or if the connection breaks with loss of memory and is then reestablished.
TCP 协议是不限制一个特定的连接(两端 socket 一样)被重复使用的。
所以这样就有一个问题:这条连接突然断开重连后,TCP 怎么样识别之前旧链接重发的包?——这就需要独一无二的 ISN (初始序列号)机制。
When new connections are created, an initial sequence number (ISN) generator is employed which selects a new 32 bit ISN. The generator is bound to a (possibly fictitious) 32 bit clock whose low order bit is incremented roughly every 4 microseconds. Thus, the ISN cycles approximately every 4.55 hours. Since we assume that segments will stay in the network no more than the Maximum Segment Lifetime (MSL) and that the MSL is less than 4.55 hours we can reasonably assume that ISN's will be unique.
当一个新连接建立时,初始序列号( initial sequence number ISN )生成器
会生成一个新的 32 位的 ISN。
这个生成器会用一个 32 位长的时钟,差不多4µs
增长一次,因此 ISN 会在大约 4.55 小时循环一次
(2^32
位的计数器,需要2^32*4 µs
才能自增完,除以 1 小时共有多少µs 便可算出2^32*4 /(1*60*60*1000*1000)=4.772185884
)
而一个段在网络中并不会比最大分段寿命( Maximum Segment Lifetime (MSL) ,默认使用 2 分钟)长,MSL 比 4.55 小时要短,所以我们可以认为 ISN 会是唯一的。
发送方与接收方都会有自己的 ISN (下面的例子中就是 X 与 Y )来做双方互发通信,具体的描述如下:
- A --> B SYN my sequence number is X
- A <-- B ACK your sequence number is X
- A <-- B SYN my sequence number is Y
- A --> B ACK your sequence number is Y
2 与 3 都是 B 发送给 A,因此可以合并在一起,因此成为three way (or three message) handshake
(其实翻译为三步握手,或者是三次通信握手更为准确)
因此最终可以得出,三次握手是必须的:
A three way handshake is necessary because sequence numbers are not tied to a global clock in the network, and TCPs may have different mechanisms for picking the ISN's. The receiver of the first SYN has no way of knowing whether the segment was an old delayed one or not, unless it remembers the last sequence number used on the connection (which is not always possible), and so it must ask the sender to verify this SYN. The three way handshake and the advantages of a clock-driven scheme are discussed in [3].
三次握手( A three way handshake )是必须的, 因为 sequence numbers (序列号)没有绑定到整个网络的全局时钟(全部统一使用一个时钟,就可以确定这个包是不是延迟到的)以及 TCPs 可能有不同的机制来选择 ISN (初始序列号)。
接收方接收到第一个 SYN 时,没有办法知道这个 SYN 是是否延迟了很久了,除非他有办法记住在这条连接中,最后接收到的那个 sequence numbers (然而这不总是可行的)。
这句话的意思是:一个 seq 过来了,跟现在记住的 seq 不一样,我怎么知道他是上条延迟的,还是上上条延迟的呢?
所以,接收方一定需要跟发送方确认 SYN。
假设不确认 SYN 中的 SEQ,那么就只有:
- A --> B SYN my sequence number is X
- A <-- B ACK your sequence number is X SYN my sequence number is Y
只有 B 确认了收到了 A 的 SEQ,A 无法确认收到 B 的。也就是说,只有 A 发送给 B 的包都是可靠的, 而 B 发送给 A 的则不是,所以这不是可靠的连接。这种情况如果只需要 A 发送给 B,B 无需回应,则可以不做三次握手。
TCP A TCP B
1. CLOSED LISTEN
2. SYN-SENT --> <SEQ=100><CTL=SYN> --> SYN-RECEIVED
3. ESTABLISHED <-- <SEQ=300><ACK=101><CTL=SYN,ACK> <-- SYN-RECEIVED
4. ESTABLISHED --> <SEQ=101><ACK=301><CTL=ACK> --> ESTABLISHED
5. ESTABLISHED --> <SEQ=101><ACK=301><CTL=ACK><DATA> --> ESTABLISHED
Basic 3-Way Handshake for Connection Synchronization
Figure 7.
在上图
1
liuminghao233 2019-01-07 13:43:26 +08:00 via iPhone
支持
|
2
e9e499d78f 2019-01-07 13:57:24 +08:00 via iPhone
所以哪里错了呢?
|
3
petelin 2019-01-07 14:16:54 +08:00
所以人家哪里错了呢? 三次握手不就是为了对方都确认能 "听到"1, 自己, 对方 "有诚意"2 吗?
|
4
swulling 2019-01-07 14:21:34 +08:00 via iPhone
看完了,所以知乎哪里错了?没有了然于心。
|
5
ChristopherWu OP |
6
m3dull 2019-01-07 15:27:22 +08:00
厉害,剖析的清楚,比看单纯的例子强多了!
|
7
ChristopherWu OP @m3dull 谢谢评论~觉得写的还行,可以关注公众号🙊
|
8
zhze93 2019-01-07 15:44:55 +08:00
支持一下
|
9
VictorJing94 2019-01-07 15:51:19 +08:00
mark
|
10
mrgblues 2019-01-07 15:51:31 +08:00
感谢分享,文章的格式很舒服。
顺便问下下面这两个地方的半字的括号是手误吗?文章中的中英混合写作有参照某种通用规则吗?像中英混合什么时候加空格,想了解下。 “ 三次握手的误解与错误类比(RFC 解读)” “意味着 X 序列号之前(不包括 X) 包都是被确认接收到的” |
11
swulling 2019-01-07 16:00:19 +08:00
|
12
ChristopherWu OP @mrgblues 不算手误,看着舒服就用了- -。具体排版规则我不清楚,没有学过 - -
|
13
ChristopherWu OP @swulling 对概念的 『正确性』苛求一点,是很有好处的,所谓差之毫厘谬以千里。
就如常见的例子: md5 是加密算法,你也可以说是『不够详细』或者说『不完备』的,但就是错的。md5 勉强可以做加密,但本质上就是不可逆的哈希算法。 再如之前 v 站批判过的『粘包』 的这个国内特有概念,你也不能说这个是『错误的』,但苛求来说,就不应该存在这样的概念。 |
14
radiolover 2019-01-07 16:53:25 +08:00 12
讲了半天,可惜一直没有讲到点子上,因为程序员不是学通信的。
在通信会话中,传达一条信息至少要两步通信:A->B 的消息,B->A 的 ACK。如果多接触一些其他行业的通信流程,例如航空、铁路调度,就会明白这一点。 TCP 建联,本质上需要传递两条信息:A->B 的初始 SYN 号,B->A 的初始 SYN 号,那么理论上需要四步通信( A->B 的初始 SYN 号,B->A 的 ACK ; B->A 的初始 SYN 号,A->B 的 ACK );只不过为了效率和性能,中间两条消息可以合并为一条,这便是现在的三路握手的来源。后续的所有报文,本质上都是信息+ACK,和消息合并。 至于其他各种所谓“容错”,通信原理已经阐述的很明白,至少两步通信已经覆盖了 99%的容错机制,但由于两军问题,信道不可能做到 100%的可用性。 |
15
radiolover 2019-01-07 17:07:42 +08:00 1
所以说,楼主说的没错,目前中文互联网的所谓“原因分析”都是皮肉之象,什么“防止包乱序及可能的重连”、“确认对方有收和发的能力”.....,这些都不是问题的本质,本质还是要从通信原理寻找答案。
|
16
e9e499d78f 2019-01-07 17:18:12 +08:00 via iPhone
楼主说那么多不能说明那个比方是错的,吹毛求疵了。
楼上还扯什么的 SYN 号、防止包乱序,也是一样的问题 |
17
timothyqiu 2019-01-07 17:20:27 +08:00
我是第二个类比的作者。「确保对方无恶意」与「确保对方收到 seq 」是对应的,两者都是后续通信的基础。
|
18
petelin 2019-01-07 17:31:57 +08:00
@radiolover
@ChristopherWu 我知道你们说的是对的,而且更容易理解(比喻对理解其实没啥用), 但是知乎那种比喻, 本质上是没有错的. 本质不就是交换信息吗, 然后要确保对方收到了这个信息. |
19
ChristopherWu OP @radiolover 我说得长是因为还要说明 :为什么需要 seq,如何以及为什么需要构造一个独特的 seq 等。
|
20
lhx2008 2019-01-07 17:58:00 +08:00 via Android 1
那如果 TCP 规定同时只能建立一条连接,那 SEQ 从 0 开始就可以了,甚至和 UDP 一样不需要握手。通过增长的 SEQ,仍然可以保证不丢包。
所以三次握手本质上还是为了建立多个连接而需要传递初始 SEQ 造成的? |
21
linKnowEasy 2019-01-07 17:59:58 +08:00
用类比的方式解释比较专业的知识时, 估计都会出现楼主说的不够正确问题. 我个人认为, 类比去解释事物的时候, 得看类比的两个本质是否有关键的一致性, 其他重要但非关键的信息在类比例子中不合适, 是可以接受的, 那这个类比就是成功的
|
22
lhx2008 2019-01-07 18:00:47 +08:00 via Android
谢希仁的意思就是 TCP 是可以同时建立多条连接而不用三次握手的后果,也没有什么问题。
|
23
lhx2008 2019-01-07 18:02:36 +08:00 via Android
但是 TCP 同时建立多条连接的意义大吗?毕竟一个端口一般都只起一条链接。
|
24
ChristopherWu OP @hx2008
>所以三次握手本质上还是为了建立多个连接而需要传递初始 SEQ 造成的? 对,你的理解是对的。 我的文中也说道了: 『 TCP 需要 seq 序列号来做可靠重传或接收,而避免连接复用时无法分辨出 seq 是延迟或者是旧链接的 seq,因此需要三次握手来约定确定双方的 ISN (初始 seq 序列号)。』 但现实没有这么理想,因为 seq 是递增的话,要考虑到被人攻击(所以现代 seq 的选择也是比较复杂的,比我说的用计时器递增还要复杂些,如可能用加密,随机选择等方式。) |
25
PureWhiteWu 2019-01-07 18:14:10 +08:00
一个叫做“不完善”,一个叫做“错误”,不完善≠错误,希望楼主能认清这个道理。
知乎的回答用了一种让人易于理解的方法来阐述了这个原理,虽然不完善,但是也谈不上错误。 楼主不要总想着 diss 别人来搞个大新闻。 |
26
Alphkiee 2019-01-07 18:17:58 +08:00 via Android
刚好我是应届生,也有这个需要,谢谢楼主
|
27
lhx2008 2019-01-07 18:23:17 +08:00 via Android
被人攻击会怎么样呢,UDP 也正常存在呀,甚至 HTTP 也可以基于 UDP 来做了。感觉这是个很大的脑洞。一个客户端接口同时建立多个 TCP 连接我认为意义也不大。完全可以通过其他机制复用。所以我现在又迷惑了
@ChristopherWu |
28
lhx2008 2019-01-07 18:28:14 +08:00 via Android
如果这样理解,知乎的类比确实是错的,我可以写另外一个类比来描述不需要三次握手直接通信情况
‘小明,我需要一个文件’ ..... 没有回应(丢包) ‘小明,我需要一个文件’ ‘这是文件的第一页’ ‘我收到了第一页’ ....... |
29
iwtbauh 2019-01-07 18:38:46 +08:00 via Android
赞同#13
除了“粘包”,“ hash ”和加密( encrypt )不分之外 同理还有 “ CC 攻击” -- 明明就是 DDoS 的一种,而且也根本没有 cc 这种说法(见 unp ) “ ppt ” -- 什么时候 ppt 一个软件能代表全部演示文稿了 “压缩“( compress )和”归档“( archive ) 傻傻分不清楚 “ Linux ”和“ GNU/Linux ” 傻傻分不清楚或者故意不区分 等等 中文技术圈里就该多一些 lz 这样的人 |
30
lhx2008 2019-01-07 18:43:19 +08:00 via Android
我刚刚看了 QUIC 协议,应该就是基于 UDP 取消了三次握手,且没有再造轮子做握手,所以是不是可以得出一个总结:
TCP 三次连接是为了传输初始 seq,双方交换信息本来需要四步,但是可以合并其中两步,所以交换这个信息最少需要三步,两步是不行的 但是,三次握手不是建立可靠信道的必须条件,他只是 TCP 协议选择的一个方法罢了。 |
31
zarte 2019-01-07 18:49:31 +08:00
不要总想搞个大新闻。
类比要的就是通俗易懂,第一个例子本身对话就包含了你所谓的传 seq。要知道打电话信号不好你就是这样确认的。 |
32
ChristopherWu OP @lhx2008 三次握手是建立可靠信道的必要条件。
还有一点我说得不清楚,不是你说的『 TCP 同时建立多条连接的意义』,而是 TCP 可以复用链接。在这种情况下,旧的包也许还在途,但是复用的新连接又有一个新包来了,因此需要 seq 来识别此情况。 |
33
lhx2008 2019-01-07 18:56:52 +08:00 via Android
@ChristopherWu 假设我跟你用 25555 端口建立了一个连接,我会一会用 seq=1555,一会用 seq=44646 同时给你维护两条 TCP 连接给你发包吗。如果你想这么干不是通常会在两个端口上建两条 TCP 连接。
编程语言的 socket 都没有这么玩的吧。 |
34
ChristopherWu OP @lhx2008 @zarte @PureWhiteWu
刚好 @linKnowEasy 你提醒我了,我原来在想这篇文章时,是想举一个更加准确的类比来说明那个类比是不对的。 我就此举一下: TCP 传递信息可以理解为美国与中国用货船来传货物,但因为一首轮船穿放不下,货物要分开一只只轮船来发货。 所以需要一个序列号来识别该货物是第几个,以便到达后将其拼接回原来的货物。 因为同一条航道(也就是 tcp 连接)上,可能会有多批货物发送(复用 tcp 连接)。发货时,双方需要通知对方这个序列号是从哪里开始( init seq )的,这样才能辨识过来的是不是一个对的货物,以及能拼接成完整的货物。 货物运输拼接( tcp )最重要的是可靠性,如果没有用三次握手来确认双方都可以获得对方的 序列号( seq )的话,就无法知道当前航班(连接)中,对的货物序号是怎么样的了。 ***粗略的写了写,这个类比可以说明为什么我文中两个类比是错误的。*** |
35
Lipshades 2019-01-07 19:13:12 +08:00
mark 一下
|
36
linKnowEasy 2019-01-07 19:13:14 +08:00
@ChristopherWu 你的类比, 按照你的理论, 也是"不正确", 货船会出现空跑的情况 /或者对方码头没有准备好的情况,
我这么说, 只想表达, 类比有局限性, 正确与否只看类比的关键信息是否有极高的相似性, 对于不了解专业知识的人来说, 序号不是关键信息, 互相通知准备完成才是关键信息 |
37
yiyi11 2019-01-07 19:30:30 +08:00 via Android
谢谢楼主,写得很详细。
|
38
FrankAdler 2019-01-07 19:31:01 +08:00
请程序员不要随便举例子
|
39
letianqiu 2019-01-07 19:58:13 +08:00
@ChristopherWu 你说的复用不就是谢希仁举的例子。
|
40
ChristopherWu OP @linKnowEasy 那你对比一下我举出来的类比,以及文中列出的两个类比,看看区别?
>序号不是关键信息, 互相通知准备完成才是关键信息 恰恰相反,我想说明标注的就是:序号才是关键信息。 准备不准备好,只是影响连接是否建立完成。 序列号是否同步到,才影响连接上的数据是否完备,可靠。 |
41
ChristopherWu OP @letianqiu 恩,是的。复用就是谢希仁举的例子,但是他的例子只是说了缺点是:连接被多开,而没有用到。但是 SYN FLOOD 一样会有这样的问题,所以核心不是旧的连接信息来到,开了一条没有用的连接。
核心是,旧的 seq 过来,tcp 要识别到此 seq 是否有效。 |
42
tonyl4 2019-01-07 20:59:21 +08:00
mark
|
43
linKnowEasy 2019-01-07 21:41:16 +08:00
@ChristopherWu 我明白你想表达序号是关键, 但是对不了解专业知识的人, 你说明了序号, 你还要解释需要的作用.
这些一些信息其实可以包含在准备确认里面, 我认为一般做类比的时候, 只会突出一个关键信息, 相比于 序号, 问候的类比更能让非专业知识的人明白, 如果是知道相关知识的人, 这样的类比是不恰当的, 但面向对象是不了解相关知识, 所以我觉得这个类比没问题, 你可以和具有相关知识的人讨论认为他 "不正确" 我仍然觉得这个类比对不知道相关知识的人来说, 是恰当的, 正确的 |
44
kaneg 2019-01-07 22:00:14 +08:00 via iPhone 1
TCP 主要解决了网络通信的可靠性和有序性这两个问题。三次握手是为了保证链接建立的可靠性,而楼主提到的 seq 主要是为了解决有序性的。
所以说知乎上的说法并没有错,只是侧重点不同而已。 |
45
ChristopherWu OP @kaneg 总结得很好
|
46
lhx2008 2019-01-07 23:34:51 +08:00
@ChristopherWu
我重新想了下,建立可靠信道确实必须要三次握手,也就是交换一个初始随机 seq 但是这么做的主要原因是**防止第三方攻击** 这里有回答: https://www.zhihu.com/question/34400902 “ 有 A B 之间的正常连接 C 可以在世界的任何角落,伪造一个合法 TCP 报文,最关键是 TCP 字段里的 sequence number、acknowledged number,只要这两项位于接收者滑动窗口内,就是合法的,对方可以接收并 Reset A、B 之间的 TCP 连接。 ” 三次握手就是为了防止第三者强行通过修改 IP 包 而可以闯入滑动窗口进行攻击。另外,也可以防止反射放大攻击。UDP 的反射放大攻击在 TCP 上面也不适用,最重要的原因就是 TCP 有三次握手。 至于信道复用的说法,我还真没听说,因为按 socket 四元组理解一个 TCP 连接 的话,没有楼主说的信道复用的可能。 QUIC 协议 也有类似的三次握手机制,他是通过交换一个 token 来防止攻击,在文档中有写到 主要目的是为了防止反射放大攻击 Address validation is used by QUIC to avoid being used for a traffic amplification attack. In such an attack, a packet is sent to a server with spoofed source address information that identifies a victim. If a server generates more or larger packets in response to that packet, the attacker can use the server to send more data toward the victim than it would be able to send on its own. Token 生成的原则: An address validation token MUST be difficult to guess. Including a large enough random value in the token would be sufficient, but this depends on the server remembering the value it sends to clients. A token-based scheme allows the server to offload any state associated with validation to the client. For this design to work, the token MUST be covered by integrity protection against modification or falsification by clients. Without integrity protection, malicious clients could generate or guess values for tokens that would be accepted by the server. Only the server requires access to the integrity protection key for tokens. There is no need for a single well-defined format for the token because the server that generates the token also consumes it. A token could include information about the claimed client address (IP and port), a timestamp, and any other supplementary information the server will need to validate the token in the future. 因为 QUIC 必须先进行 TLS 握手,所以不用担心会伪造序列号攻击 至于谢希仁说的,主要还是为什么要三次握手而不是两次握手,#14 @radiolover 说的比较透彻 |