
独立开发者笔记非 webrpc 官方文章。Token 请用自己的占位替换。人不在电脑旁边时有时只想用手机看一眼家里机器上的某张图。发微信会压缩传网盘又绕自己搭 frp 还得弄公网机器。后来我用 webrpc 试了一条更直接的路电脑用 Go 守着图片手机用 Java App 点一下「拉取」经 P2P 会话把文件拉过来显示。webrpc本身是面向无公网 IP 的跨平台 P2P 通信 SDK——Token 标识设备登录后建加密会话再用SendData/SendFile收发。官网https://www.webrpc.cn/注意方向是手机预览电脑上的图不是手机往电脑传图。电脑侧示例文件手机 App 界面预览成功后为什么不用网盘 / 邮箱 / frp做法痛点发微信/邮箱压缩、有痕、不适合私有文件传到网盘再下绕一圈云隐私与延迟都差frp 开 HTTP 静态目录要公网 VPS还要管暴露面webrpc手机和电脑都可在 NAT 后优先 P2PAPI 接近 RPC一句话要的是「跨网预览自家电脑文件」不是「先拥有公网入口」。架构电脑推文件手机预览┌─────────────────┐ webrpc P2P ┌──────────────────┐ │ 电脑 Go Agent │ ◄── OpenSession / 拉取 ──► │ 安卓 Java App │ │ Token PC │ ── SendFile(image.png) ──► │ Token PHONE │ │ 持有 image.png │ │ ImageView 显示 │ └─────────────────┘ └──────────────────┘交互约定故意写短便于抄手机登录开回调线程等待文件帧手机OpenSession(PC)发送文本PULL|image.png电脑回调收到请求对本sessionId执行SendFile(./image.png)手机回调type1文件流落盘 →BitmapFactory→ImageView准备https://www.webrpc.cn/ 注册准备两个 TokenYOUR_TOKEN_PC/YOUR_TOKEN_PHONE下载macOS/Linux SDK电脑与Android SDK手机电脑放好image.png可用本文同目录样例Android StudioJNA 或官网 Android 绑定方式加载libwebrpc下文按与桌面 Java 相同的 JNA 心智写核心真机请换成 Android.so与jniLibs目录示意pc-photo-preview/ ├── image.png ├── pc/main.go # 电脑发送端 └── android/ # 仅核心类非整包工程 ├── WebrpcBridge.java └── PreviewActivity.java一、电脑端 Go收到 PULL 就 SendFilepackagemain/* #cgo CFLAGS: -I. #cgo darwin LDFLAGS: -L. -lwebrpc-Mac #cgo linux,amd64 LDFLAGS: -L. -lwebrpc-Linux #cgo linux,arm64 LDFLAGS: -L. -lwebrpc-Linux-arm64 #if defined(__APPLE__) #include libwebrpc-Mac.h #elif defined(__linux__) defined(__aarch64__) #include libwebrpc-Linux-arm64.h #elif defined(__linux__) #include libwebrpc-Linux.h #endif #include stdlib.h */importCimport(encoding/binaryfmtiolognetstringstimeunsafe)funcmain(){token:C.CString(YOUR_TOKEN_PC)pass:C.CString(YOUR_PASSWORD_PC)perm:C.CString()deferC.free(unsafe.Pointer(token))deferC.free(unsafe.Pointer(pass))deferC.free(unsafe.Pointer(perm))cli:C.WebrpcClient_New(token,pass,perm)ifcli0{log.Fatal(New failed)}forC.WebrpcClient_LoginStatus(cli)0{time.Sleep(2*time.Second)}port:int(C.WebrpcClient_GetReceivePort(cli))log.Printf([PC] 登录成功回调端口%d等待手机 PULL…,port)goserveCallback(cli,port)select{}}funcserveCallback(cli C.GoUintptr,portint){conn,err:net.Dial(tcp,fmt.Sprintf(127.0.0.1:%d,port))iferr!nil{log.Fatal(err)}deferconn.Close()for{sidBuf:make([]byte,4)if_,err:io.ReadFull(conn,sidBuf);err!nil{return}sid:binary.BigEndian.Uint32(sidBuf)typ:make([]byte,1)if_,err:io.ReadFull(conn,typ);err!nil{return}iftyp[0]!2{// 只处理数据请求文件帧按需跳过continue}lenBuf:make([]byte,4)io.ReadFull(conn,lenBuf)n:binary.BigEndian.Uint32(lenBuf)payload:make([]byte,n)io.ReadFull(conn,payload)req:string(payload)log.Printf([PC] 收到请求 session%d: %s,sid,req)ifstrings.HasPrefix(req,PULL|){name:strings.TrimPrefix(req,PULL|)ifname{nameimage.png}gosendFile(cli,sid,name)}}}funcsendFile(cli C.GoUintptr,siduint32,pathstring){cpath:C.CString(path)deferC.free(unsafe.Pointer(cpath))start:time.Now()ret:C.WebrpcClient_SendFile(cli,C.uint(sid),cpath)log.Printf([PC] SendFile %s ret%d elapsed%v,path,int(ret),time.Since(start))}跑起来大致是这样# 把 image.png 与动态库放在同级后cdpcgo run.二、安卓端 Java核心只要两块下面是可嵌入的核心类不是完整 Android Studio 工程。你需要自行把 Android 版libwebrpc-*.so放进jniLibs用 JNA 或 JNI 暴露与官网一致的 C API下列接口名与 开发文档 · Java 对齐AndroidManifest按需声明网络权限1回调里收文件帧并回调 UI文件帧格式官网sessionId:u32 BE | type:u81 | nameLen:u32 | name | dataLen:u32 | fileBytes// PreviewCallback.java —— 读本机回调 TCP遇到文件就交给 ActivityfinalclassPreviewCallbackimplementsRunnable{interfaceListener{voidonImageBytes(StringfileName,byte[]data);voidonText(Stringtext);}privatefinallonghandle;privatefinalintport;privatefinalListenerlistener;privatefinalWebrpcLiblib;// JNA 接口同官网 Java 示例PreviewCallback(WebrpcLiblib,longhandle,intport,Listenerlistener){this.liblib;this.handlehandle;this.portport;this.listenerlistener;}Overridepublicvoidrun(){try(SocketsocketnewSocket(127.0.0.1,port);DataInputStreaminnewDataInputStream(socket.getInputStream())){while(true){intsessionIdin.readInt();inttypein.readUnsignedByte();if(type2){intnin.readInt();byte[]datain.readNBytes(n);listener.onText(newString(data,StandardCharsets.UTF_8));}elseif(type1){intnameLenin.readInt();StringnamenewString(in.readNBytes(nameLen),StandardCharsets.UTF_8);intdataLenin.readInt();byte[]filein.readNBytes(dataLen);listener.onImageBytes(name,file);}}}catch(IOExceptione){Log.e(webrpc,callback ended,e);}}}2Activity登录 → 拉取 → ImageView// PreviewActivity.java关键路径publicclassPreviewActivityextendsAppCompatActivity{privateWebrpcLiblib;privatelonghandle;privateImageViewimageView;privateTextViewstatusView;OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_preview);imageViewfindViewById(R.id.imagePreview);statusViewfindViewById(R.id.status);findViewById(R.id.btnPull).setOnClickListener(v-pullFromPc());newThread(this::bootWebrpc).start();}privatevoidbootWebrpc(){libNative.load(webrpc,WebrpcLib.class);// 按你的 .so 名调整handlelib.WebrpcClient_New(YOUR_TOKEN_PHONE,YOUR_PASSWORD_PHONE,);while(lib.WebrpcClient_LoginStatus(handle)0){try{Thread.sleep(2000);}catch(InterruptedExceptionignored){}}intportlib.WebrpcClient_GetReceivePort(handle);runOnUiThread(()-statusView.setText(已登录回调端口 port));newThread(newPreviewCallback(lib,handle,port,newPreviewCallback.Listener(){OverridepublicvoidonImageBytes(StringfileName,byte[]data){BitmapbmpBitmapFactory.decodeByteArray(data,0,data.length);runOnUiThread(()-{imageView.setImageBitmap(bmp);statusView.setText(已预览fileName (data.length bytes));});}OverridepublicvoidonText(Stringtext){runOnUiThread(()-statusView.setText(text));}}),webrpc-cb).start();}privatevoidpullFromPc(){newThread(()-{intsidlib.WebrpcClient_OpenSession(handle,YOUR_TOKEN_PC,);if(sid0){runOnUiThread(()-statusView.setText(OpenSession 失败电脑端是否在线));return;}StringreqPULL|image.png;intretlib.WebrpcClient_SendData(handle,sid,req,req.length(),5000);runOnUiThread(()-statusView.setText(已请求拉取SendData retret));}).start();}OverrideprotectedvoidonDestroy(){if(handle!0)lib.WebrpcClient_Free(handle);super.onDestroy();}}布局里一个ImageView 按钮「从电脑拉取 image.png」即可。跑通后手机界面就是文首那张 App 截图标题「电脑图片预览」、中间显示image.png、底部拉取按钮。三、怎么联调先开电脑go run .确认登录成功再开 App确认手机登录成功手机点「从电脑拉取 image.png」电脑日志出现PULL|image.png与SendFile ok手机ImageView显示与image.png相同的画面两端可以一个在家宽、一个在 4G这才是无公网 IP 场景的意义。四、什么场景适合这么做比较适合私有相册预览、现场看工控截图、家庭电脑/NAS 上的单张图片快速查看。不太适合浏览器里的实时视频会议更该看 WebRTC只是临时把一个网页目录暴露出去frp 往往更快。没有公网 IP 时链路可以收成电脑 Go 登录并等待PULL|文件名收到后SendFile手机 Java 登录、OpenSession到电脑 Token、发拉取请求在回调文件帧里解码显示。双方都不必做端口映射。五、几个坑安卓侧多留意必须用 Android 对应 ABI 的 soarmeabi-v7a / arm64-v8a不要拿桌面 dylib 硬塞回调在子线程更新ImageView必须runOnUiThread大图内存可先写缓存文件再BitmapFactory.Options.inSampleSize抽样OpenSession 返回 0电脑未登录、Token 填反、或网络握手失败——先同 Wi‑Fi 验证回包/发文件不要堵死回调读循环——SendFile放在电脑侧收到请求后的新 goroutine上文已如此小结这篇演示的是一条很常见却常被「网盘/frp」绕远的需求手机预览电脑本地图片。webrpc 提供的是跨 NAT 的会话与SendFileGo 负责守文件Java 负责请求与ImageView。完整 SDK、Token 与多语言文档见官网https://www.webrpc.cn/。仅用于合法业务。握手无法保证任意网络 100% 成功产品层请做超时与重试。