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

TCP 握手只有三种结局:接通、被拒、超时。这三种要先分清,再谈加密。 HTTPS 多的那一层在握手之后——代价是额外的往返,不是额外的字节。

📝 本节和前面几节的关系

1.2.6 讲的是「什么都在联网」,这一节讲「联网之后怎么保证不被偷看」。 1.2.8 会收束到「为什么偏偏是 TCP/IP 赢了」,给整个 1.2 节做个总结。

一、握手只有三种结局

任何一次 TCP 连接尝试,结局只有三种,分清楚这三种是排障的第一步。

结局 对端做了什么 本机拿到什么
接通 回了 SYN+ACK 连接建立
被拒 回了 RST ConnectionRefusedError
超时 什么都没回 TimeoutError

第二行那个 RST 是「明确的拒绝」:包到了、机器活着、但那个端口没人听。 第三行那个「什么都没回」可能是三种情况:路径不通、被防火墙静默丢弃、或者对端太忙。 三种情况从本机看完全一样,所以只能靠「换目标试」来缩小范围。 这个区别在排障时非常关键:被拒说明方向对、服务没开;超时说明方向可能就不对。

二、为什么「被拒」也可能很慢

这一点反直觉:RST 本该是毫秒级的事,但实测常常要等一两秒。 原因是 SYN 重传:第一个 SYN 丢了(或被拦),要等超时重传,第二个才拿到 RST。

情形 耗时量级 原因
一次就拿到 RST 毫秒 对端立即回
重传后才拿到 1~3 秒 第一个 SYN 丢了
一直被静默丢弃 到超时上限 防火墙 DROP

第二行就是本机 Demo 里出现的那个数:连本机没监听的端口,等了 2 秒才被拒。 所以「被拒」与「超时」不能靠耗时区分, 必须看错误码——这也是写网络代码时不要「靠时间猜结果」的原因。

三、HTTPS 多出来的那几步

TCP 握手完成时,通道是通的,但内容是明文。 HTTPS 在这之后再加一段 TLS 握手:

协议 总往返数 多出来的是什么
HTTP 1 ——
HTTPS(TLS 1.2) 3 2 个往返:密钥协商 + 完成
HTTPS(TLS 1.3) 2 1 个往返:密钥协商与完成合并

这两行数字是规范规定的,不是本机实测——要数准得抓完整的握手过程。 TLS 1.3 把两次往返并成一次, 省的不是字节,是往返次数——而往返次数乘上单程时延,就是用户感知到的「打开慢」。 所以跨洋访问时,TLS 1.3 的体感差别比 TLS 1.2 明显得多: 一次往返 150 毫秒,省一次就是省 150 毫秒。

四、动手 Demo:从【单纯建立连接】到【安全建立连接】

把三种结局都量出来:本机已监听的端口(接通)、本机没监听的端口(被拒)、外网 443(接通)。 然后对比 HTTP 与 HTTPS 的往返次数——那部分是规范值,不是实测值,正文里写清楚了。

import socket, threading
import _netsim as N

srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
srv.bind(('127.0.0.1', 0))
srv.listen(4)
okport = srv.getsockname()[1]

def acc():
    while True:
        try:
            c, _ = srv.accept()
        except OSError:
            return
        c.close()

threading.Thread(target=acc, daemon=True).start()

tmp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tmp.bind(('127.0.0.1', 0))
deadport = tmp.getsockname()[1]
tmp.close()

for tag, host, port in (('本机已监听', '127.0.0.1', okport),
                        ('本机没监听', '127.0.0.1', deadport),
                        ('外网 443', 'www.python.org', 443)):
    dt, st = N.tcp_connect_ms(host, port, timeout=4.0)
    print('%-10s %-14s %.1f ms' % (tag, st, dt))
srv.close()

实跑输出:

--------------------------------------------------------------------
从「单纯建立连接」到「安全建立连接」
--------------------------------------------------------------------
**同一个端口,三次握手只有三种结局:接通、被拒、超时。**
**先把这三种结局都量出来,再谈加密。**

  对象           结局            耗时ms
  --------------------------------------
  本机已监听     接通            0.4
  本机没监听     被拒(RST)     2023.0
  外网 443       接通            158.4

**先看一个反直觉的数**:连「本机没监听」的端口,也等了 2023 毫秒才被拒。**
**毫秒级拒绝只是理想情况**:这次的 SYN 重传了一次才拿到 RST,所以时间一下子涨到两秒。

**所以「被拒」与「超时」不能靠耗时区分,必须看错误码。**
**被拒拿到的是 `ConnectionRefusedError`,超时拿到的是 `TimeoutError`。**

**这两个错误码对应两种完全不同的现场**:
**被拒说明包到了、对面没开服务(或防火墙主动回 RST);超时说明包根本没到,或被静默丢弃。**
**方向相反,处理方式也相反。**

**上面量到的都只是 TCP 握手**,此时数据还是明文。HTTPS 比 HTTP 多的那一层在握手之后:

  协议                建立连接要几个往返
  -------------------------------------------
  HTTP 明文           1 个(TCP)
  HTTPS + TLS 1.2     3 个(TCP + 2 个 TLS)
  HTTPS + TLS 1.3     2 个(TCP + 1 个 TLS)

**这两个数是规范规定的往返次数,不是本机实测**——要数准得抓完整的握手过程,本机没有抓包工具。

**「安全建立连接」的代价就是这几下额外的往返。**
**所以 TLS 1.3 把两次并成一次,不是为了省字节,是为了省往返**——
**往返本身有传播时延,跨洋一次就是一百多毫秒。**

表里「本机没监听」那一行是 2 秒级的,比「接通」慢了两个数量级——这就是 SYN 重传的代价。 外网 443 那行是几十到一百多毫秒,主要花在两段链路的传播时延上,不是协议栈的开销。 把这三个数放在一起看,能得出一个实用结论: 「慢」有三种完全不同的原因——重传、传播、排队;对应三种完全不同的处理方式。

30 秒实验

  1. 把 timeout 从 4 秒改成 1 秒,看「本机没监听」那一行会不会变成超时。
  2. 试着连一个不存在的外网 IP(如 192.0.2.1), 看是超时还是被拒——这能验证「静默丢弃」与「明确拒绝」的区别。
  3. 查一下 TLS 1.3 的握手过程,想想为什么它能比 1.2 少一个往返。

常见误区

  1. 以为「被拒」一定是毫秒级。 SYN 重传会让它变成秒级, 所以不能用耗时区分「被拒」与「超时」,要看错误码。
  2. 以为 HTTPS 慢是因为「加密计算多」。 主要成本是额外的往返, 不是加解密运算——AES 在现代 CPU 上有硬件指令,很快。
  3. 以为 TLS 1.3 只是「更快一点」。 它砍掉了一整个往返,跨洋访问时这是实打实的体感差别。
自测三问(点开对答案) 1. TCP 握手的三种结局分别拿到什么错误? —— **接通**:连接建立;**被拒**:`ConnectionRefusedError`(收到 RST); **超时**:`TimeoutError`(什么都没收到)。 2. 为什么连本机没监听的端口也要等 2 秒? —— **因为第一个 SYN 丢了,触发了重传**,第二次才拿到 RST。 **说明「被拒」的耗时不可靠,不能用来判断原因。** 3. HTTPS 比 HTTP 多出来的成本主要是什么? —— **主要是额外的往返次数**(TLS 1.2 多 2 次、1.3 多 1 次)。 加解密本身有硬件加速,**不是主要瓶颈**。

下一节收束整个 1.2 节:为什么最后活下来的是 TCP/IP,而不是别的协议栈。

相关笔记