我是 webrpc 作者。SDK、Token 和开发文档在 https://webrpc.cn。
第一次接 webrpc 的人,几乎都会愣一下:
登录成功之后,对端发来的数据不是「SDK 调一下你注册的函数」;
而是 SDK 在本机开一个 TCP 端口,你自己connect 127.0.0.1:port,再循环读二进制帧。
很多人下意识会问:为什么不做成 C 函数指针、Java/Android 的 JNI 回调,或各语言 FFI 直接注入业务函数?那样写 Demo 不是更短吗?
这篇只回答这一件事。原理文里提过「回调端口」四个字;这里把设计动机写透。
webrpc是面向无公网 IP 的跨平台 P2P 通信 SDK:Token 标识设备,登录后建加密会话,再用SendData/SendFile收发。官网:https://webrpc.cn。
先把机制说清楚
客户端就绪后:
- 调用
WebrpcClient_GetReceivePort,拿到本机端口号。 - 业务进程(或独立进程)连接
127.0.0.1:该端口。 - 循环读取帧。帧头与官网一致:
sessionId : uint32 大端(4 字节) type : uint8(1 字节) 2 = 数据流 1 = 文件流数据流后面跟长度与 payload;文件流后面跟文件名与文件内容。你按sessionId区分会话,按type区分这是业务字节还是文件。
三条硬边界:
- 回调口永远只监听本机
127.0.0.1,不会绑0.0.0.0对外暴露。 - SDK不会主动调用你注册的任何业务函数。没有「注入回调表」这一条路。
- 这是刻意设计,不是历史遗留的临时方案。
函数注入看起来香,跨语言时却很贵
「函数注入」在不同栈里名字不一样,本质都是:让 SDK 在某个线程里,直接调用你提供的入口。
| 形态 | 典型样子 | 你要处理的额外问题 |
|---|---|---|
| C 函数指针 | RegisterOnData(fn),SDK 调fn | 调用约定、线程、生命周期、是否可重入 |
| Java / Android JNI | Native 线程里CallVoidMethod | JNIEnv、AttachCurrentThread、全局引用、异常 |
| 各语言 FFI | Rust / Python / Go 把闭包或函数塞进动态库 | ABI、GC、异步运行时、谁持有所有权 |
每多一种语言,就要再维护一套「SDK 如何安全地调回业务」的约定。webrpc 的目标是:同一套 Native 能力,给 C、Go、Python、Rust、Java、Android 等示例用。若走注入,兼容面会按语言数放大,排错也会变成「到底是 JNI 挂了,还是业务逻辑挂了」。
本机 TCP 把问题收成一件事:谁都会连回环、读字节。
帧格式固定,和业务用什么语言无关。
为什么坚持本机 TCP:五个工程理由
1. 一套协议,适配多种语言
C 用recv,Go 用net.Conn,Python 用socket,Rust 用TcpStream,Java 用Socket。
读的是同一份二进制帧,不需要为每种语言再写一套「回调签名」。
2. 刻意不做「SDK 主动调你」
SDK 的职责停在:会话上的数据到达后,写到本机监听端口。
业务何时读、读多少、解析成 JSON 还是 Protobuf、要不要丢到工作线程——都由你决定。
这样动态库边界清晰:SDK 不持有业务函数指针,也不依赖某门语言的运行时。
3. 业务可以独立进程
回调口在本机。网盘守护进程、Agent、桌面壳,可以是不同进程:只要能连上127.0.0.1:port,就能收到同一会话上的推送。
函数注入模型通常默认「回调和 SDK 在同一进程地址空间」,拆进程要另起一套 IPC。
4. 崩溃与卡顿更好隔离
业务解析坏包、阻塞、甚至崩溃时,问题首先落在「读 TCP 的那一侧」。
SDK 进程(或动态库所在宿主)不必因为业务回调里抛了语言异常,就进入难以定义的状态。完全隔离取决于你怎么部署进程;但至少调用约定上,SDK 没有伸进业务栈去执行你的代码。
5. 排错用常规网络工具即可
端口号来自GetReceivePort。连不上,先查是不是连了局域网 IP、是不是还没登录就读、是不是端口记错。
这些是 TCP 程序员熟悉的问题,不必先怀疑「回调有没有被正确 Register」。
安全上再强调一次:只监听回环,对端外网机器不能直接连你的回调口。真正的跨网传输仍走 webrpc 会话;回调只是「已经到达本机之后,交给你的进程」这一层。
和 SendData 不是一回事
容易混的一句话是:「回调」和「发送」是不是同一条管道上的两头?
不是。
| 本机 TCP 回调 | SendData/SendFile | |
|---|---|---|
| 方向 | 接收对端已经送到本机的业务数据 / 文件 | 发送本端业务数据 / 文件到对端 |
| 你怎么用 | connect+ 读循环 | 调发送 API,带sessionId |
| 数据在哪边 | 对端 → 会话 → 本机 SDK → 你的读循环 | 你的进程 → SDK → 会话 → 对端回调口 |
一个管「收对方的业务数据」,一个管「发给对方的业务数据」。
超时、返回值、乱序,是发送 API 自己的语义;回调口解决的是接收侧怎么进进程。两件事不要绑在一起解释。
接入时按这个顺序验证
- 登录成功后再
GetReceivePort。 - 只连
127.0.0.1,不要连局域网 IP 或0.0.0.0。 - 先读通帧头:
sessionId+type,再解析 payload。 - 用一次对端
SendData,确认本机读循环能打出完整一帧。 - 再接自己的业务协议。
端口写死、登录未完成就读、连错地址,都会表现为「偶发收不到」。这些是接入问题,不是「该改回函数回调」的信号。
对照速查
为什么不用 C 函数指针 / JNI / FFI 注入?
跨语言时每一种运行时都要单独约定调用、线程和生命周期。webrpc 选择固定帧格式 + 本机 TCP,让各语言用同一套读逻辑。SDK不会主动调用业务函数。
回调会监听全网卡吗?
不会。永远只监听127.0.0.1。
业务能不能单独进程收数据?
能。连本机回调端口即可。
这和 SendData 阻塞有关系吗?
没有。TCP 回调负责收对端数据;SendData/SendFile负责发到对端。方向不同。
帧格式是什么?
大端sessionId(4 字节)+type(1 字节);2数据流,1文件流。细节以官网开发文档为准。
完整 API、各语言示例和 Token 在控制台:https://webrpc.cn。把「回调」理解成本机回环上的推送端口,而不是「SDK 调你的函数」,后面的排错会顺很多。