独立模式下程序只能碰自己机器上的东西;要碰别的机器,中间必须有一条「能被寻址」的通道。 这一节把这句话落到代码上:文件名字换成 IP 加端口,磁盘换成协议栈。
1.1.1 数清了接口,这一节讲「为什么要用接口」。 1.1.3 会把视角从「送比特」抬到「送信息」——通道通了之后,还得让人看懂。
一、两种「把数据放到别处」
同一个需求「把这 32 字节送到另一个地方」,有两种做法:
| 做法 | 寻址靠什么 | 对端在哪 | 能不能跨机器 |
|---|---|---|---|
| 写文件 | 文件名 + 路径 | 同一块磁盘 | 不能 |
| 套接字 | IP + 端口 | 任何能路由到的地方 | 能 |
第一行那个「不能」是可以绕的:把文件放在共享目录里,别的机器也能打开。 但那样一来,共享目录本身就是靠网络协议实现的——绕了一圈还是回到套接字。 这就是「独立模式」与「网络互连模式」的分界:有没有一个跨机器的寻址体系。
二、IP 加端口解决了两件事
网络上一个地址要同时回答两个问题:
| 问题 | 由谁回答 | 例子 |
|---|---|---|
| 送到哪台机器 | IP 地址 | 192.168.3.152 |
| 送到这台机器的哪个程序 | 端口号 | 8080 |
两样缺一不可:只给 IP,包到了机器上不知道交给谁;只给端口,根本不知道该往哪台机器发。
所以「地址」在网络里从来不是单指 IP,
常见的写法 192.168.3.152:8080 是一个「套接字地址」,不是单纯的 IP 地址。
这个区分在排障时很有用:
ping 只测 IP 可达,telnet 才测得到端口——ping 通了但连不上,八成是端口没开或被防火墙挡了。
三、回环地址是干什么的
127.0.0.1 这个地址不对应任何网卡,它是给「本机自己」留的固定地址。
| 写法 | 含义 |
|---|---|
127.0.0.1 |
只在本机内部走,不出网卡 |
0.0.0.0(监听时) |
所有网卡都接受连接 |
192.168.3.152 |
只有这张网卡对应的网段能连 |
第二行那个 0.0.0.0 是监听时的写法,
它不是一个能 ping 的地址——它的意思是「我不挑网卡,来者不拒」。
这条区别值得记牢:本地调试用 127.0.0.1,对外服务用 0.0.0.0。
把调试端口开成 0.0.0.0 是常见的安全事故,尤其在没有防火墙的机器上。
四、动手 Demo:独立模式与网络互连模式的差别
同一段 32 字节,先用文件送一次、再用回环套接字送一次, 对比「寻址靠什么」与「耗时」。 第二个的耗时含一次真正的 TCP 往返——虽然没出网卡,但协议栈该做的都做了。
import socket, threading, time
# 第一种:写文件,只有文件名,没有地址
open('hello-36', 'wb').write(b'x' * 32)
# 第二种:走套接字,必须给出 IP 与端口
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
srv.bind(('127.0.0.1', 0)) # 0 表示让系统挑一个空闲端口
srv.listen(1)
port = srv.getsockname()[1]
def acc():
c, _ = srv.accept()
c.recv(64)
c.sendall(b'ok')
c.close()
threading.Thread(target=acc, daemon=True).start()
c = socket.create_connection(('127.0.0.1', port), timeout=2)
t0 = time.perf_counter()
c.sendall(b'x' * 32)
c.recv(8)
print('往返 %.3f 毫秒' % ((time.perf_counter() - t0) * 1000))
c.close(); srv.close()
实跑输出:
--------------------------------------------------------------------
独立模式与网络互连模式的差别
--------------------------------------------------------------------
**独立模式下,程序只能碰自己这台机器上的东西**;
**要碰另一台机器,中间必须有一条「能被寻址」的通道。**
下面用两种方式把同一段文字送到「另一个地方」:
第一种:写进本机文件(独立模式的做法,全程没有地址)。
写入 32 字节,用的是文件名,不是地址。
第二种:走回环地址 127.0.0.1(网络模式的做法,必须给出 IP 与端口)。
对端收到 32 字节,往返 0.165 毫秒。
方式 寻址靠什么 对端在哪 能不能跨机器
--------------------------------------------------------------
写文件 文件名 同一块磁盘 不能
套接字 IP + 端口 任何能路由到的地方 能
**这一节书里讲的「从独立模式到网络互连模式」,落到代码上就是这个区别**:
**文件名换成 IP 加端口,本机文件系统换成协议栈。**
「端口 0」这个写法要记住:bind 到 0 是让操作系统挑一个当前空闲的端口,
挑完用 getsockname() 读回来。
这是写测试代码的常用手法——固定端口会撞车,0 端口不会。
但生产环境不能用 0:对端不知道该连哪个端口,除非你另外告诉它。
30 秒实验
- 把
bind的地址从127.0.0.1改成0.0.0.0,再用另一台机器连你这台的 IP, 看能不能通——能通就说明这个端口对外开了,注意安全。 - 把 32 字节改成 32 KB,看往返耗时怎么变,再想想为什么不是线性增长。
- 在
accept之前把服务端关掉,看客户端报什么错。
常见误区
- 以为写文件也能跨机器。 通过共享目录确实能,但那已经是网络在起作用了, 不是文件系统本身的能力。
- 以为
0.0.0.0是一个可以访问的地址。 它只在监听时有意义, 你在浏览器里输0.0.0.0访问不到任何东西。 - 以为回环快就代表网络快。 回环全程不碰网卡,它测的是协议栈开销,与网线、交换机、 运营商都无关。
自测三问(点开对答案)
1. 为什么「IP 加端口」才算完整的地址? —— **IP 定位到机器,端口定位到机器上的程序**。缺 IP 不知道发给谁,缺端口不知道交给哪个进程。 **这个组合叫套接字地址,不是 IP 地址。** 2. `bind(('127.0.0.1', 0))` 里的 0 是什么意思? —— **让操作系统挑一个空闲端口**。真实端口要用 `getsockname()` 读回来。 **测试代码常用,生产环境不这么写。** 3. ping 得通但服务连不上,最可能是哪一层的问题? —— **网络层通了,问题在传输层之上**。 常见原因是端口没监听、被防火墙挡、或者服务只绑了 `127.0.0.1`。 **下一步应该用端口探测,而不是继续 ping。**下一节换个角度:比特送到了还不算完,人得能看懂才算「信息通信」。
- ☐ (本节完,下一节 1.1.3 从计算机通信到信息通信)