04.01 应用通信与分层:一条 IM 消息跨过哪些边界
返回第四卷 · 字节前置:03.01 · 进程前置:03.02
04.01 应用通信与分层:一条 IM 消息跨过哪些边界
DeepTutor 初稿经技术与教学审阅后的静态课程。路径、字节图、时延和故障都是有条件的教学模型;本次没有运行 Go、网络实验、站点构建或 IM 服务,也没有验证任何真实消息送达。
从一个业务需求进入网络
到目前为止,01.12 的 imhistory check/list/export 只处理本机 JSON 文件;03.02–03.04 解释了进程、系统调用和等待。现在把目标改成未来教学 IM:虚构用户 u-a 在设备 A 上输入消息 m-a,希望同一会话 c-a 中的 u-b 在设备 B 上看到它。两台设备为什么不能仅共享 A 进程的 Go 变量?字节要经过哪些边界?“发送成功”究竟在哪一步成立?
本章不写服务代码。先补客户端、服务端、消息和协议,再介绍分层与封装,最后用带单位的模型估计时延。04.02 才讲 IP、端口、DNS 和地址定位;04.03 才用 HTTP/WebSocket 建立第一个联网小模型;04.04–04.06 再讲 TCP 可靠传输与流控。这里不预设你已经知道这些名词。
| 起步阅读 | 原理深化 | 业务实践 |
|---|---|---|
| 谁和谁通信、遵守什么约定 | 字节怎样跨层、时延由什么组成 | 标注 A→服务→B 的证据与失败点 |
所有用户、设备、会话和消息都是虚构示例。课程固定的 OpenIM 公开项目是后续源码参照;本章不把纸上路径说成它的已核对实现,也不把本地 CLI 成功称为网络受理、设备接收或已读。
一、从两端各自的任务定义“通信”
客户端是在某次交互中发起请求或维持连接、希望获得服务的一端;服务端是在该交互中提供约定服务的一端。这里它们是软件角色,不是“普通用户”和“公司”这样的永久身份,也不必永远对应一台独立物理机器。一个应用既可对用户设备充当服务端,也可对后面的存储系统充当客户端。
设备 A 上的客户端 教学 IM 服务 设备 B 上的客户端
u-a 输入 m-a ───请求/消息───→ 校验、决定是否受理 ───事件──→ u-b 可能显示
←─某种回答─── 可能保存或路由 ←─某种确认─箭头表达未来系统可能设计的交互位置,不是现有服务的运行记录。对于应用需求,应先分别写明:A 想提交什么;服务能承诺什么;B 在什么条件下才能显示;用户何时算已读。若 B 离线,服务也许保存等待补拉,也许只报告暂不可达,取决于后来选定的产品与可靠性合同,不能在本章默认其中之一。
端点是某次通信可定位的参与位置。当前只需知道网络软件必须找到对方并区分交互对象;04.02 才把主机地址、端口、名称解析和路由入口逐个展开。u-b 是应用用户身份,不是网络地址;c-a 是会话身份,也不能直接作为 IP 或端口使用。
一次应用事件“发送 m-a”至少有输入与状态:消息 ID、会话、正文、发起者身份、版本和可能的时间;服务端还要核对权限和格式。客户端在内存里构造一个 Message 结构体,只说明本机程序形成了对象,不意味着服务端进程已经读到它。通信需要把对象按双方约定转换成可传送的字节,再由对方解释。
二、协议是两端共同遵守的约定
协议不只是字段名称。至少要讲清四类问题:
| 方面 | 要约定什么 | 若不约定会怎样 |
|---|---|---|
| 语法 | 字段编码、顺序/结构、长度、版本、消息边界 | 接收方不知道从哪读到哪 |
| 语义 | message_id、conversation_id、body 表示什么 | 两端即使读到字节也可能理解不同 |
| 时序 | 谁先发、何时回应、能否同时发、如何超时 | 双方可能都在等待对方 |
| 错误与兼容 | 非法字段、未知版本、重复请求怎样表示 | 一端误把失败当成功或无法演进 |
在 S1 的本地 JSON 中,version=1、conversation_id、messages 已有明确格式:messages:[] 合法,缺失或 null 拒绝,消息 ID 在会话内不能重复。这是本地文件合同,并非自动成为联网 IM 的传输协议。未来若把消息放到网络上,仍需决定认证来源、请求身份、响应语义、消息边界、资源上限和版本兼容。尤其不能仅因客户端填了 sender_id:"u-a" 就相信它有权代表 u-a;身份验证与会话授权在 S3 讲。
初学者还要把文字与字节分开。比如 你好 在 UTF-8 中为 6 字节,虽然通常看作两个汉字。若某教学协议在正文前放长度字段,它应明说“长度单位是字节”还是别的单位:
仅作概念演示的应用层载荷:
body_length = 6(字节) | body = E4 BD A0 E5 A5 BD(“你好”的 UTF-8)这个示意不是本课程已上线的 wire 格式,也没有规定长度字段占几个字节或是否允许大端/小端;完整帧设计要等 04.08。它只说明双方必须就边界达成同一解释。若发送方写 6 而接收方按 6 个字符去读,就可能错位甚至等待永远不会到来的字节。非法长度、超限和未知版本应被明确拒绝,而不靠“读到 EOF 就算一条完整消息”猜测。
三、分层与封装:应用消息不等于网络包
网络通信包含多类工作。为降低每个程序必须理解的细节,可按职责分层:
| 层次的入门名称 | 本章先理解的职责 | 暂不展开的细节 |
|---|---|---|
| 应用层 | m-a 的含义、请求和回答合同 | HTTP/WebSocket 的具体格式在 04.03 |
| 传输层 | 两端程序间的传送服务 | TCP/UDP、可靠性和流控在 04.04–04.06 |
| 网络层 | 跨网络定位与转发 | IP、路由与路径在 04.02、04.11 |
| 链路层 | 相邻节点之间承载数据 | 帧、局部寻址与 MTU 在 04.11 |
封装是发送方向下一层交付数据时,该层为履行自己的职责加入控制信息;接收方再按层解释并把上层数据交回去。可以想成一个分层包裹,但实际协议和接口比信封比喻更严格:
发送端:应用消息 m-a
→ 应用编码后的字节
→ 传输层的数据与控制信息
→ 网络层的数据与转发信息
→ 链路上的承载单位
接收端:逐层检查、取出上层内容 → 应用才解释 m-a中间转发节点通常主要按较低层信息处理本次路径;代理等设备也可能理解更高层,因此不要把上图当作所有实际网络设备永不例外的规则。分层的教学价值是:文件中的消息、应用请求、传输字节、网络分组各有自己的边界和错误含义。若本地编码已失败,就不用先猜路由;若名称尚未解析,服务端还没机会校验 JSON。
尤其不要画成“一条 IM 消息 = 一个 TCP 段 = 一个 IP 包”。TCP 为应用提供的是字节流,不保留应用消息的边界;一次应用写入的字节可以分段到达,也可能与相邻写入一起被读取。应用必须按自己协议的长度或分隔规则重建完整消息。传输分段、重复、确认和短读短写将在 04.04 与 04.08 细讲。即使传输层报告某种确认,也不能直接当作服务端已把 m-a 保存,或 u-b 已经看见。
四、画出 u-a → 服务 → u-b 的边界与证据
先只画概念路径,不假设具体网络库、数据库或 OpenIM 实现:
u-a 点击发送
→ 客户端检查输入并编码应用请求
→ 本机操作系统接受要发送的字节
→ 网络路径使字节抵达教学服务的一侧
→ 服务端解码、鉴别请求并决定业务结果
→ 若产品设计要求,保存或为 u-b 组织转发
→ 设备 B 收到可解释的应用消息
→ u-b 实际打开并阅读(另一个事件)每个箭头都可能失败,也各需独立证据。比如客户端 Write 返回成功,只能说明它在该接口合同范围内交出了字节;字节是否经网络到对端、服务是否解码、身份是否允许、历史是否保存、B 是否在线,不能由这个返回值推出。即使未来使用 TCP,传输确认也只覆盖对应传输层的事实,不能代替应用层保存或用户已读。服务端可能先接受再异步处理,也可能在完成保存后才回应;这些要由具体应用合同定义。
| 观察点 | 若有对应证据,可以说什么 | 不能跳到哪一步 |
|---|---|---|
客户端形成 m-a | 本地对象存在 | 服务端已收到 |
| 本机写入接口成功 | 本机按接口接收了待发送字节 | 服务端业务接受 |
| 服务端返回应用层“已接受” | 它按约定处理到某一检查点 | B 设备已显示,除非合同明确包含并证实 |
| B 的客户端确认收取 | B 设备按该协议看到数据 | u-b 本人已读 |
| 已读事件被记录 | 某个定义的已读条件发生 | 所有设备、所有时刻都已一致 |
同样一个“成功”字样如果没有主体、时间、范围和确认点,无法用于排障或设计可靠性。后续 P2、P3 会逐项定义这些保证;04.01 只训练你不跨越证据边界。
五、一次单链路时延怎样由四部分组成
用户抱怨消息慢,不能只说“网络距离太远”。从一个节点到下一条链路的入门模型看,时间至少可拆成处理、排队、传输、传播四部分。它们回答不同问题,单位都应先化为秒或毫秒:
| 部分 | 直观问题 | 本章怎样建模 |
|---|---|---|
| 处理时延 | 节点检查与准备数据花多久? | 给定或测量,不能由距离推出 |
| 排队时延 | 前面还有多少待发数据? | 随流量、队列和策略变化 |
| 传输时延 | 将 L 比特推入速率 R 的链路需多久? | L / R,L 单位 bit,R 单位 bit/s |
| 传播时延 | 已进入链路的信号穿越距离 d 需多久? | d / v,d 单位 m,v 单位 m/s |
传输与传播不是一个动作:前者像将整段比特依次放上链路所花的时间,后者像其中信号沿介质前进的时间。提高链路速率 R 会降低同样 L 的传输时延;若距离和传播速度不变,它不会让 d/v 自动减半。处理、排队也不会因单纯改 R 就按同一比例缩小。
现在给一组完全声明的纸上条件:一个节点向下一跳发送 1200 B 数据,本题将它当作要传输的总长度;链路速率 1,000,000 bit/s;传播距离 1200 km,假定信号速度 200,000,000 m/s;节点处理 2 ms,排队 5 ms。本模型不计链路层额外头部,也不含反向确认、握手、重传、服务端存储或 B 的显示。
1200 B × 8 bit/B = 9600 bit
传输:9600 bit ÷ 1,000,000 bit/s = 0.0096 s = 9.6 ms
距离:1200 km = 1,200,000 m
传播:1,200,000 m ÷ 200,000,000 m/s = 0.006 s = 6 ms
合计:2 ms 处理 + 5 ms 排队 + 9.6 ms 传输 + 6 ms 传播 = 22.6 ms核对量纲是算术的一部分:bit / (bit/s) = s,m / (m/s) = s。把 1200 B 当成 1200 bit 会把传输时间算小 8 倍;把 1200 km 直接除以 m/s 也会错 1000 倍。03.01 的位、字节和前缀知识在这里成为网络分析的基础。
若只把速率改成 10,000,000 bit/s,保持本题其他假设不变,传输变为 9600/10,000,000 = 0.96 ms,传播仍是 6 ms,模型合计 2+5+0.96+6=13.96 ms。真实排队时间也许会随速率变化,题目里的“仍是 5 ms”只是为了隔离一个因素。整条 A→服务→B 路径有多跳和多个应用阶段,绝不能把这个 22.6 ms 单链路模型写成实际收件延迟。
六、速率、吞吐、排队与失败要按阶段说
链路速率是某段链路在既定条件下可传送比特的速率,常见单位 Mb/s。本章用十进制 1 Mb/s = 1,000,000 bit/s;1 MB/s = 1,000,000 byte/s,数值上约等于 8 Mb/s,不要把小写 b 与大写 B 混用。应用吞吐则是某段观察时间里完成的应用数据量或消息数除以该时间;它还受编码开销、路径瓶颈、排队、接收方处理、重传与业务规则影响。不能由“接入链路标称 1 Mb/s”直接推出每秒一定能送达多少条 m-a。
排队发生在多个位置:网络设备要排链路发送,服务端可能排业务处理,存储也可能排查询。它们都使请求的墙上时间增长,但队列对象和处理能力不同。03.04 的“可运行线程等 CPU”又是另一种等待。定位延迟时应把时间标注到阶段,而不是笼统把所有等待叫“网络慢”。当到达量长期大于某阶段可处理量,队列可能持续增加;真实缓冲有限时还会出现拒绝、丢弃或背压,具体策略须由协议和系统设计说明。
| 假想现象 | 先定位的阶段 | 不能立刻断定 |
|---|---|---|
| 客户端尚未把正文编码成功 | 应用输入与协议 | IP 路由故障 |
| 对方位置尚未找到 | 名称或地址入口,04.02 深化 | 服务端已拒绝消息 |
| 连接尚未建立 | 传输/端点阶段,04.02–04.05 深化 | JSON 格式一定无效 |
| 字节传到了服务端但返回非法版本 | 应用协议解析 | 链路没有传输能力 |
| 服务端接受却 B 暂未显示 | 服务后续路由/设备状态 | 必然是网络丢包 |
超时也只说明在规定期限内没有拿到所等的回答,不自动证明对方完全没有处理。若调用者重试,可能造成重复请求;如何用消息身份、确认点与补偿应对,会在 S5 讨论。当前只需在失败报告中写明“哪一端在等哪一种回答、超时从何时开始、此前有哪些已证实步骤”,不要把“未知结果”直接改写为“必然失败”。
七、用反例保护业务判断
反例一:一个网络包到达,便说 u-b 已读。 网络层的数据到达某个设备或节点,与 B 的应用是否解码、是否显示、用户是否打开,是不同事件。必须用各层对应的观察证明。
反例二:传输确认便说服务端历史已保存。 传输层确认只覆盖其协议定义的数据交付范围;服务端可能尚未验证权限、尚未写存储或写入失败。应用层“受理”也要明确是否包含持久化。
反例三:连接看起来还在,便说用户在线可达。 中间设备、移动网络、应用进程和设备状态可能改变;连接状态不等于端到端可用,更不等于用户当前在读屏幕。心跳、重连和在线语义在 04.05 与 S5 深化。
反例四:客户端写了 sender_id:"u-a",便信任它。 字段是请求内容,不是身份证明。服务端身份验证和会话成员授权是单独的业务边界,S3 才详细设计。
反例五:把正文压缩一半,时延必然减半。 只有 L/R 中的 L 在相同链路条件下可能下降;压缩/解压会增加处理,传播 d/v 不变,排队也可能变化。瓶颈若在存储或应用队列,结果更不能由报文大小单独预测。
一个实际业务问题应写成可验证的问题。例如“单聊发送后 2 秒内 B 未显示”,要先问 A 是否拿到服务端何种回答,服务是否记录了 m-a 的业务状态,B 是否有连接或补拉路径,各阶段时间怎样记录;“群聊慢”要再加目标数量和处理队列。04.01 没有这些真实观察,也没有运行中的在线 IM,因此只给出需要哪些证据,不为用户构造一个未经验证的根因。
八、完整纸上路径与分层练习
请先在纸上画一条 u-a(A) → 教学服务 → u-b(B) 路线,用不同符号标出:业务消息、编码字节、网络承载、服务端业务决定、B 设备显示和已读。再把第五码 1200 B 单链路模型写在 A→服务中的某一跳旁,不要把该数字画成整个业务请求时长。最后改变一个条件,解释哪些结论随之变化:
- 协议版本不被识别:底层字节可能已到服务端,但应用层应拒绝,不得报告“已保存”。
- 排队从 5 ms 变 25 ms:本题单跳合计变为 42.6 ms,传输与传播仍分别是 9.6 与 6 ms;这不自动说明排队发生在何处。
- B 暂离线:A 到服务端的路径可能完成,B 显示尚无证据;是否存储待补拉依赖产品合同。
这些数字和结论都是静态预期。学习者后来实现联网小模型时,应单独记录代码版本、拓扑、请求、阶段时间、实际结果与未覆盖范围,不能把纸上表格填入“已经实测”。
分层练习:先回答,再核对反馈
1. 客户端一定是手机,服务端一定是一台固定物理机器吗?
不一定。它们是一次交互中的软件角色,部署形态可以变化。
2. `u-b` 是网络地址吗?
不是。它是应用用户身份;地址和端口在 04.02 另讲。
3. A 的 Go 变量里有 `m-a`,能说明服务端读到了吗?
不能。变量仍在 A 进程的本地地址空间。
4. 协议除了字段名还要约定什么?
字节编码、边界、版本、字段语义、交互顺序与失败表示。
5. 本地 JSON v1 自动就是线上消息格式吗?
不是。它是 01.12 的文件合同,联网协议必须另外定义。
6. `你好` 的长度字段若按 UTF-8 字节,值是多少?
6 字节;它通常看作两个汉字,但协议必须说明单位。
7. 为什么只信 `sender_id` 字段不够?
客户端可自行填写字段;真实身份与会话授权需服务端独立验证。
8. 一次应用 `Write` 一定对应一个 TCP 段吗?
不一定。TCP 是字节流,不保留应用写入或消息边界。
9. 一条业务消息与一个 IP 包总是一一对应吗?
不是。分层封装与传输分段可改变承载单位的数量。
10. 什么叫封装?
下层为履行自己的职责,在承载上层数据时加入相应控制信息。
11. 1200 B 是多少 bit?
1200×8=9600 bit。
12. 9600 bit 在 1,000,000 bit/s 链路上的传输时间?
9600/1,000,000=0.0096 s=9.6 ms。
13. 1200 km、传播速度 2×10⁸ m/s 的传播时间?
1,200,000/200,000,000=0.006 s=6 ms。
14. 本题单跳四项合计多少?
2+5+9.6+6=22.6 ms,只在题设条件下成立。
15. 速率提升十倍,传播时间也会降为 0.6 ms 吗?
不会。在距离与传播速度不变时仍是 6 ms;传输降为 0.96 ms。
16. `1 MB/s` 与 `1 Mb/s` 数值相同吗?
不同。本题十进制单位下前者约为后者的 8 倍。
17. 单链路 22.6 ms 能作为 A 到 B 的实测端到端延迟吗?
不能。它没有计入多跳、反向回答、握手、应用处理和 B 显示。
18. 超时就证明服务端没有保存 `m-a` 吗?
不能。超时只说明期限内没有收到等待的回答;结果可能未知。
19. TCP 的传输确认就等于服务端业务接受吗?
不等于。服务端的解析、权限、存储和回答各有独立确认点。
20. B 的设备收到了消息,便证明 `u-b` 已读吗?
不能。设备接收和用户打开阅读是不同事件。
21. 排队时间变长,能立刻断定是运营商链路拥塞吗?
不能。网络节点、服务端应用队列和存储都可能排队,需要阶段证据。
22. 当前课程本地 `imhistory check` 能证明在线消息成功吗?
不能。它只核对本机文件格式与领域规则,不联网。
来源与下一步
- Kurose 与 Ross 的作者课程入口及时延知识检查:网络分层、处理、排队、传输与传播的入门顺序。
- RFC 9293:TCP:传输层字节流与后续可靠传输的标准参照;本章不提前把传输确认扩展成应用层送达。
- Go
net包:04.02–04.03 学习端点和连接时的标准库入口。
下一章04.02 地址、名称与路由入口将把本章的“端点在哪里”细化为名称、IP、端口与路由入口;先能独立画出 u-a → 服务 → u-b 的分层路径,并说清一个 22.6 ms 单链路数字没有覆盖哪些业务阶段。