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

独立模式下程序只能碰自己机器上的东西;要碰别的机器,中间必须有一条「能被寻址」的通道。 这一节把这句话落到代码上:文件名字换成 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 秒实验

  1. 把 bind 的地址从 127.0.0.1 改成 0.0.0.0,再用另一台机器连你这台的 IP, 看能不能通——能通就说明这个端口对外开了,注意安全。
  2. 把 32 字节改成 32 KB,看往返耗时怎么变,再想想为什么不是线性增长。
  3. 在 accept 之前把服务端关掉,看客户端报什么错。

常见误区

  1. 以为写文件也能跨机器。 通过共享目录确实能,但那已经是网络在起作用了, 不是文件系统本身的能力。
  2. 以为 0.0.0.0 是一个可以访问的地址。 它只在监听时有意义, 你在浏览器里输 0.0.0.0 访问不到任何东西。
  3. 以为回环快就代表网络快。 回环全程不碰网卡,它测的是协议栈开销,与网线、交换机、 运营商都无关。
自测三问(点开对答案) 1. 为什么「IP 加端口」才算完整的地址? —— **IP 定位到机器,端口定位到机器上的程序**。缺 IP 不知道发给谁,缺端口不知道交给哪个进程。 **这个组合叫套接字地址,不是 IP 地址。** 2. `bind(('127.0.0.1', 0))` 里的 0 是什么意思? —— **让操作系统挑一个空闲端口**。真实端口要用 `getsockname()` 读回来。 **测试代码常用,生产环境不这么写。** 3. ping 得通但服务连不上,最可能是哪一层的问题? —— **网络层通了,问题在传输层之上**。 常见原因是端口没监听、被防火墙挡、或者服务只绑了 `127.0.0.1`。 **下一步应该用端口探测,而不是继续 ping。**

下一节换个角度:比特送到了还不算完,人得能看懂才算「信息通信」。

相关笔记