📖 Day 5 / 15 · 《计算机网络:自顶向下方法》阅读笔记
每天 30 分钟,15 天读懂计算机网络。今天我们搞清楚两件事:Socket 编程到底是怎么回事,以及 电子邮件三协议 SMTP/POP3/IMAP 怎么协作。
「如果说 HTTP 是点外卖,那 Socket 就是你亲自下厨——从备菜到上桌,每一步都在你手里。」
前面四天我们聊了应用层的那帮”明星协议”——HTTP、DNS、CDN 等等。它们就像高级餐厅的菜单,你只管点菜,后厨怎么操作你不用操心。
但今天,我们要钻进后厨。
Socket 编程是网络编程的基本功,理解了它,你就理解了所有网络应用的本质。然后我们再聊聊邮件协议三兄弟——SMTP、POP3、IMAP,看看你的电子邮件是怎么在网络中飞来飞去的。
一、Socket 是什么?——给你装个”门”#
1.1 生活类比:Socket 就是你的”收发室”#
想象你住在一栋大楼里:
- 大楼地址 = IP 地址
- 房间号 = 端口号(Port)
- 收发室 = Socket
大楼门口有个收发室,所有信件包裹都从这里进出。你作为住户,只需要在收发室开一个”窗口”,就能收发信件。至于信是怎么送来的——快递员骑车、走路还是坐火箭——你不用管,收发室帮你搞定。
Socket 就是程序和网络之间的那个”收发室”。操作系统给你一个文件描述符(在 Unix 世界里万物皆文件),你往里写数据,数据就发出去了;你从里读数据,就收到了对方的回复。
1.2 Socket 的本质#
从操作系统的角度看,Socket 是一个通信的端点。它是:
1 | [应用程序] ←→ [Socket] ←→ [操作系统网络栈] ←→ [网络] ←→ [对方的Socket] ←→ [对方应用程序] |
每个 Socket 由五个要素唯一确定:
| 要素 | 说明 |
|---|---|
| 协议 | TCP 或 UDP |
| 本地 IP | 你的机器地址 |
| 本地端口 | 你的端口号 |
| 远程 IP | 对方的地址 |
| 远程端口 | 对方的端口号 |
💡 冷知识:在 Mac 上,你可以用
lsof -i查看当前所有活跃的 Socket。试试在终端敲一下,你会看到一堆你熟悉的 App 都开着”收发室”。
二、TCP Socket 编程——打电话模型#
2.1 TCP 通信的三板斧#
TCP 是面向连接的协议,所以用 TCP Socket 通信就像打电话:
- 拨号建立连接(三次握手)
- 互相通话(数据收发)
- 挂电话(四次挥手)
对应到代码里,服务器端和客户端各有自己的流程。
2.2 服务器端流程#
1 | socket() → 创建 Socket(开门) |
⚠️ 关键细节:
accept()会返回一个新的 Socket!为什么?因为原来的 Socket 还要继续监听有没有新的客人来。就像餐厅门口的迎宾员,把客人领进去后自己还要回到门口继续迎宾。
2.3 客户端流程#
1 | socket() → 创建 Socket(开门) |
2.4 Python 实战:一个简易的 Echo 服务器#
来写点代码。我们的 Mac mini 上跑一个 TCP 服务器,客户端发什么,服务器就回什么。
服务器端 server.py:
1 | import socket |
客户端 client.py:
1 | import socket |
运行试试:
1 | # 终端1:启动服务器 |
你在客户端输入 “Hello”,服务器就会回你 “Echo: Hello”。简单,但这就是所有 TCP 网络应用的骨架——Nginx、Redis、MySQL,骨子里都是这套流程。
2.5 实战分析:你的 Nginx 在做什么?#
你的 Mac mini 上跑着 Nginx,它本质上就是一个超级加强版的 TCP 服务器:
1 | # 看看 Nginx 在监听哪些端口 |
你会看到 Nginx 在 80 和 443 端口上 listen()。每当有浏览器来连接,Nginx 就 accept() 一个新连接,然后按照 HTTP 协议的格式去解析收到的数据,返回网页内容。
Nginx 和我们上面的 Echo 服务器的区别:
| 对比项 | Echo 服务器 | Nginx |
|---|---|---|
| 解析协议 | 不解析,收到啥回啥 | 严格按 HTTP 协议解析 |
| 并发模型 | 每连接一个线程 | 事件驱动(epoll/kqueue) |
| 性能 | 玩具级别 | 生产级别,能扛几万并发 |
| 功能 | 回声 | 反向代理、负载均衡、静态文件… |
但核心骨架是一样的——socket → bind → listen → accept → recv/send → close。
🤔 思考题:你的 Nginx 如果用
SO_REUSEADDR选项(允许端口重用),重启的时候就不会报 “Address already in use”。我们的示例代码里已经加了这个选项setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)。
2.6 关于 frp 的 Socket 视角#
你的 frp(Fast Reverse Proxy)也完全是 Socket 编程的产物:
- frpc(客户端) 跑在内网机器上,主动
connect()到 frps 的公网服务器 - frps(服务端)
listen()在公网上,等待 frpc 来连接 - 当外部用户访问 frps 的某个端口,frps 通过已建立的连接把数据转发给 frpc,frpc 再转发给内网的真实服务
这叫做反向代理隧道,本质上就是把好几层 Socket 串起来玩接力赛。
1 | 用户 ──→ frps(公网) ──TCP隧道──→ frpc(内网) ──→ 你的本地服务 |
每一跳都是一个 Socket 连接,frp 负责在中间做数据的搬运工。理解了 Socket,你就能理解 frp 的整个工作原理。
三、UDP Socket 编程——寄明信片模型#
3.1 UDP 不需要握手#
UDP 是无连接的,就像寄明信片:写好地址贴好邮票直接扔邮箱,不管对方收没收到。
所以 UDP Socket 的流程超级简单:
发送方:
1 | socket() → sendto() → close() |
接收方:
1 | socket() → bind() → recvfrom() → close() |
不需要 listen()、accept()、connect()——省心多了,代价是不保证可靠性。
3.2 Python 实战:UDP 版本#
服务器:
1 | import socket |
客户端:
1 | import socket |
看,代码更短了吧?但要注意——如果网络丢包了,你的 “Hello UDP” 可能就石沉大海了。
3.3 TCP vs UDP 选型指南#
| 特性 | TCP | UDP |
|---|---|---|
| 连接 | 需要握手 | 不需要 |
| 可靠性 | 保证送达、有序 | 不保证 |
| 速度 | 较慢(有开销) | 快 |
| 适用场景 | 网页、邮件、文件传输 | 视频、游戏、DNS |
| 生活类比 | 打电话 | 寄明信片 |
💡 你的实际应用:你的 Nginx 用 TCP(HTTP 基于 TCP),而 DNS 查询默认用 UDP。如果你想搭一个游戏服务器或者实时视频流,UDP 是更好的选择。
四、邮件协议三兄弟#
搞定了 Socket,我们来看应用层的另一个重量级选手——电子邮件。
电子邮件系统就像一个虚拟的邮政系统,有三套协议各司其职:
| 协议 | 全称 | 作用 | 生活类比 |
|---|---|---|---|
| SMTP | Simple Mail Transfer Protocol | 发送邮件 | 寄信的邮筒 |
| POP3 | Post Office Protocol v3 | 接收邮件(下载后删除) | 去邮局取包裹,取完就没了 |
| IMAP | Internet Message Access Protocol | 同步邮件(服务端为主) | 在线查看邮箱,信件始终在服务器上 |
4.1 SMTP——发邮件的”快递员”#
工作原理#
SMTP 是推(Push)协议——你把邮件推给服务器,服务器再推给收件方的服务器。整个流程:
1 | 你的邮件客户端 → SMTP → 你的邮件服务器 → SMTP → 对方的邮件服务器 → (对方用 POP3/IMAP 来取) |
SMTP 使用 TCP,端口 25(标准)、587(加密提交)、465(SMTPS)。
SMTP 命令一览#
SMTP 的命令极其简洁,是纯文本对话:
| 命令 | 作用 | 例子 |
|---|---|---|
HELO |
打招呼 | HELO mydomain.com |
MAIL FROM |
发件人 | MAIL FROM: <sender@example.com> |
RCPT TO |
收件人 | RCPT TO: <receiver@example.com> |
DATA |
邮件正文 | DATA 然后输入内容,以 . 结束 |
QUIT |
退出 | QUIT |
手动模拟 SMTP 对话#
你甚至可以用 nc(netcat)手动和 SMTP 服务器对话,感受一下这个协议有多朴素:
1 | nc smtp.example.com 25 |
🪄 是不是很像在跟服务器聊天? 没错,SMTP 就是这么朴实无华——纯文本、一问一答。所有的”高级邮件功能”都是在这套简单对话上加的料。
用 Python 发一封邮件#
1 | import smtplib |
🔒 安全提醒:现代 SMTP 都要求 TLS 加密。裸奔的 SMTP(端口 25 不加密)就像寄明信片——邮递员(路由器)都能看到内容。TLS 就像把信装进了密封信封。
4.2 POP3——取完就走的协议#
POP3 是拉(Pull)协议——你的客户端主动去服务器上把邮件下载到本地。
POP3 的典型流程是”下载并删除”:
- 连接到邮件服务器(端口 110,加密 995)
- 登录认证
- 下载邮件到本地
- (默认)从服务器删除邮件
- 断开连接
📦 生活类比:POP3 就像你去快递柜取包裹——取走之后柜子里就空了。你在自己电脑上看邮件,服务器上就没有了。
POP3 的优缺点:
✅ 简单,适合存储空间有限的服务器
✅ 下载后离线也能看
❌ 多设备不同步(手机上下载了,电脑上就没了)
❌ 不支持服务端搜索
1 | import poplib |
4.3 IMAP——云时代的邮件协议#
IMAP 也是拉协议,但和 POP3 不同,它是”同步”模式——邮件始终存在服务器上,客户端只是读取副本。
☁️ 生活类比:IMAP 就像用网盘看文件——文件始终在云端,你在任何设备上看到的都是同一份。手机上标记为已读,电脑上也变成已读。
IMAP 的特点:
- 邮件存储在服务器,客户端缓存
- 支持服务端搜索(不用下载就能搜)
- 支持文件夹管理(收件箱、已发送、草稿…)
- 多设备同步状态(已读、已回复、已删除)
- 端口 143(不加密),993(IMAPS,加密)
1 | import imaplib |
4.4 POP3 vs IMAP 对比#
| 特性 | POP3 | IMAP |
|---|---|---|
| 邮件存储位置 | 下载到本地 | 服务器上 |
| 多设备同步 | ❌ 不支持 | ✅ 支持 |
| 离线阅读 | ✅ 支持 | ⚠️ 需要先缓存 |
| 文件夹管理 | ❌ 只有收件箱 | ✅ 多文件夹 |
| 服务端搜索 | ❌ | ✅ |
| 适合场景 | 单设备、存储有限 | 多设备(现代主流) |
| 协议端口 | 110 / 995(SSL) | 143 / 993(SSL) |
🎯 现代推荐:除非你有特殊需求,否则一律用 IMAP。谁还不是手机+电脑+平板三件套呢?
4.5 邮件的完整旅程#
让我们跟踪一封邮件从发出到被阅读的完整过程:
1 | Alice (alice@a.com) Bob (bob@b.com) |
关键细节:对方邮件服务器的地址是通过 DNS 的 MX 记录查到的。还记得 Day 4 学的 DNS 吗?DNS 不光做域名→IP 的转换,还管邮件路由!
1 | # 查一下 gmail 的 MX 记录 |
你会看到多条 MX 记录,优先级从低到高(数字越小优先级越高)。如果首选服务器挂了,邮件就发给备选服务器——又一个可靠性的设计。
五、Socket 编程进阶话题#
5.1 阻塞 vs 非阻塞#
默认情况下,TCP Socket 是阻塞的——recv() 如果没有数据,程序就卡在那里等。
1 | 阻塞模式:快递没来,我在门口死等 |
非阻塞 Socket 配合 select/poll/epoll(Linux)/kqueue(macOS)可以实现单线程高并发——Nginx 就是这么干的。
1 | import select |
🍎 macOS 特供:Mac 用的是
kqueue(比select强大得多),Linux 用的是epoll。它们的理念相同:不要一个一个问,让操作系统告诉你谁准备好了。
5.2 粘包问题#
TCP 是字节流协议,不保留消息边界。你发了两个包 "Hello" 和 "World",对方可能收到的是 "HelloWorld" 一个整块,也可能分成 "Hel" "loWor" "ld" 三块。
🍜 生活类比:你往水管里倒了两杯水,另一头接到的就是连续的水流,谁知道哪杯是哪杯?
解决方案:
- 固定长度:每条消息都是固定字节数
- 分隔符:用特殊字符标记消息边界(HTTP 用
\r\n) - 长度前缀:先发长度,再发数据
1 | import struct |
5.3 实战:在你的 Mac mini 上看看 Socket 的世界#
来点真东西,打开终端:
1 | # 1. 查看所有 TCP 连接(看看谁连着你的 Mac) |
💡 小技巧:
nc(netcat)被称为”网络瑞士军刀”,本质上就是一个最简单的 Socket 工具,可以手动收发任意 TCP/UDP 数据。调试网络问题时非常好用。
六、邮件协议的工程实践#
6.1 如果你想在 Mac mini 上搭邮件服务器#
虽然个人搭邮件服务器现在比较少了(被 Gmail/QQ 邮箱统治),但了解一下架构有助理解协议:
1 | ┌────────────────────────────────┐ |
- Postfix:Mail Transfer Agent(MTA),负责通过 SMTP 收发邮件
- Dovecot:Mail Delivery Agent(MDA),负责通过 IMAP/POP3 让客户端读取邮件
⚠️ 现实警告:搭邮件服务器最大的挑战不是软件配置,而是域名信誉。你在家用 IP 上发的邮件,大概率会被 Gmail 直接扔进垃圾箱。SPF、DKIM、DMARC 这些认证机制我们以后再聊。
6.2 邮件安全速览#
| 安全机制 | 作用 | 类比 |
|---|---|---|
| TLS/SSL | 加密传输内容 | 信件装密封信封 |
| SPF | 验证发件服务器是否有权 | 检查发件地址是否在白名单 |
| DKIM | 邮件数字签名 | 信件盖火漆印章 |
| DMARC | 统一 SPF+DKIM 策略 | 收件规则总指挥 |
七、把知识点串起来——从 Socket 到协议#
到这里你应该明白了一个核心思想:
所有应用层协议(HTTP、SMTP、IMAP、POP3…)本质上都是建立在 Socket 之上的”对话规则”。
- HTTP 就是约定好在 TCP Socket 上用
GET /path HTTP/1.1\r\n这样的格式对话 - SMTP 就是约定好在 TCP Socket 上用
HELO、MAIL FROM、RCPT TO这样的格式对话 - DNS 用 UDP Socket,查询报文有特定的二进制格式
协议 = Socket + 数据格式约定 + 交互流程约定
理解了这一点,你甚至可以自己设计协议!
1 | # 你的自定义协议 v1.0 |
这本质上就是 Telegram、微信、QQ 干的事情——用 Socket 加上自定义的应用层协议。听起来是不是没那么神秘了?
关键知识点回顾#
Socket 编程#
| 知识点 | 要点 |
|---|---|
| Socket 定义 | 应用程序与网络之间的通信端点(收发室) |
| TCP 流程(服务端) | socket → bind → listen → accept → recv/send → close |
| TCP 流程(客户端) | socket → connect → send/recv → close |
| UDP 流程 | 不需要连接,直接 sendto / recvfrom |
| TCP vs UDP | 可靠 vs 高效、有序 vs 无序、有连接 vs 无连接 |
| 粘包问题 | TCP 不保留消息边界,需自行处理(长度前缀/分隔符) |
| I/O 多路复用 | select/epoll/kqueue 实现单线程高并发 |
邮件协议#
| 知识点 | 要点 |
|---|---|
| SMTP | 发送协议,Push 模型,端口 25/587/465 |
| POP3 | 接收协议,下载并删除,端口 110/995 |
| IMAP | 接收协议,服务端同步,端口 143/993 |
| MX 记录 | DNS 中指定邮件服务器的记录 |
| 邮件安全 | TLS 加密 + SPF/DKIM/DMARC 认证 |
核心思想#
协议 = Socket + 数据格式约定 + 交互流程约定
所有应用层协议的本质都是在 Socket 基础上定义的”对话规则”。
Day 6 预告 —— 传输层概述与多路复用#
今天我们从应用层往下走了一层(Socket 是应用层和网络层的接口),明天正式进入传输层:
- 🎯 传输层到底干了什么?(hint:进程之间的逻辑通信)
- 🎯 多路复用(Multiplexing)与多路分用(Demultiplexing)
- 🎯 端口号的那些事儿
- 🎯 无连接传输:UDP 详解
- 🎯 可靠数据传输的原理(停等协议、滑动窗口)
「如果说 Socket 是门口的收发室,那传输层就是整个城市的交通系统——它决定了信件怎么高效可靠地从这头送到那头。」
Stay tuned! 🚀
本文是《计算机网络:自顶向下方法》(第8版)读书笔记系列的第 5 篇。
作者:小黑白 · 2026.06.15