我之前的写的日志系统,我发现每次都要手动输入 IP 地址才能开始收集日志,觉得很麻烦,所以我想着写一个自动连接日志服务器的功能。其功能流程如下:
1. 什么是 Bonjour ?
具体参考地址:
Bonjour 是苹果公司提出的一套零配置网络协议,旨在通过 IP 实现设备的自动发现和服务注册。它源于互联网工程任务组(IETF)下的 ZEROCONF 工作组,主要解决以下三个方面的问题:
- 地址分配:为主机分配 IP 地址,支持自分配的链接本地地址。
- 命名:使用多播 DNS(mDNS)在本地网络中通过名称而非 IP 地址进行设备识别。
- 服务发现:自动查找网络上的服务,使应用程序能够轻松识别和连接所需的服务。
Bonjour 使用户无需手动配置网络设置,简化了设备的连接和服务的使用,提升了用户体验。
2. 基于 Bonjour 的约定
日志系统提供的是日志和网络记录,日志上传的方式目前是基于 http 实现的,所以现在广播的内容如下:
{
"name": "Log Record Server",
"type": "http",
"port": 27751,
"protocol": "tcp",
"txt": {
"path": "join",
"token": "***"
}
}
之所以有 token 是为了标识用户,保证是日志系统发出的。
所有请求接口公共请求头:
{
"Content-Type": "application/json;charset=utf-8"
}
接口说明: 加入日志系统接口
- 接口地址:
/join - 请求方式: POST
请求参数:
| 参数名 | 类型 | 必填 | 描述 |
|---|---|---|---|
| model | string | 是 | 手机型号 |
| token | string | 是 | 发现返回的 token |
| id | string | 是 | 设备信息md5签名 |
请求示例:
{
"model": "john@example.com",
"token": "***",
"id": ""
}
成功响应示例:
{
"code": 0 | 1 | 2,
"message": "ok | token错误 | Access denied"
}
3. 环境说明以及使用
我们 App 采用的是 react-native ,其对应的库为:react-native-zeroconf ;日志系统使用 electron 开发的,其环境是 nodejs ,其对应的库为:bonjour-service 。
3.1. react-native-zeroconf 安装与使用
官网对应的教程地址:react-native-zeroconf
使用 yarn 安装:
yarn add react-native-zeroconf
对于 android 来说,打开项目的 AndroidManifest.xml 文件,添加以下权限:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
这样 android 的配置就完成了。对于 ios 来说在 plist 文件中添加以下内容:
<key>NSBonjourServices</key>
<array>
<string>my_service._tcp.</string>
<string>my_other_service._tcp.</string>
</array>
<key>NSLocalNetworkUsageDescription</key>
<string>Describe why you want to use local network discovery here</string>
其中 my_service._tcp. 和 my_other_service._tcp. 代表本地服务名称,也就是:服务类型.协议.,日志系统的类型是 type ,协议是 tcp ,所以就写成 http.tcp.。NSLocalNetworkUsageDescription 则代表对应的提示,打开 App 后会出现这样的提示。
使用 react-native 查看附近的服务很简单:
import Zeroconf from "react-native-zeroconf";
const zeroconf: Zeroconf = new Zeroconf();
zeroconf.on("resolved", async (service) => {
console.log(service)
});
zeroconf.scan("http", "tcp");
当发现后就会打印,打印的信息就是跟发现服务相关的信息。打印信息大致如下(日志系统打印的):
{
"addresses": [
"192.168.118.103",
"fe80::14a4:ff35:6d14:3c25"
],
"fullName": "admindeiMac.local._http._tcp.",
"host": "admindeiMac.local.",
"name": "Log Record Server$$1932364ea87-0-8822403450b6",
"port": 27751,
"txt": {
"path": "/join",
"token": "1932364ea87-0.8822403450b6"
}
}
其中每个参数的大致含义为:
- addresses 保存的是发布服务的 IP 地址,这里是 IPv4 和 IPv6
- fullName 发布服务的完整名称
- host 跟网址差不多,提供服务的主机名
- name 发布时指定的服务名称
- port 服务需要使用的端口号
- txt 额外的数据,如果服务提供方需要额外的数据,那就会通过这个
当 zeroconf 去掉订阅后会收到 remove 的事件:
zeroconf.on("remove", (name) => {
console.log("remove-----====", name);
});
当我关闭日志系统后,这里会打印,打印内容大致为:Log Record Server$$1932364ea87-0-8822403450b6 ,也就是发布的 name 名称。
扫描附近的服务使用 scan 方法,其 API 参数说明:
scan(
/** @default "http" */
type?: string,
/** @default "tcp" */
protocol?: string,
/** @default "local." */
domain?: string,
/** @default "NSD" */
implType?: ImplType,
): void;
其中 type 为服务类型,比如日志系统就是 http 。不管事发现服务还是发布服务都可能出现错误,所以一般需要监听是否有错误发生,这样也方便排查问题。
zeroconf.on("error", (err) => {
logger.warn(LOG_KEY, "zeroconf出现错误", err);
})
至于发布服务,也能使用 react-native-zeroconf 来发布服务,但是由于我这里不需要所以就不再说明,如果仍然想知道,可以参考 bonjour-service 这个库。
3.2. bonjour-service
我用于在 electron 端发布服务的;在日志系统中有一个 http 的服务,专门用来收集 app 上报上来的数据。如果 app 知道日志系统的 IP 地址和端口号可以立马将本地的日志上报到日志系统中;我觉得手动配置 IP 地址啥的比较麻烦,于是我就编写了这套自动发现的模式,只要你的手机和日志系统在同一个局域网的网一网段下就能实现自我发现。
官网对应的教程地址:bonjour-service
使用 yarn 安装:
yarn add bonjour-service
接下来就是开心的使用了,不像 rn 那样还需要那么多设置,非常麻烦。
import { Bonjour } from 'bonjour-service';
const bonjour = new Bonjour()
bonjour.publish({
name: `Log Record Server$$${this.token}`,
type: 'http',
port: httpPort,
protocol: 'tcp',
txt: { path: JOIN_PATH, token: this.token },
});
这样就发布了一个服务,等着被其他需要的发现。接下来我们说说各个参数的含义:
其中 new Bonjour() 也可以传入一些参数:
{
multicast: true // use udp multicasting
interface: '192.168.0.2' // explicitly specify a network interface. defaults to all
port: 5353, // set the udp port
ip: '224.0.0.251', // set the udp ip
ttl: 255, // set the multicast ttl
loopback: true, // receive your own packets
reuseAddr: true // set the reuseAddr option when creating the socket (requires node >=0.11.13)
}
| 参数 | 功能 | 默认值 | 参数类型 | 技术标准 | 注意事项 |
|---|---|---|---|---|---|
multicast | 是否启用多播模式 | true | 布尔值 | RFC 1112 | • 启用后使用 UDP 多播通信 • 需要网络和操作系统支持 • 某些云环境可能默认禁用 |
interface | 指定多播网络接口 | all | 字符串 | RFC 3678 | • 多网卡环境特别有用 • 指定后仅在特定接口收发包 • 需确保接口支持多播 |
port | 指定 mDNS 服务端口 | 5353 | 数字 | RFC 6762 | • IANA 官方 mDNS 端口 • 不建议修改 • <1024 端口可能需要 root 权限 |
ip | 指定 mDNS 多播地址 | '224.0.0.251' | 字符串 | RFC 6762 Section 5 | • 标准 mDNS 多播地址 • 不建议修改 • IPv6 使用 FF02::FB |
ttl | 多播包生存时间 | 255 | 数字(0-255) | RFC 1112 Section 6.1 | • 影响数据包传播距离 • 较大值传播更远 • 局域网可设小值 |
loopback | 是否接收自己的多播包 | true | 布尔值 | RFC 3678 Section 5.2 | • 开发调试建议启用 • 生产环境可禁用 • 影响 IP_MULTICAST_LOOP |
reuseAddr | 允许端口复用 | true | 布尔值 | POSIX.1-2017 RFC 3493 | • Node.js ≥ 0.11.13 • 多进程部署必须启用 • 影响 SO_REUSEADDR |
只不过在使用过程中,会提示没有这样的属性,这是因为这个库的类型文件有问题导致的。可以不管这样的报错,实际上是有的。只不过我暂时用不到设置这些值。对于 publish 来说,下面属性就跟发现很多相似了。
| 参数 | 功能 | 默认值 | 参数类型 | 技术标准 | 注意事项 |
|---|---|---|---|---|---|
name | 服务实例名称 | 必填 | 字符串 | RFC 6763 Section 4.1.1 | • 在网络中必须唯一 • 支持 UTF-8 编码 • 用于服务发现显示 |
type | 服务类型 | 必填 | 字符串 | RFC 6763 Section 7 | • 格式:_服务名._协议 • 例:_http._tcp • IANA 注册列表 |
port | 服务端口号 | 必填 | 数字 | RFC 6763 Section 6.1 | • 1-65535 范围 • <1024 可能需要权限 • 建议使用注册端口(1024-49151) |
host | 主机名 | 本机名 | 字符串 | RFC 6763 Section 4.1.2 | • 可以是 FQDN • 默认使用系统主机名 • 需要能被 DNS 解析 |
txt | 服务元数据 | {} | 对象 | RFC 6763 Section 6.3 | • 键值对格式 • 值限制为字符串或 Buffer • 单条记录不超过 255 字节 |
subtypes | 服务子类型 | [] | 字符串数组 | RFC 6763 Section 7.1 | • 用于服务细分 • 可选功能 • 便于服务过滤 |
protocol | 协议类型 | 'tcp' | 字符串 | RFC 6763 | • 通常为 'tcp' 或 'udp' • 影响服务发现 • 建议保持默认 |
使用 bonjour-service 来进行服务发现,大致参考 react-native-zeroconf 。
4. 功能演示
如果你的 react-native App 集成了 @wutiange/log-listener-plugin 插件,版本需要大于等于 2.0.1-alpha.3 版本;然后在 App 中安装了 react-native-zeroconf 插件就可以实现自动发现日志服务的功能。然后就可以在日志系统中看到:
当你点击连接以后就会变成下面的页面:
这样就代表已经跟日志系统建立了连接,接下来你在 App 中的任何操作所产生的日志都能上报到日志系统中。