从「一台机器分给多人」到「多台机器互相说话」,多了一样东西:地址。 这一节把这句话量出来:一次 TCP 握手在本机到底要多久。
1.2.2 的分时系统解决的是「一台机器怎么分」,这一节解决「多台机器怎么连」。 1.2.4 会讲这件事怎么变成「计算机网络」这个学科。
一、多出来的是「寻址」
一台机器内部通信,两个进程靠进程号或端口就能找到对方。 跨机器就不行了——得先找到机器,再找到机器上的进程。
| 范围 | 怎么找对方 | 需要什么 |
|---|---|---|
| 同一台机器 | 进程号/端口 | 操作系统内部表 |
| 同一个网段 | MAC 地址 | ARP 广播 |
| 跨网段 | IP 地址 | 路由表 |
第三行那个「路由表」是网络真正诞生的标志: 有了它,两台机器不需要直连也能通信——中间可以隔任意多台设备。 这也是「互联网」这个词的字面意思:网络之间互连(inter-network)。
二、一次握手到底多久
TCP 建立连接要三次握手,这是所有网络编程的起点。 它的耗时由两段组成:
| 组成 | 来源 | 量级 |
|---|---|---|
| 传播时延 | 信号在介质里跑 | 回环≈0,同城 1 毫秒,跨洋百毫秒 |
| 处理时延 | 协议栈拼包解包 | 微秒到毫秒 |
| 排队时延 | 设备忙时等 | 不可预测 |
第一行那个「回环≈0」很重要:回环上测到的就全是处理时延, 所以它只能说明协议栈开销,不能代表网络。 真要测网络,必须跨机器——而跨机器的数受环境影响极大,没法当基准。 这是网络性能测试的根本困难:你测到的永远是你那一段路,不是全路。
三、为什么不用「文件共享」代替
早期确实有人这么干:把文件放在共享目录,别人来读。但它有三个绕不过去的短板:
| 短板 | 表现 | 通信方式怎么解决 |
|---|---|---|
| 实时性 | 文件有轮询间隔 | 长连接推送 |
| 并发 | 同时写会冲突 | 协议层加锁与序号 |
| 异构 | 不同系统路径规则不同 | 统一的报文格式 |
第二行那个「协议层加锁」就是 TCP 序号机制的由来:它保证两端的字节流顺序一致、不重不漏。 所以「计算机之间的通信」不是「文件传输的升级版」,它是一套新的抽象: 把「一堆机器」看成「一张能寻址的网」。 这个抽象一旦成立,上层就不用关心底下有几台设备、走的什么线。
四、动手 Demo:两台计算机通信,一次来回要多久
起一个监听端口,真连上去 30 次,量 TCP 三次握手的耗时。 先预热一次再计时,把第一次的冷启动开销丢掉。 这个 Demo 测的是回环,也就是协议栈本身的开销。
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(8)
port = srv.getsockname()[1]
def acc():
while True:
try:
c, _ = srv.accept()
except OSError:
return
c.close()
threading.Thread(target=acc, daemon=True).start()
N.tcp_connect_ms('127.0.0.1', port) # 预热,丢掉
res = sorted(N.tcp_connect_ms('127.0.0.1', port)[0] for _ in range(30))
print('最小 %.3f 中位 %.3f 最大 %.3f 毫秒'
% (res[0], res[len(res) // 2], res[-1]))
print('最大/最小 %.1f 倍' % (res[-1] / res[0]))
srv.close()
实跑输出:
--------------------------------------------------------------------
两台计算机通信,一次来回要多久
--------------------------------------------------------------------
**「计算机之间的通信」听起来抽象,量一下就很具体**:起一个监听端口,真的连上去,
量 TCP 三次握手用了多少时间。
项目 数值
---------------------------
次数 30
最小 0.282 毫秒
中位 14.465 毫秒
最大 15.845 毫秒
最大/最小 56.1 倍
**注意这是回环地址**:数据根本没出网卡,全程在操作系统内部走了一圈。
**所以这个数只说明「协议栈本身的往返开销」,不代表真实网络。**
**但最大与最小差了 56 倍**——回环上都能差这么多,说明单次计时根本没有意义。
**所以看网络性能只看两样:中位数与吞吐;最小值和最大值都会骗人。**
**真实网络里,同样一次握手要加上两段链路传播时延**:同城一般 1 毫秒上下,跨省十几毫秒,
跨国上百毫秒。**这些数本机测不到,不给估计值。**
最大与最小差几十倍,这不是测错了,是回环本身就抖。 原因在于「什么时候被调度」:计时的那一下可能正好赶上系统在忙别的事。 所以网络性能只看两样:中位数(稳不稳定)与吞吐(能跑多快)。 最小值和最大值都会骗人。 这一点在跨机器测量里更明显:同一段路,早上和晚上的时延能差好几倍。
30 秒实验
- 把 30 次改成 300 次,看中位数会不会变——中位数应该比较稳,最大值会更大。
- 把
127.0.0.1换成你本机的局域网 IP(如192.168.3.152),看中位数变不变。 - 把端口改成没有服务监听的一个,看错误类型与耗时有什么不同。
常见误区
- 以为回环测出来的就是网络时延。 回环不碰网卡,测到的全是协议栈开销。 真实网络还要加上传播与排队。
- 以为最小耗时代表性能。 最小值往往是个偶然,中位数才代表常态。
- 以为三次握手很贵。 在回环上它是微秒到毫秒级, 真正贵的是跨洋的传播时延——那是物理定律,优化不掉。
自测三问(点开对答案)
1. 同一台机器内的通信与跨机器通信,最本质的区别是什么? —— **多了一层「寻址」**:机器内靠端口,跨机器还要先找到机器。 **跨网段时靠路由表**,这才是「网络」成立的前提。 2. 回环上测到的握手耗时能不能代表真实网络? —— **不能**。回环不经过网卡与链路,测到的只是协议栈处理开销。**真实网络还要加传播、排队时延。** 3. 为什么说网络性能只看中位数与吞吐? —— **最小值是偶然,最大值受系统负载影响**,两者都不代表常态。 **中位数反映稳定水平,吞吐反映上限。**下一节把这件事变成一个学科:当越来越多的机器连起来,地址该怎么分配?
- ☐ (本节完,下一节 1.2.4 计算机网络的产生)