📖 Day 6 / 15 · 《计算机网络:自顶向下方法》阅读笔记
每天 30 分钟,15 天读懂计算机网络。前面五天我们都在应用层打转——HTTP、DNS、Socket——今天终于要往下走一层了。欢迎来到传输层,这是理解一切网络通信的钥匙。
一、传输层:网络世界的”快递分拣中心”#
1.1 从应用层到传输层#
前五天我们聊了不少应用层的东西:HTTP 负责网页、DNS 负责域名解析、SMTP 负责发邮件。这些应用层协议看起来各司其职,井井有条。
但你有没有想过一个问题:应用层协议怎么把数据送出去?又是怎么接收数据的?
当你在浏览器里输入一个网址,浏览器(应用层)要发 HTTP 请求,但它不会自己把数据塞到网线上——它调用的是操作系统提供的 Socket API。而 Socket API 的底层,就是传输层。
🎯 一句话理解传输层
传输层是操作系统网络协议栈中负责端到端通信的一层,它为应用程序提供数据传输服务。
生活类比:想象网络是一个快递系统。网络层(IP)负责把包裹从北京运到上海(主机到主机),而传输层(TCP/UDP)负责把包裹从小区快递柜分拣到具体的某个门牌号(进程到进程)。
1.2 传输层 vs 网络层:别搞混了#
这是新手最容易混淆的地方。我们用一张表来对比:
| 维度 | 网络层(IP) | 传输层(TCP/UDP) |
|---|---|---|
| 通信端点 | 主机到主机(Host-to-Host) | 进程到进程(Process-to-Process) |
| 核心协议 | IP | TCP、UDP |
| 地址标识 | IP 地址 | 端口号 |
| 可靠性 | 尽最大努力交付(不可靠) | TCP 可靠,UDP 不可靠 |
| 工作单位 | 数据报(Datagram) | 报文段(Segment) |
简单说:IP 协议负责把数据送到正确的机器,传输层协议负责把数据送到正确的程序。
这就像快递系统:
- 网络层:快递员把包裹送到你所在小区的快递柜 ✅
- 传输层:快递柜根据取件码,把包裹分到你的私人信箱 ✅
缺了传输层,数据到了你的 Mac mini,也不知道该交给 nginx 还是 SSH——大家都在等数据,凭什么这个包给你不给它?
1.3 传输层提供的服务#
传输层主要提供两大核心服务(以及一些细节):
1. 可靠数据传输(Reliable Data Transfer)
网络层(IP)是不可靠的——数据包可能丢失、损坏、乱序。传输层的 TCP 协议通过序列号、确认、重传、流量控制等机制,在不可靠的网络之上构建出可靠的传输通道。
生活类比:你网购一个手机,快递可能丢件。TCP 就像买了一份”全额保价+签收确认”服务——丢了赔,坏了换,直到你亲手收到为止。
2. 拥塞控制(Congestion Control)
如果所有人都疯狂往网络里灌数据,网络就会堵死。TCP 内置了拥塞控制机制,感知网络拥堵时主动减速。
生活类比:高速公路上的限速不是为你一个人设的——如果每辆车都飙 200 码,大家一起堵死。TCP 的拥塞控制就像智能导航,发现前方拥堵就提前减速。
3. 多路复用/分用(Multiplexing/Demultiplexing)
这是今天的重头戏,稍后详谈。
生活类比:一栋写字楼只有一个地址(IP 地址),但里面有 100 家公司(进程)。快递送到前台后,前台根据包裹上的”公司名称”(端口号)分发到对应的公司。
二、端口:进程的”门牌号”#
2.1 为什么需要端口?#
你的 Mac mini 上同时跑着很多东西:
- nginx 监听 8080 端口,提供 Web 服务
- SSH 监听 22 端口,让你远程登录
- frpc 在后台做端口转发
- 浏览器 在随机的高位端口上发起连接
- 微信 也在收发数据
当网络层把一个 IP 数据报送到你的 Mac mini 时,这个数据报里面装的数据到底该交给哪个进程?
答案是:端口号(Port Number)。
🎯 一句话理解端口
端口是一个 16 位的数字(0~65535),用于标识主机上的某个进程。IP 地址 + 端口号 = 唯一确定网络上某个进程的通信端点。
生活类比:IP 地址是写字楼的地址(中关村大街 1 号),端口号是楼层和房间号(18 楼 1801 室)。只有两者结合,快递才能精确送到你手上。
2.2 端口号的范围划分#
端口号不是随便用的,IANA(互联网号码分配局)把 65536 个端口分成了三个区间:
1 | ┌──────────────────────────────────────────────────────┐ |
生活类比:知名端口就像紧急电话(110、119、120),大家都知道且不能乱占;注册端口像公司固定电话,需要申请注册;动态端口就像酒店客房号,你入住时临时分配,退房后下一个人用。
2.3 实战:看看你 Mac mini 上的端口#
光说不练假把式,我们来看看实际的端口使用情况。在你的 Mac mini 上执行:
1 | # 查看哪些进程在监听 TCP 端口 |
你会看到类似这样的输出:
1 | COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME |
看,每个服务都守着自己的端口,各司其职:
- sshd 守着 22 号门 → SSH 远程连接从这进
- nginx 守着 8080 号门 → HTTP 请求从这进
- frpc 守着 7000 号门 → frp 内部通信用
如果你再看看已建立的连接:
1 | # 查看所有活跃的 TCP 连接 |
你会看到每个连接都有本地端口和远程端口。比如你 SSH 连到服务器:
1 | COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME |
注意那个 51234——这就是你的 SSH 客户端使用的临时端口(ephemeral port),它是从动态端口范围里随机选的。服务器收到你的连接后,把数据送回这个 51234 端口,你的 SSH 进程就能收到了。
三、多路复用与分用:传输层的”分拣大师”#
3.1 什么是多路复用/分用?#
这是传输层最核心的功能之一,理解了它,你就理解了”数据是怎么找到正确的进程的”。
多路复用(Multiplexing):发送方(源主机)从多个 Socket 收集数据,给每个数据块加上头部信息(包含源端口、目的端口等),然后交给网络层发送出去。
多路分用(Demultiplexing):接收方(目的主机)从网络层收到 IP 数据报,取出传输层报文段,根据头部信息中的端口号,把数据交给对应的 Socket,最终送达正确的进程。
🎯 生活类比:邮局的分拣中心
多路复用 = 你写字楼里的各家公司,把当天要寄出的信全送到一楼的快递收发室(传输层)。收发室把每封信装进信封,贴上邮票和地址(加上头部),统一交给快递公司(网络层)发出。
多路分用 = 第二天,快递从全国各地送来一大堆包裹,全堆在收发室。收发室根据每个包裹上的”公司名称+房间号”(端口号),分拣到对应公司的信箱里。A 公司收 A 公司的,B 公司收 B 公司的,不会搞混。
用一张图来表示:
1 | 发送方主机 接收方主机 |
3.2 多路分用的工作流程:拆快递#
让我们用一个具体例子走一遍流程。假设你的 Mac mini 上 nginx 正在监听 8080 端口,有人从外网访问你的博客:
步骤 1:客户端发送请求
某个用户在浏览器里访问 http://your-server-ip:8080。浏览器会:
- 创建一个 Socket,操作系统给它分配一个临时端口(比如
51200) - 构造 HTTP 请求报文,交给传输层
- 传输层封装为 TCP 报文段:
1 | TCP 报文段头部(简化): |
步骤 2:网络层传输
网络层把 TCP 报文段封装进 IP 数据报,源 IP 是客户端的 IP,目的 IP 是你 Mac mini 的 IP,然后通过互联网一路转发到你家路由器。
步骤 3:接收方多路分用
你的 Mac mini 收到这个 IP 数据报后:
- IP 层检查目的 IP 地址——是我的,收下
- 剥掉 IP 头部,发现里面是 TCP 数据,交给 TCP 模块
- TCP 模块检查报文段头部中的目的端口号:8080
- 在 Socket 表里查找——谁在监听 8080?→ nginx!
- 把数据放入 nginx 的 Socket 接收缓冲区
nginx 从 Socket 中读出数据,发现是一个 HTTP GET 请求 /,于是返回博客首页。
🏠 再打个比方
你的 Mac mini 就是一栋大楼,IP 地址是大楼地址。大楼里有多家公司:
- 22 号房间(端口 22):保安部(sshd)
- 8080 号房间(端口 8080):展示部(nginx)
- 7000 号房间(端口 7000):物流部(frpc)
快递送到大楼前台(IP 层),前台看快递上写的房间号(端口号),然后送对应的部门。如果写的是 8080,就送展示部;写的是 22,就送保安部。前台不需要关心快递内容,它只管按门牌号分发。
3.3 无连接的多路分用(UDP)#
UDP 的多路分用很简单——只看目的端口号。
一个 UDP Socket 由一个二元组唯一标识:
1 | (目的 IP 地址, 目的端口号) |
也就是说,只要目的 IP 和目的端口一样,不管这个包是从哪里来的,都丢进同一个 Socket。
生活类比:UDP 就像大楼里的公共广播系统。任何人都可以往”8080 号广播”投递消息,广播站不会管你是谁、从哪来的,只要有消息就播。如果你同时在 8080 上等消息,你会收到所有发到 8080 的消息,不管是 A 发的还是 B 发的。
这意味着什么? 一个 UDP Socket 可以接收来自任何源地址的数据。这就是为什么 DNS 服务器只用一个端口(53)就能同时响应成千上万的客户端请求——所有请求都进入同一个 Socket,DNS 进程根据报文内容区分是哪个客户端的查询。
3.4 面向连接的多路分用(TCP)#
TCP 的多路分用更精细——用一个四元组来标识一条连接:
1 | (源 IP 地址, 源端口号, 目的 IP 地址, 目的端口号) |
这意味着 TCP 服务器可以同时维护多条到不同客户端的连接,即使它们都连到同一个目的端口。
生活类比:TCP 就像大楼里的快递柜。每个格子上不仅写了房间号(目的端口),还写了寄件人信息(源 IP + 源端口)。这样你就能区分:这个包裹是张三从北京寄来的,那个是李四从上海寄来的——虽然都送到 8080 号房间,但来自不同的寄件人,分别放在不同的格子里。
用你的 nginx 实战来看:
1 | # 查看 nginx 的所有 TCP 连接 |
你会看到类似这样的输出:
1 | COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME |
看!nginx 只用一个端口(8080),但通过四元组区分了三条不同的连接。每条连接都有自己的文件描述符(FD),各自独立收发数据,互不干扰。这就是 TCP 多路分用的威力。
四、实战分析:你的 Mac mini 上发生了什么?#
4.1 场景一:你 SSH 远程连接到 Mac mini#
你在公司电脑上执行 ssh bicheng@your-mac-mini-ip,背后发生了什么?
1 | 你的公司电脑 Mac mini |
你的 SSH 客户端用临时端口 51234 作为源端口,发到 Mac mini 的 22 端口。Mac mini 的 TCP 模块根据目的端口 22,把数据交给 sshd 进程。
4.2 场景二:外网用户访问你的博客#
用户浏览器访问 http://your-domain:8080,nginx 收到请求后返回博客页面。但等等——你的 Mac mini 在内网,外网怎么访问到 8080 端口的?
这就涉及到 frp 的端口转发了:
1 | 外网用户 云服务器 ECS Mac mini (内网) |
看看这里面经过了多少次多路复用和分用:
- 外网用户 → ECS:数据到达 ECS 的 8080 端口,TCP 模块分用给 frps
- ECS frps → Mac mini frpc:frps 把数据通过隧道(也是 TCP 连接)发给 frpc
- Mac mini frpc → nginx:frpc 在本机把数据转发给 nginx 的 8080 端口,又一次多路分用
- 返回路径反过来走一遍
每一次经过传输层,都涉及一次多路复用(封装发送)和多路分用(接收拆解)。是不是突然觉得这个过程有点壮烈?
4.3 场景三:端口冲突——两个程序抢一个门#
你一定遇到过这种报错:
1 | nginx: [emerg] bind() to 0.0.0.0:8080 failed (98: Address already in use) |
这就是端口冲突——8080 号门已经被别的进程占了,nginx 进不去。
传输层的多路分用要求:一个端口在同一时间只能被一个进程绑定(对于同一个 IP 地址和协议来说)。这是操作系统的硬规定,因为分用的时候 TCP 模块需要明确知道”端口 8080 的数据该交给谁”。如果两个进程都绑定了 8080,TCP 模块就不知道该交给谁了。
生活类比:一扇门后面只能坐一个部门。如果两个部门都想占 1801 室,前台分快递时就懵了——这个件到底给谁?所以必须先到先得,后来的人换个房间。
解决方法也很简单:
1 | # 查看谁占了 8080 |
五、多路复用/分用的底层机制:Socket 的角色#
5.1 Socket 是什么?#
在深入理解了多路复用/分用之后,我们再来重新认识 Socket。
Socket 是操作系统提供的一个编程接口(API),它是应用层和传输层之间的”大门”。应用程序通过 Socket 来收发数据,而操作系统通过 Socket 来管理数据的分发。
一个 Socket 在操作系统内核中对应一个数据结构,大致包含以下信息:
1 | ┌─────────────────────────────────────────┐ |
生活类比:Socket 就是写字楼里每个公司门口的信箱。信箱上标了公司名称和房间号(IP + 端口),里面有收件格(接收缓冲区)和发件格(发送缓冲区)。你把要寄的东西放发件格,快递员来取走;快递员送来的东西放收件格,你随时来取。
5.2 多路分用的精确逻辑#
操作系统的 TCP/UDP 模块在收到网络层递交的报文段后,多路分用的逻辑大致是:
UDP 分用逻辑:
1 | 收到 UDP 报文段 → |
TCP 分用逻辑:
1 | 收到 TCP 报文段 → |
这就解释了为什么 TCP 服务器用 listen() 监听一个端口,但每个客户端连接都会创建一个新的 Socket——每个连接都有唯一的四元组,需要独立的 Socket 来管理。
六、深入思考:为什么传输层这么设计?#
6.1 为什么端口号是 16 位?#
16 位意味着有 65536 个端口可用。这个数字怎么来的?这是 1980 年代设计的,当时 65536 个端口绰绰有余。即使到了今天,对于一台普通主机来说,同时跑几万个连接也不是常态(当然,高并发服务器是另一回事,C10K/C100K 问题催生了 epoll、io_uring 等技术)。
如果你好奇:Linux 默认的临时端口范围是 32768
60999(约 28000 个),你可以用65535。sysctl net.ipv4.ip_local_port_range查看。macOS 上则是 49152
6.2 为什么 TCP 用四元组而 UDP 只用二元组?#
因为 TCP 是面向连接的——每条连接是一对端点之间的独占通道,服务器需要精确区分”这条数据属于哪条连接”。而 UDP 是无连接的——每个报文段都是独立的,服务器不关心(也不需要关心)这条数据是从哪条”连接”来的,只关心它要送到哪个端口。
生活类比:TCP 像电话系统——你需要先拨号建立连接,通话中每句话都属于这条线路,挂断就结束。UDP 像信箱——每封信独立投递,邮递员只看收件地址(端口号),不管这封信跟你上次收到的信有没有关系。
6.3 一个端口能被 TCP 和 UDP 同时使用吗?#
可以! 因为 TCP 和 UDP 是两套独立的多路分用系统。端口 53 可以同时被 DNS 的 TCP 连接和 UDP 连接使用,互不干扰。
1 | TCP Socket 表: ( ..., 目的端口=53, ... ) → DNS over TCP 进程 |
生活类比:大楼里的 1801 室,左边开了一家公司叫”TCP 快递”,右边开了一家叫”UDP 快递”。虽然门牌号都是 1801,但它们是两个完全独立的房间,各自服务各自的客户。
七、用 Wireshark 实际看看多路分用#
如果你想亲眼看到多路复用/分用在发挥作用,可以用 Wireshark(或 tcpdump)抓个包:
1 | # 抓取 8080 端口的 TCP 数据包 |
你会看到每个包的 TCP 头部都包含了源端口和目的端口:
1 | 14:30:01.234 IP 192.168.1.100.51234 > 192.168.1.200.8080: Flags [S], seq 1234567 |
看到 51234 和 8080 了吗?这就是多路复用/分用在起作用——每个 TCP 报文段都带着端口信息,让接收方知道该怎么分发。
🎯 关键知识点回顾#
- 传输层提供端到端的进程间通信,而网络层只提供主机到主机的交付
- IP 地址定位主机,端口号定位进程——两者结合才能精确找到通信目标
- 端口号范围:知名端口(0-1023)、注册端口(1024-49151)、动态端口(49152-65535)
- 多路复用 = 发送方从多个 Socket 收集数据、加头部、交给网络层
- 多路分用 = 接收方从网络层取数据、根据端口号分发给正确的 Socket
- UDP 多路分用只看二元组(目的IP, 目的端口),不区分来源
- TCP 多路分用看四元组(源IP, 源端口, 目的IP, 目的端口),精确区分每条连接
- 一个端口可以被 TCP 和 UDP 同时使用,因为它们是独立的分用系统
- 端口冲突的根因是同一协议下同一端口只能被一个进程绑定
- frp 端口转发本质上就是在多个层级进行多路复用和分用——理解了这个过程,你的运维排障能力会上一个台阶
📌 Day 7 预告:UDP 协议与可靠数据传输原理
今天我们理解了传输层的”分发机制”,明天来看传输层的两个核心协议之一——UDP。为什么 DNS、视频通话、游戏都偏爱 UDP?它的”不可靠”到底是优势还是劣势?
然后我们会开始啃一块硬骨头:可靠数据传输原理(RDT)。这是 TCP 可靠性的理论基础,我们会从零开始一步步构建一个可靠传输协议,感受一下先驱们的智慧。
明天见!🔌
本文是基于《计算机网络:自顶向下方法》(第8版)的读书笔记,结合实际操作经验编写。