有连接与无连接的分界只有一条:传数据之前要不要先握手。 有连接不等于可靠,无连接也不等于不可靠——「连接」只是两端各存了一份状态。
1.6 讲完了「一层一层怎么送」,1.7 换三个角度看传输方式。 这一节是有/无连接,1.7.2 是电路/分组,1.7.3 是按接收端数量分。
一、差别就在握不握手
同样发 100 字节,两条路的动作完全不同:
| 步骤 | TCP | UDP |
|---|---|---|
| 传之前 | 三次握手 | 什么都不做 |
| 传输中 | 编号、确认、重传 | 发完就不管 |
| 传之后 | 四次挥手 | 无 |
| 头部 | 20 字节 | 8 字节 |
第一行那个握手不是客套,是为了交换三个数:双方的初始序号、双方的窗口大小、双方的 MSS。 没有这三个数,「确认」和「重传」都无从谈起。 所以握手是「有连接」这个能力的前提,不是可选项。 第二行是两者的本质区别:TCP 要保证「发出去的每个字节都到了、而且顺序对」, UDP 只保证「我尽力发了」。 这不代表 UDP 不可靠——在局域网里它的丢包率可能低到看不见,只是它不负责检测。
二、有连接不等于可靠
这句话要拆开看,因为它反直觉:
| 说法 | 对不对 | 为什么 |
|---|---|---|
| TCP 有连接 | 对 | 两端各存一份状态 |
| TCP 一定可靠 | 不对 | 网线一断,状态还在但路不通 |
| UDP 无连接 | 对 | 不存状态 |
| UDP 不可靠 | 看情况 | 局域网里可能一个不丢 |
第二行是本节最该记住的一条:TCP 的「连接」是逻辑概念,不是物理事实。 拔掉网线的那一瞬间,两端的连接状态还在内存里,但数据已经发不出去了。 要等到超时重传失败,它才知道「断了」——这就是为什么断了网的程序会「卡住一会儿」才报错。 第四行是另一半:「不可靠」说的是「不提供可靠性保证」,不是「一定丢」。 所以实时音视频用 UDP 不是因为「不在乎丢」,而是因为「重传来不及,不如让它丢」。 这个区别很关键:前者是放弃,后者是取舍。
三、怎么选
按「能不能容忍延迟」来选,不是按「重不重要」:
| 场景 | 选什么 | 理由 |
|---|---|---|
| 网页、文件、邮件 | TCP | 一个字节都不能少 |
| 直播、通话、游戏 | UDP | 晚到的数据没用 |
| DNS 查询 | UDP | 一问一答,重发一次就好 |
| 设备发现 | UDP 组播 | 不需要确认 |
第二行那个「晚到的数据没用」是实时通信的核心: 一段语音的第 100 毫秒数据,如果第 500 毫秒才到,它不但没用,还会打乱播放节奏。 所以 UDP 的选择不是「省事」,是「重传在这里是负收益」。 第三行是常见的细节:DNS 主用 UDP,但应答超过 512 字节时会改用 TCP(或者用 EDNS0 扩展)。 所以「DNS 是 UDP」这句话只对一半,这也是看书时要留神的地方:书上的结论往往有个前提。
四、动手 Demo:有连接与无连接,差在「有没有握手」
同一份 100 字节,TCP 与 UDP 各发一次,量耗时与头部。 注意这两个耗时不是同一件事,后面会讲清。
import socket, threading, time
srv = socket.socket()
srv.bind(('127.0.0.1', 0))
srv.listen(5)
port = srv.getsockname()[1]
def acc():
c, _ = srv.accept()
c.recv(200)
c.sendall(b'y' * 100)
c.close()
threading.Thread(target=acc, daemon=True).start()
t0 = time.perf_counter()
c = socket.create_connection(('127.0.0.1', port), timeout=2)
c.sendall(b'x' * 100)
c.recv(200)
tcp_ms = (time.perf_counter() - t0) * 1000
c.close()
srv.close()
u = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
u.settimeout(2)
t0 = time.perf_counter()
u.sendto(b'x' * 100, ('127.0.0.1', 9))
udp_ms = (time.perf_counter() - t0) * 1000
u.close()
print('TCP 建连+收发 %7.3f 毫秒 头 20 字节' % tcp_ms)
print('UDP 仅入队 %7.3f 毫秒 头 8 字节' % udp_ms)
实跑输出:
--------------------------------------------------------------------
有连接与无连接,差在「有没有握手」
--------------------------------------------------------------------
**TCP 要先握手再传数据,UDP 直接扔。**下面同一份 100 字节,两种方式各发一次,量耗时。
方式 建连 耗时毫秒 头部字节
--------------------------------------------------
TCP **要三次握手** 0.473 20
UDP **不握手** 0.325 8
**UDP 那一行的耗时只是「本机把包放进内核队列」的时间**,
**根本没等对方**——所以它比 TCP 小一个量级是必然的。
**有连接不等于可靠,无连接不等于不可靠**:**TCP 的「连接」只是两端各存了一份状态**(序号、窗口),
**网线一断,这两份状态都还在,连接却已经不通了**——**所以「连接」是个逻辑概念,不是物理事实。**
TCP 那一行包含三次握手、发数据、收应答、关连接的全过程; UDP 那一行只包含「把包放进内核队列」, 根本没等对方——所以它比 TCP 小是必然的,而且这两个数不能直接比。 这个对比的价值不在数值,在「量的是什么」: 「有连接」意味着一次往返的等待,「无连接」意味着零等待。 这就是为什么高频小包场景必须用 UDP——每次省一个往返,累积起来就是数量级的差别。 注意 UDP 那一行是发往 9 号端口(discard):没人接收也不会报错, 因为 UDP 没有「连接失败」这个概念,只有「发送缓冲区满了」才会出错。
30 秒实验
- 把 UDP 改成先
connect再send,看耗时变不变——它仍然不握手,只是把目的地址记下来了。 - 把 TCP 那次跑 100 遍取平均,看回环握手到底要多少毫秒。
- 想一下:为什么视频会议软件明明用 UDP,却还要自己做一层确认与重传?
常见误区
- 以为「有连接」意味着有一条物理通路。 它只是两端各存了一份状态,网线断了状态还在, 连接却已经不通。
- 以为 UDP 不可靠就会丢包。 「不可靠」指不提供保证,局域网里可能一个都不丢。
- 以为重要业务就该用 TCP。 要看能不能容忍延迟:实时语音用 TCP 反而更糟, 一次重传就能把节奏打乱。
自测三问(点开对答案)
1. TCP 握手到底在交换什么? —— **双方的初始序号、窗口大小、MSS。** 没有这三个数,确认与重传都无从谈起, 所以握手是「有连接」能力的前提。 2. 为什么说「TCP 有连接」不等于「TCP 一定可靠」? —— **因为连接只是两端内存里的状态,不是物理事实。**网线一断,状态还在但数据发不出去, 要等超时才报错。 3. 实时语音为什么用 UDP? —— **因为晚到的数据没用**:重传会让延迟进一步变大,还不如让它丢。这是取舍,不是放弃可靠性。下一节换另一个角度:电路交换与分组交换——一个先修路再走,一个不修路直接扔。
- ☐ (本节完,下一节 1.7.2 电路交换与分组交换)