我是 webrpc 作者。
做跨端互联时,最怕的不是业务字符串,而是 每换一个系统就换一套通信栈:Windows 一套、Android 一套、iOS 又一套,Linux 服务端再重写。协议对不齐、回调模型不一致,联调成本会把「能连通」拖成几个月。
这篇讲 webrpc 的跨平台口径:Windows / Android / iOS / Linux / macOS 共用同一套 API 与调用顺序------换的是对应平台的动态库和语言绑定,不是换一套打洞协议。不支持浏览器内嵌;浏览器实时音视频仍看 WebRTC。文末用 Go Demo 一分钟验证连通,正文附各语言同名 API 对照。
webrpc 是面向无公网 IP 的跨平台 P2P 通信 SDK:Token 标识设备,登录后建加密会话,再用 SendData / SendFile 收发。
不想写代码、先体验:装 File2File(桌面多系统);个人网盘再加 mywebdisk

「一套 API 全兼容」到底指什么
指的是:
| 含义 | 说明 |
|---|---|
| 同一套接口名与顺序 | New → LoginStatus → GetReceivePort → OpenSession → SendData / SendFile → Free |
| 同一套回调帧约定 | 本机连 127.0.0.1:port,按 sessionId / type 读数据流或文件流 |
| 换库不换心智 | 各系统下载对应 Native 库;Go / Java / Python / C / Rust 等按官网示例绑定 |
不是 :一份二进制拷到所有系统直接跑。Windows 用 Windows 库,Android 用 jniLibs 里的 so,iOS / macOS / Linux 各自取包。架构(amd64 / arm64)也不要混。
也不包括 :浏览器里直接 import 一个 JS SDK。当前产品面是原生多端;网页若只是管理后台,通信仍由桌面或 App 进程承担。

平台与语言怎么选
官网下载区按系统提供包,开发文档按语言给示例。常见组合:
| 端 | 常见语言 | 备注 |
|---|---|---|
| Windows | Go / C / C++ / Python | 桌面客户端、家里电脑 Agent |
| macOS | Go / C / Python / Rust | 与 Windows 对称的桌面端 |
| Linux | Go / C / Rust / Python | NAS、树莓派、常驻服务 |
| Android | Java(+ Native so) | 手机访客端、IoT |
| iOS | 官网 iOS 包 + 对应绑定 | 手机端;以控制台文档为准 |
语言再多,主线不变。Android 上多一步把 so 放进 jniLibs、用 JNA/JNI 声明同一组 C API;Python 用 ctypes 时注意 restype / argtypes。坑在绑定层,不在「每端发明一套 OpenSession」。
跨语言同名对照(概念级)
下面不是完整工程,只说明 名字与顺序一致(具体签名以官网各语言页为准):
text
WebrpcClient_New(token, passwd, permission)
WebrpcClient_LoginStatus(handle)
WebrpcClient_GetReceivePort(handle)
WebrpcClient_OpenSession(handle, peerToken, permission)
WebrpcClient_SendData(handle, sessionId, data, len, timeOutMs)
WebrpcClient_SendFile(handle, sessionId, absPath)
WebrpcClient_Free(handle)
Go 用 CGO、Java 用 JNA/JNI、Python 用 ctypes,包一层壳而已。业务协议(JSON、ping、拉文件)你自己定;连通层保持这一套。

复制粘贴跑通 Demo(Go)
用 Go 在任意已下载对应库的桌面系统上验证:两枚 Token、两端登录、OpenSession、一小包 SendData。Android / iOS 换官方移动示例即可,调用顺序相同。
目录
text
webrpc-crossplat-demo/
├── libwebrpc-Mac.h # Windows/Linux 换成官网对应文件名
├── libwebrpc-Mac.dylib
└── main.go
main.go
go
package main
/*
#cgo CFLAGS: -I.
#cgo darwin LDFLAGS: -L. -lwebrpc-Mac
#cgo linux,amd64 LDFLAGS: -L. -lwebrpc-Linux
#cgo linux,arm64 LDFLAGS: -L. -lwebrpc-Linux-arm64
#cgo windows LDFLAGS: -L. -lwebrpc-Windows
#if defined(__APPLE__)
#include "libwebrpc-Mac.h"
#elif defined(_WIN32)
#include "libwebrpc-Windows.h"
#elif defined(__linux__) && defined(__aarch64__)
#include "libwebrpc-Linux-arm64.h"
#elif defined(__linux__)
#include "libwebrpc-Linux.h"
#else
#error "unsupported platform"
#endif
#include <stdlib.h>
*/
import "C"
import (
"encoding/binary"
"fmt"
"io"
"log"
"net"
"os"
"os/signal"
"runtime"
"syscall"
"time"
"unsafe"
)
const (
tokenA = "YOUR_TOKEN_A"
passA = "YOUR_PASSWORD_A"
tokenB = "YOUR_TOKEN_B"
passB = "YOUR_PASSWORD_B"
)
func waitLogin(h C.GoUintptr, who string) {
deadline := time.Now().Add(60 * time.Second)
for time.Now().Before(deadline) {
st := C.WebrpcClient_LoginStatus(h)
log.Printf("[%s] LoginStatus=%d", who, int(st))
if st != 0 {
return
}
time.Sleep(time.Second)
}
log.Fatalf("[%s] 登录超时:检查 Token、密码、是否用对了 %s/%s 的库", who, runtime.GOOS, runtime.GOARCH)
}
func readCallback(who string, port int, ready chan struct{}) {
conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", port))
if err != nil {
log.Printf("[%s] 连回调失败: %v", who, err)
return
}
defer conn.Close()
log.Printf("[%s] 回调已连接 127.0.0.1:%d", who, port)
close(ready)
for {
sidBuf := make([]byte, 4)
if _, err := io.ReadFull(conn, sidBuf); err != nil {
log.Printf("[%s] 读 sessionId: %v", who, err)
return
}
sid := binary.BigEndian.Uint32(sidBuf)
typ := make([]byte, 1)
if _, err := io.ReadFull(conn, typ); err != nil {
return
}
switch typ[0] {
case 2:
lenBuf := make([]byte, 4)
if _, err := io.ReadFull(conn, lenBuf); err != nil {
return
}
n := binary.BigEndian.Uint32(lenBuf)
payload := make([]byte, n)
if _, err := io.ReadFull(conn, payload); err != nil {
return
}
log.Printf("[%s] 收到 session=%d: %s", who, sid, string(payload))
case 1:
nameLenBuf := make([]byte, 4)
if _, err := io.ReadFull(conn, nameLenBuf); err != nil {
return
}
nameLen := binary.BigEndian.Uint32(nameLenBuf)
io.CopyN(io.Discard, conn, int64(nameLen))
dataLenBuf := make([]byte, 4)
if _, err := io.ReadFull(conn, dataLenBuf); err != nil {
return
}
dataLen := binary.BigEndian.Uint32(dataLenBuf)
io.CopyN(io.Discard, conn, int64(dataLen))
default:
log.Printf("[%s] 未知 type=%d", who, typ[0])
}
}
}
func runA() {
token := C.CString(tokenA)
pass := C.CString(passA)
perm := C.CString("")
defer C.free(unsafe.Pointer(token))
defer C.free(unsafe.Pointer(pass))
defer C.free(unsafe.Pointer(perm))
h := C.WebrpcClient_New(token, pass, perm)
if h == 0 {
log.Fatal("[A] WebrpcClient_New 失败")
}
defer C.WebrpcClient_Free(h)
waitLogin(h, "A")
port := int(C.WebrpcClient_GetReceivePort(h))
ready := make(chan struct{})
go readCallback("A", port, ready)
select {
case <-ready:
case <-time.After(5 * time.Second):
log.Fatal("[A] 回调未连上")
}
log.Printf("[A] 在线 os=%s arch=%s ,等待 B ... Ctrl+C 退出", runtime.GOOS, runtime.GOARCH)
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
<-ch
}
func runB() {
token := C.CString(tokenB)
pass := C.CString(passB)
perm := C.CString("")
defer C.free(unsafe.Pointer(token))
defer C.free(unsafe.Pointer(pass))
defer C.free(unsafe.Pointer(perm))
h := C.WebrpcClient_New(token, pass, perm)
if h == 0 {
log.Fatal("[B] WebrpcClient_New 失败")
}
defer C.WebrpcClient_Free(h)
waitLogin(h, "B")
port := int(C.WebrpcClient_GetReceivePort(h))
ready := make(chan struct{})
go readCallback("B", port, ready)
select {
case <-ready:
case <-time.After(5 * time.Second):
log.Fatal("[B] 回调未连上")
}
peer := C.CString(tokenA)
defer C.free(unsafe.Pointer(peer))
start := time.Now()
sid := C.WebrpcClient_OpenSession(h, peer, perm)
if sid == 0 {
log.Fatal("[B] OpenSession=0:确认 A 在线、Token 正确;可跨系统(如 Linux A + Windows B)")
}
log.Printf("[B] OpenSession 成功 sessionId=%d 耗时 %v", uint32(sid), time.Since(start))
msg := C.CString("ping-crossplat")
defer C.free(unsafe.Pointer(msg))
ret := C.WebrpcClient_SendData(h, sid, msg, C.int(len("ping-crossplat")), 5000)
log.Printf("[B] SendData ret=%d", int(ret))
if ret == 1 {
fmt.Println("跨端 API 路径验证通过:同一套 OpenSession/SendData,换系统只换库")
} else {
fmt.Println("本次未成功,检查库架构与对端后重试")
os.Exit(1)
}
time.Sleep(2 * time.Second)
}
func main() {
log.Printf("runtime %s/%s(请确认动态库与此匹配)", runtime.GOOS, runtime.GOARCH)
role := os.Getenv("ROLE")
if role == "" && len(os.Args) > 1 {
role = os.Args[1]
}
switch role {
case "A", "a":
runA()
case "B", "b":
runB()
default:
fmt.Println("用法: ROLE=A go run . 然后 ROLE=B go run .")
os.Exit(2)
}
}
怎么跑
- webrpc.cn 准备两枚 Token,下载 当前系统 + CPU 架构 对应的库。
- 填 Token / 密码。需要
CGO_ENABLED=1。
bash
ROLE=A CGO_ENABLED=1 go run .
ROLE=B CGO_ENABLED=1 go run .
期望:A 收到 ping-crossplat;B 打印验证通过。
进一步:A 放 Linux NAS,B 放 Windows 笔记本------两边仍是同一套 API,只是各自链接本平台的库。
Android:按官网 Java 示例接 so,顺序仍是登录 → 回调 → OpenSession → SendData。iOS 同理,取控制台 iOS 包。

跑通之后
- 业务协议两端用同一套字符串或 JSON,与系统无关。
- 大文件走
SendFile,路径规则与返回值见专文。 - 浏览器会议不要硬套本 SDK;原生多端个人云、设备 RPC,用这一套即可。
控制台下载各平台 SDK、查看语言示例:webrpc.cn。跨平台要的不是五份互不相通的协议,而是 五端同一条调用链,按系统换库。