📚 第一章 网络基础知识
📄 一句话概括

有连接与无连接的分界只有一条:传数据之前要不要先握手。 有连接不等于可靠,无连接也不等于不可靠——「连接」只是两端各存了一份状态。

📝 本节和前面几节的关系

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 秒实验

  1. 把 UDP 改成先 connect 再 send,看耗时变不变——它仍然不握手,只是把目的地址记下来了。
  2. 把 TCP 那次跑 100 遍取平均,看回环握手到底要多少毫秒。
  3. 想一下:为什么视频会议软件明明用 UDP,却还要自己做一层确认与重传?

常见误区

  1. 以为「有连接」意味着有一条物理通路。 它只是两端各存了一份状态,网线断了状态还在, 连接却已经不通。
  2. 以为 UDP 不可靠就会丢包。 「不可靠」指不提供保证,局域网里可能一个都不丢。
  3. 以为重要业务就该用 TCP。 要看能不能容忍延迟:实时语音用 TCP 反而更糟, 一次重传就能把节奏打乱。
自测三问(点开对答案) 1. TCP 握手到底在交换什么? —— **双方的初始序号、窗口大小、MSS。** 没有这三个数,确认与重传都无从谈起, 所以握手是「有连接」能力的前提。 2. 为什么说「TCP 有连接」不等于「TCP 一定可靠」? —— **因为连接只是两端内存里的状态,不是物理事实。**网线一断,状态还在但数据发不出去, 要等超时才报错。 3. 实时语音为什么用 UDP? —— **因为晚到的数据没用**:重传会让延迟进一步变大,还不如让它丢。这是取舍,不是放弃可靠性。

下一节换另一个角度:电路交换与分组交换——一个先修路再走,一个不修路直接扔。

相关笔记