📖 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 通信就像打电话

  1. 拨号建立连接(三次握手)
  2. 互相通话(数据收发)
  3. 挂电话(四次挥手)

对应到代码里,服务器端和客户端各有自己的流程。

2.2 服务器端流程#

1
2
3
4
5
6
7
8
9
10
11
socket()   → 创建 Socket(开门)

bind() → 绑定 IP 和端口(挂上门牌号)

listen() → 监听连接(坐在门口等客人)

accept() → 接受连接(开门迎客,返回新的 Socket)

recv()/send() → 收发数据(和客人聊天)

close() → 关闭连接(送客关门)

⚠️ 关键细节accept() 会返回一个新的 Socket!为什么?因为原来的 Socket 还要继续监听有没有新的客人来。就像餐厅门口的迎宾员,把客人领进去后自己还要回到门口继续迎宾。

2.3 客户端流程#

1
2
3
4
5
6
7
socket()   → 创建 Socket(开门)

connect() → 连接服务器(拨号)

send()/recv() → 收发数据(通话)

close() → 关闭连接(挂电话)

2.4 Python 实战:一个简易的 Echo 服务器#

来写点代码。我们的 Mac mini 上跑一个 TCP 服务器,客户端发什么,服务器就回什么。

服务器端 server.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import socket
import threading

def handle_client(conn, addr):
"""处理每个客户端连接"""
print(f"[+] {addr} 连上来了")
while True:
data = conn.recv(1024) # 收数据,每次最多 1024 字节
if not data:
break # 对方关了连接
print(f"[{addr}] 说: {data.decode()}")
conn.sendall(b"Echo: " + data) # 原样返回
conn.close()
print(f"[-] {addr} 断开了")

def main():
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(('0.0.0.0', 9527))
server.listen(5) # 最多 5 个排队等候的连接
print("🚀 Echo 服务器启动了,监听端口 9527")

while True:
conn, addr = server.accept()
# 每个连接开一个线程处理,不然只能一次服务一个客人
thread = threading.Thread(target=handle_client, args=(conn, addr))
thread.start()

if __name__ == '__main__':
main()

客户端 client.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import socket

def main():
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 9527))

while True:
msg = input("你说: ")
if msg == 'quit':
break
client.sendall(msg.encode())
reply = client.recv(1024)
print(f"服务器: {reply.decode()}")

client.close()

if __name__ == '__main__':
main()

运行试试:

1
2
3
4
5
# 终端1:启动服务器
python3 server.py

# 终端2:启动客户端
python3 client.py

你在客户端输入 “Hello”,服务器就会回你 “Echo: Hello”。简单,但这就是所有 TCP 网络应用的骨架——Nginx、Redis、MySQL,骨子里都是这套流程。

2.5 实战分析:你的 Nginx 在做什么?#

你的 Mac mini 上跑着 Nginx,它本质上就是一个超级加强版的 TCP 服务器:

1
2
# 看看 Nginx 在监听哪些端口
lsof -i -P | grep 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
2
3
4
5
6
7
8
9
10
import socket

server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server.bind(('0.0.0.0', 9528))
print("🚀 UDP 服务器启动,监听 9528")

while True:
data, addr = server.recvfrom(1024)
print(f"[{addr}] 说: {data.decode()}")
server.sendto(b"UDP Echo: " + data, addr)

客户端:

1
2
3
4
5
6
7
import socket

client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
client.sendto(b"Hello UDP!", ('127.0.0.1', 9528))
data, _ = client.recvfrom(1024)
print(f"服务器: {data.decode()}")
client.close()

看,代码更短了吧?但要注意——如果网络丢包了,你的 “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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
nc smtp.example.com 25
# 服务器: 220 smtp.example.com ESMTP Ready
# 你输入: HELO mydomain.com
# 服务器: 250 smtp.example.com
# 你输入: MAIL FROM: <me@example.com>
# 服务器: 250 OK
# 你输入: RCPT TO: <friend@example.com>
# 服务器: 250 OK
# 你输入: DATA
# 服务器: 354 Start mail input
# 你输入: Subject: Hello!
# 你输入: This is a test email.
# 你输入: .
# 服务器: 250 OK queued
# 你输入: QUIT

🪄 是不是很像在跟服务器聊天? 没错,SMTP 就是这么朴实无华——纯文本、一问一答。所有的”高级邮件功能”都是在这套简单对话上加的料。

用 Python 发一封邮件#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import smtplib
from email.mime.text import MIMEText

# 构造邮件
msg = MIMEText("这是来自 Python 的测试邮件,恭喜你搞定了 SMTP!")
msg['Subject'] = 'SMTP 测试 ✉️'
msg['From'] = 'sender@example.com'
msg['To'] = 'receiver@example.com'

# 通过 SMTP 发送
try:
smtp = smtplib.SMTP('smtp.example.com', 587)
smtp.starttls() # 启动 TLS 加密
smtp.login('sender@example.com', 'your_password')
smtp.send_message(msg)
smtp.quit()
print("✅ 邮件发送成功!")
except Exception as e:
print(f"❌ 发送失败: {e}")

🔒 安全提醒:现代 SMTP 都要求 TLS 加密。裸奔的 SMTP(端口 25 不加密)就像寄明信片——邮递员(路由器)都能看到内容。TLS 就像把信装进了密封信封。

4.2 POP3——取完就走的协议#

POP3 是拉(Pull)协议——你的客户端主动去服务器上把邮件下载到本地。

POP3 的典型流程是”下载并删除”:

  1. 连接到邮件服务器(端口 110,加密 995)
  2. 登录认证
  3. 下载邮件到本地
  4. (默认)从服务器删除邮件
  5. 断开连接

📦 生活类比:POP3 就像你去快递柜取包裹——取走之后柜子里就空了。你在自己电脑上看邮件,服务器上就没有了。

POP3 的优缺点

✅ 简单,适合存储空间有限的服务器
✅ 下载后离线也能看
❌ 多设备不同步(手机上下载了,电脑上就没了)
❌ 不支持服务端搜索

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import poplib

try:
pop = poplib.POP3_SSL('pop.example.com', 995)
pop.user('your_email@example.com')
pop.pass_('your_password')

# 查看邮件数量和总大小
status = pop.stat()
print(f"邮箱里有 {status[0]} 封邮件")

# 获取最新一封
resp, lines, octets = pop.retr(status[0])
msg_content = b'\n'.join(lines).decode()
print(msg_content[:200]) # 打印前 200 个字符

pop.quit()
except Exception as e:
print(f"Error: {e}")

4.3 IMAP——云时代的邮件协议#

IMAP 也是拉协议,但和 POP3 不同,它是”同步”模式——邮件始终存在服务器上,客户端只是读取副本。

☁️ 生活类比:IMAP 就像用网盘看文件——文件始终在云端,你在任何设备上看到的都是同一份。手机上标记为已读,电脑上也变成已读。

IMAP 的特点:

  • 邮件存储在服务器,客户端缓存
  • 支持服务端搜索(不用下载就能搜)
  • 支持文件夹管理(收件箱、已发送、草稿…)
  • 多设备同步状态(已读、已回复、已删除)
  • 端口 143(不加密),993(IMAPS,加密)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import imaplib
import email

try:
imap = imaplib.IMAP4_SSL('imap.example.com', 993)
imap.login('your_email@example.com', 'your_password')

# 选择收件箱
imap.select('INBOX')

# 搜索所有邮件
status, messages = imap.search(None, 'ALL')
mail_ids = messages[0].split()
print(f"收件箱里有 {len(mail_ids)} 封邮件")

# 获取最新一封
latest_id = mail_ids[-1]
status, msg_data = imap.fetch(latest_id, '(RFC822)')
raw_email = msg_data[0][1]
msg = email.message_from_bytes(raw_email)

print(f"发件人: {msg['From']}")
print(f"主题: {msg['Subject']}")

imap.logout()
except Exception as e:
print(f"Error: {e}")

4.4 POP3 vs IMAP 对比#

特性 POP3 IMAP
邮件存储位置 下载到本地 服务器上
多设备同步 ❌ 不支持 ✅ 支持
离线阅读 ✅ 支持 ⚠️ 需要先缓存
文件夹管理 ❌ 只有收件箱 ✅ 多文件夹
服务端搜索
适合场景 单设备、存储有限 多设备(现代主流)
协议端口 110 / 995(SSL) 143 / 993(SSL)

🎯 现代推荐:除非你有特殊需求,否则一律用 IMAP。谁还不是手机+电脑+平板三件套呢?

4.5 邮件的完整旅程#

让我们跟踪一封邮件从发出到被阅读的完整过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Alice (alice@a.com)                  Bob (bob@b.com)
| |
| 1. SMTP (端口587/465) |
| → 发给 a.com 的邮件服务器 |
| |
| 2. a.com 服务器查 DNS MX 记录 |
| 找到 b.com 的邮件服务器地址 |
| |
| 3. SMTP (端口25) |
| a.com → b.com 的邮件服务器 |
| |
| 4. Bob 的客户端 |
| IMAP/POP3 拉取 |
| (端口993/995) |

关键细节:对方邮件服务器的地址是通过 DNS 的 MX 记录查到的。还记得 Day 4 学的 DNS 吗?DNS 不光做域名→IP 的转换,还管邮件路由!

1
2
3
4
# 查一下 gmail 的 MX 记录
dig MX gmail.com
# 或者
nslookup -type=mx gmail.com

你会看到多条 MX 记录,优先级从低到高(数字越小优先级越高)。如果首选服务器挂了,邮件就发给备选服务器——又一个可靠性的设计。


五、Socket 编程进阶话题#

5.1 阻塞 vs 非阻塞#

默认情况下,TCP Socket 是阻塞的——recv() 如果没有数据,程序就卡在那里等。

1
2
阻塞模式:快递没来,我在门口死等
非阻塞模式:快递没来?我先干别的,过会儿再来看

非阻塞 Socket 配合 select/poll/epoll(Linux)/kqueue(macOS)可以实现单线程高并发——Nginx 就是这么干的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import select

# 假设你有一堆 socket 连接
sockets_list = [server_socket, client1, client2, ...]

while True:
# 同时监听所有 socket,哪个有数据就处理哪个
readable, _, _ = select.select(sockets_list, [], [])
for s in readable:
if s is server_socket:
# 新连接来了
conn, addr = s.accept()
sockets_list.append(conn)
else:
# 某个客户端发数据来了
data = s.recv(1024)
...

🍎 macOS 特供:Mac 用的是 kqueue(比 select 强大得多),Linux 用的是 epoll。它们的理念相同:不要一个一个问,让操作系统告诉你谁准备好了

5.2 粘包问题#

TCP 是字节流协议,不保留消息边界。你发了两个包 "Hello""World",对方可能收到的是 "HelloWorld" 一个整块,也可能分成 "Hel" "loWor" "ld" 三块。

🍜 生活类比:你往水管里倒了两杯水,另一头接到的就是连续的水流,谁知道哪杯是哪杯?

解决方案:

  1. 固定长度:每条消息都是固定字节数
  2. 分隔符:用特殊字符标记消息边界(HTTP 用 \r\n
  3. 长度前缀:先发长度,再发数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import struct

def send_msg(sock, msg):
"""发送带长度前缀的消息"""
msg_bytes = msg.encode()
sock.sendall(struct.pack('!I', len(msg_bytes)) + msg_bytes)

def recv_msg(sock):
"""接收带长度前缀的消息"""
# 先读 4 字节的长度
raw_len = recv_exactly(sock, 4)
if not raw_len:
return None
msg_len = struct.unpack('!I', raw_len)[0]
# 再读指定长度的消息体
return recv_exactly(sock, msg_len).decode()

def recv_exactly(sock, n):
"""确保精确读取 n 个字节"""
data = b''
while len(data) < n:
chunk = sock.recv(n - len(data))
if not chunk:
return None
data += chunk
return data

5.3 实战:在你的 Mac mini 上看看 Socket 的世界#

来点真东西,打开终端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 1. 查看所有 TCP 连接(看看谁连着你的 Mac)
netstat -an -p tcp | grep ESTABLISHED

# 2. 查看监听端口(看看你的 Mac 开了哪些"门")
netstat -an -p tcp | grep LISTEN

# 3. 查看 Nginx 的 Socket 设置
lsof -i -P -n | grep nginx

# 4. 看看 frp 的连接
lsof -i -P -n | grep frp

# 5. 手动用 Socket 给 Nginx 发请求
# (nc 命令就是最简单的 Socket 客户端)
echo -e "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n" | nc 127.0.0.1 80

💡 小技巧nc(netcat)被称为”网络瑞士军刀”,本质上就是一个最简单的 Socket 工具,可以手动收发任意 TCP/UDP 数据。调试网络问题时非常好用。


六、邮件协议的工程实践#

6.1 如果你想在 Mac mini 上搭邮件服务器#

虽然个人搭邮件服务器现在比较少了(被 Gmail/QQ 邮箱统治),但了解一下架构有助理解协议:

1
2
3
4
5
6
7
8
9
                  ┌────────────────────────────────┐
│ 你的 Mac mini │
│ │
外部邮件 ──SMTP──→ │ Postfix (MTA, 端口25) │
│ ↓ 投递到本地 │
│ Dovecot (IMAP/POP3, 端口993/995) │
│ ↑ │
邮件客户端 ─IMAP──→ │ │
└────────────────────────────────┘
  • 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 上用 HELOMAIL FROMRCPT TO 这样的格式对话
  • DNS 用 UDP Socket,查询报文有特定的二进制格式

协议 = Socket + 数据格式约定 + 交互流程约定

理解了这一点,你甚至可以自己设计协议!

1
2
3
4
5
6
# 你的自定义协议 v1.0
# 消息格式: [类型(1字节)][长度(4字节)][数据(变长)]

MSG_TYPE_CHAT = 1
MSG_TYPE_FILE = 2
MSG_TYPE_HEARTBEAT = 3

这本质上就是 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