MDNS学习笔记

MDNS学习笔记
WildboarGMDNS学习笔记
[TOC]
🚥引言
1️⃣ 为什么需要 DNS?
在计算机网络中,设备之间真正通信依赖的是 IP 地址,比如:
1 | 192.168.0.100 |
但对人类来说,记住一堆数字显然并不友好。
于是 DNS(Domain Name System) 应运而生,它的作用非常简单:
把人类可读的域名,转换成机器可用的 IP 地址
例如: www.example.com → 93.184.216.34
2️⃣ 传统 DNS 是如何工作的?
在一个典型的网络环境中,DNS 的工作流程是这样的:
- 设备想访问
www.example.com - 向配置好的 DNS 服务器发起查询
- DNS 服务器返回对应的 IP 地址
- 设备使用该 IP 建立连接
这里有一个前提条件:
网络中 必须存在一个 DNS 服务器
并且所有设备都 知道它的地址
在互联网环境下,这完全不是问题:
- 运营商
- 路由器
- 公共 DNS(8.8.8.8、1.1.1.1)
3️⃣ DNS 在局域网中遇到的问题
但如果我们把场景换到 局域网(LAN),问题就来了。 比如:
- 家庭内网
- 实验室
- 开发板 + PC
- IoT 设备
- 临时搭建的小网络 在这些场景中,往往会出现:
- ❌ 没有专门的 DNS 服务器
- ❌ 设备 IP 由 DHCP 动态分配
- ❌ IP 经常变化
- ❌ 手动维护 hosts 文件不现实
你可能会遇到这种尴尬情况:
“我知道设备在局域网里,但我不知道它现在的 IP 是多少。”
4️⃣ 问题来了:有没有一种「零配置」的名字解析?
理想的情况是:
- 不需要配置 DNS 服务器
- 不需要手动指定 IP
- 设备一上线就能被访问
- 用名字而不是 IP 比如我希望直接访问:
1 | http://客厅灯.local |
而不是去路由器后台翻 IP。
这正是 mDNS 要解决的问题。
🔋工作原理
1️⃣.mDNS 的核心思想
mDNS(Multicast DNS)的核心思想可以用一句话概括:
没有 DNS 服务器,所有设备既是客户端,也是服务器。
可以理解为:
一种在局域网中,不依赖 DNS 服务器的域名解析机制
在传统 DNS 中:
- 查询请求发给 DNS 服务器
- 服务器返回结果
而在 mDNS 中:
- 查询请求通过 组播 发送到局域网
- 所有设备都能收到这个请求
- 只有“名字匹配”的设备才会回应
核心特点是:
- 📡 使用 组播 而不是单播
- 🌐 作用范围仅限 本地链路 不会被路由转发
- ⚙️ 完全 零配置
- 🏷️ 常见域名后缀是
.local
2️⃣. mDNS 使用的网络基础
mDNS 并没有重新发明一套协议,而是建立在现有网络机制之上:
| 项目 | mDNS 取值 |
|---|---|
| 传输层协议 | UDP |
| 端口号 | 5353 |
| IPv4 组播地址 | 224.0.0.251 |
| IPv6 组播地址 | ff02::fb |
| 作用范围 | 本地链路(Link-Local) |
3️⃣. 一个 mDNS 查询是如何发生的?
以我的wb2模组模拟的一个灯服务为例:
Step1:发起查询
我在浏览器输入:
1 | http://mywb2light.local |
系统发现:
- 后缀是
.local - 这是一个 mDNS 域名
于是构造一个 DNS 格式的数据包(注意:格式还是 DNS):
- 查询名:
mywb2light.local - 查询类型:A / AAAA
- 目标地址:224.0.0.251:5353
并通过 UDP 发送到组播地址。
Step2: 局域网内设备接收查询
此时,局域网内所有加入组播的设备都会收到消息,比如你的打印机,文件服务器,nas
但每个设备都会检查,查询的名字是不是 “我”
显然,只会有我的wb2模组查到,哎呀,有人找我!
Step 3:目标设备回应
我的WB2开启了http灯的服务,且已经加入了组播 注册的主机名就是:
1 | mywb2light.local |
它会:
- 构造一条 DNS Response
- 包含自己的 IP 地址, 比如:
192.168.0.103 - 同样发送到 224.0.0.251:5353
而不是直接单播给请求者。
Step 4:请求方接收并缓存结果
请求设备收到响应后:
- 提取 IP 地址
- 缓存一段时间(TTL)
- 后续访问无需再次查询
至此,一次 mDNS 解析完成。
4️⃣.为什么回应也要用组播?
你可能会问:
“既然已经知道是谁在问了,为什么不直接单播回应?”
这是 mDNS 的一个重要设计点:
- 让所有设备都看到这个响应
- 其他设备可以:
- 更新缓存
- 避免重复查询
- 降低整体网络流量
这种机制被称为:
共享响应(Shared Response)
当然,在某些情况下(如冲突处理),也可以使用单播回应。
5️⃣.名字冲突是如何解决的?
既然没有中心服务器,就不可避免会出现冲突:
两台设备都想叫
卧室灯.local怎么办?
mDNS 的解决方式是:
启动时“自我查询” 设备上线后,会先查询:
1
卧室灯.local
看有没有人已经在用。
冲突检测 如果收到响应,说明名字已被占用:
自动修改名字
常见策略:
1
2
3卧室灯.local
卧室灯-2.local
卧室灯-3.local
冲突广播
如果真的发生冲突(两个设备同时声明):
- 会广播冲突信息
- 其中一个设备必须让步并改名
整个过程完全自动完成。
💉如何发布一条记录
在 mDNS 中,“发布一条记录”并不是向某个服务器注册,而是:
在合适的时机,向局域网主动广播自己拥有的 DNS 记录
这些记录通常包括:
- 主机名 → IP(A / AAAA)
- 服务信息(配合 DNS-SD)
mDNS 中的“记录”是什么?
mDNS 使用的仍然是标准 DNS Resource Record(RR),常见的有:
| 记录类型 | 作用 |
|---|---|
| A | 主机名 → IPv4 |
| AAAA | 主机名 → IPv6 |
| PTR | 服务类型枚举 |
| SRV | 服务所在主机与端口 |
| TXT | 服务附加信息 |
例如最简单的一条主机记录:
1 | my-device.local. A 192.168.1.50 |
发布记录的基本原则
mDNS 有几个非常重要的原则:
- 没有注册中心
- 记录由“拥有者”负责发布
- 通过组播发送
- 其他设备被动缓存
主机名记录(A / AAAA)的发布流程
Step 1:名称探测(Probe)
设备启动后,不能立刻宣布自己,而是先探测:
“有没有人已经在用
my-device.local?”
做法是:
- 向
224.0.0.251:5353发送 Query - 查询名:
my-device.local - 查询类型:A / AAAA
如果在规定时间内 没有收到回应,说明名字可用。
Step 2:宣布(Announce)
确认名字可用后,设备会主动发送 Announcement:
- 本质:一条 DNS Response
- 不针对某个查询
- 包含:
my-device.local的 A / AAAA 记录
- 发送到组播地址
通常会:
- 连续发送 2~3 次
- 间隔逐渐增大(防丢包)
这一步就是正式发布 mDNS 记录。
Step 3:周期性刷新
为了防止缓存过期:
- 每条记录都有 TTL(通常 120 秒)
- 设备会在 TTL 过半时再次广播
- 刷新仍然使用 Response(Announcement)
记录撤销(Goodbye)
当设备下线或服务停止时,不能直接消失。 正确做法是发送一条 Goodbye 包:
同样是 DNS Response
TTL = 0
表示:
“这条记录马上失效”
其他设备收到后会立刻清除缓存。
服务记录的发布(DNS-SD)
如果你要发布的不只是主机名,而是一个服务(比如 HTTP):
【示例目标】 发布一个 HTTP 服务: my-device.local:80
需要发布的记录(成组出现)
mDNS 服务发布通常包含 四类记录:
PTR
(服务类型 → 实例名)
1
_http._tcp.local. → My Web Server._http._tcp.local.
SRV
(实例名 → 主机名 + 端口)
1
My Web Server._http._tcp.local. SRV my-device.local:80
TXT
(服务属性)
1
My Web Server._http._tcp.local. TXT "path=/"
A / AAAA
(主机名 → IP)
1
my-device.local. A 192.168.1.50
mDNS 要求这些记录一起发布,否则服务发现会不完整。
什么时候该“发布”?
mDNS 并不是一直广播,而是事件驱动:
| 场景 | 是否发布 |
|---|---|
| 设备启动 | ✅ |
| IP 地址变化 | ✅ |
| 服务启动 | ✅ |
| 收到相关查询 | ✅ |
| TTL 即将过期 | ✅ |
| 设备下线 | ✅(Goodbye) |
1 | 设备启动 |
🔍服务发现的核心
在 mDNS 中,A/AAAA 解决的是“你在哪”,
而 PTR + SRV 解决的是“你提供什么服务,以及怎么连”。
这也是 DNS-SD(DNS Service Discovery)的核心设计。
1. 为什么不能只用 A 记录?
假设局域网中有多台设备:
dev1.localdev2.localdev3.local它们可能分别提供:- HTTP
- SSH
- MQTT
- 打印服务
如果只有 A 记录,你只能做到:
“我知道这个名字对应哪个 IP”
但你不知道:
- 哪些设备提供 HTTP?
- 服务监听在哪个端口?
- 一台设备是否有多个同类服务?
PTR 和 SRV 正是为了解决这些问题而存在的。
2. PTR:服务“目录索引”
PTR 记录的作用:
列出“某一类服务”在局域网中有哪些实例
PTR 的查询对象不是主机名,而是“服务类型” 例如:
1 | _http._tcp.local. |
这并不是某台设备,而是一个服务类别
PTR 记录的含义: 一条 PTR 记录长这样:
1 | _http._tcp.local. PTR My Web Server._http._tcp.local. |
含义是:
- 在本地网络中,存在一个 HTTP 服务实例
- 名字叫 My Web Server
一个服务类型可以对应多个 PTR
1 | _http._tcp.local. PTR Web1._http._tcp.local. |
表示:局域网中有两个 HTTP 服务实例
3. SRV:告诉你“怎么连”
真正的连接信息 是由 SRV 提供的。
SRV 记录包含什么? 一条 SRV 记录包含:
- 目标主机名
- 端口号
- 优先级(priority)
- 权重(weight)
1 | My Web Server._http._tcp.local. |
表示:
- HTTP 服务实例 My Web Server
- 运行在
my-device.local的 80 端口
4. 为什么端口不能直接写在 PTR 里
设计目标是:
- 一个服务实例 = 一个逻辑实体
- 它可以:
- 换端口
- 换主机
- 多主机负载 而 PTR 只负责索引,SRV 负责定位。
5. 服务发现的完整流程
step1:查询服务类型(PTR) 客户端发送:
1 | Query: _http._tcp.local. PTR ? |
得到:
1 | PTR → My Web Server._http._tcp.local. |
step2:查实例定位(SRV) 客户端继续查询:
1 | Query: My Web Server._http._tcp.local. SRV ? |
得到:
1 | SRV → my-device.local:80 |
step3: 解析主机名(A / AAAA)
1 | Query: my-device.local. A ? |
得到 IP
step4: (可选):读取 TXT
1 | Query: My Web Server._http._tcp.local. TXT ? |
获取额外属性(如路径、版本、能力)。
6. DNS-SD工程化的设计
| 记录 | 责任 |
|---|---|
| PTR | 发现(有哪些) |
| SRV | 定位(在哪 + 端口) |
| TXT | 描述(属性能力) |
| A/AAAA | 地址解析 |
这种拆分带来的好处:
- 一个设备可发布多个服务
- 一个服务可迁移主机
- 服务实例名可读性强
- 客户端无需硬编码端口
7. 完整的服务记录发布示例
1 | _http._tcp.local. PTR My Web Server._http._tcp.local. |




