ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Go + webrpc 实战:人在外面,远程查家里 NAS 磁盘和目录

2026/8/21 20:40:38 拓冰建站 浏览量
Go + webrpc 实战:人在外面,远程查家里 NAS 磁盘和目录 独立开发者笔记非官方教程。Token 与 SDK 见 webrpc 控制台。人不在家里时常会碰到两个很具体的问题家里的 NAS / 小主机还活着吗磁盘还剩多少空间某个目录里有没有把备份跑完用 frp 可以硬把 Web 管理页或 SSH 透出来但你要先维护一台公网机器还要操心端口暴露面。之前我写过一篇更完整的双端 RPC 示例Ping/GetDeviceInfo这次换一个更贴近日常的痛点用 JSON 发请求远程查磁盘用量和列目录。下面示例依然是无公网 IP 场景A 端挂在家里当 AgentB 端在外面发起查询。这次和之前示例有什么不同点之前这篇载荷格式RPC|Ping|…管道分隔标准 JSON业务连通性测试disk.usage / dir.list代码结构双文件各写一套抽出公共protocol.go思路文内合并展示协议长这样// 请求{id:1,method:disk.usage}{id:2,method:dir.list,params:{path:/Photos}}// 响应{id:1,ok:true,data:{path:/,total_gb:931.5,free_gb:412.3}}{id:2,ok:true,data:{entries:[2024,2025,backup]}}准备控制台拿两个 TokenYOUR_TOKEN_A家里 Agent、YOUR_TOKEN_B外面 Client下载对应平台 SDK头文件和动态库放工程根目录Go 1.20能跑 CGO目录nas-remote-query/ ├── libwebrpc-Mac.h ├── libwebrpc-Mac.dylib ├── agent/main.go # 家里 NAS └── client/main.go # 外面查询公共回调读帧 JSON 类型两端都要连127.0.0.1:回调端口读 SDK 推来的数据。帧格式与官网一致sessionId(4) type(1) payload。数据流 type2。typeRequeststruct{IDintjson:idMethodstringjson:methodParamsmap[string]stringjson:params,omitempty}typeResponsestruct{IDintjson:idOKbooljson:okDatainterface{}json:data,omitemptyErrorstringjson:error,omitempty}funcreadDataFrame(conn net.Conn)(sessionIDuint32,payload[]byte,errerror){sidBuf:make([]byte,4)if_,errio.ReadFull(conn,sidBuf);err!nil{return}sessionIDbinary.BigEndian.Uint32(sidBuf)typ:make([]byte,1)if_,errio.ReadFull(conn,typ);err!nil{return}iftyp[0]!2{returnsessionID,nil,fmt.Errorf(unsupported frame type %d,typ[0])}lenBuf:make([]byte,4)if_,errio.ReadFull(conn,lenBuf);err!nil{return}n:binary.BigEndian.Uint32(lenBuf)payloadmake([]byte,n)_,errio.ReadFull(conn,payload)return}Agent家里 NAS 处理 disk.usage 和 dir.listagent/main.go核心逻辑funchandleRequest(webrpc C.GoUintptr,sessionIDuint32,raw[]byte){varreq Requestiferr:json.Unmarshal(raw,req);err!nil{reply(webrpc,sessionID,Response{OK:false,Error:bad json})return}log.Printf([Agent] 收到请求: %s,string(raw))varresp Response resp.IDreq.IDswitchreq.Method{casedisk.usage:resp.OKtrueresp.DatadiskUsage(/)// 见下方实现casedir.list:path:req.Params[path]ifpath{path.}entries,err:listDir(path)iferr!nil{resp.OKfalseresp.Errorerr.Error()}else{resp.OKtrueresp.Datamap[string]interface{}{entries:entries}}default:resp.OKfalseresp.Errorunknown method}reply(webrpc,sessionID,resp)}funcreply(webrpc C.GoUintptr,sessionIDuint32,resp Response){body,_:json.Marshal(resp)log.Printf([Agent] 回包: %s,body)gofunc(){cmsg:C.CString(string(body))deferC.free(unsafe.Pointer(cmsg))ret:C.WebrpcClient_SendData(webrpc,C.uint(sessionID),cmsg,C.int(len(body)),3000)log.Printf([Agent] SendData ret%d,int(ret))}()}funcdiskUsage(pathstring)map[string]interface{}{// Linux/macOS 可用 syscall.Statfs此处示意返回结构returnmap[string]interface{}{path:path,total_gb:931.5,free_gb:412.3,}}funclistDir(pathstring)([]string,error){ents,err:os.ReadDir(path)iferr!nil{returnnil,err}names:make([]string,0,len(ents))for_,e:rangeents{namesappend(names,e.Name())}returnnames,nil}Agent 的main与官网示例相同WebrpcClient_New→ 等LoginStatus→GetReceivePort→ goroutine 里net.Dial回调端口循环readDataFrame后调handleRequest。Client外面发 JSON 查询funcmain(){// … New / Login / GetReceivePort 同上 …gostartCallbackReader(int(tcpPort))// 打印收到的 JSON 响应peer:C.CString(YOUR_TOKEN_A)deferC.free(unsafe.Pointer(peer))sid:C.WebrpcClient_OpenSession(webrpc,peer,permission)ifsid0{log.Fatal(OpenSession 失败)}log.Printf(会话建立 sessionId%d,uint32(sid))sendJSON(webrpc,sid,Request{ID:1,Method:disk.usage})time.Sleep(time.Second)sendJSON(webrpc,sid,Request{ID:2,Method:dir.list,Params:map[string]string{path:/Photos},})select{}}funcsendJSON(webrpc C.GoUintptr,sid C.uint,req Request){body,_:json.Marshal(req)cmsg:C.CString(string(body))deferC.free(unsafe.Pointer(cmsg))ret:C.WebrpcClient_SendData(webrpc,sid,cmsg,C.int(len(body)),5000)log.Printf([Client] 发送 %s ret%d,body,int(ret))}回调里收到响应后直接log.Printf打印 JSON若要做得像同步 RPC可以用map[int]chan Response按id等待这里为短示例保持异步打印。CGO 头与官网一致/* #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 */importC跑起来家里Agent 先启动cdnas-remote-query/agentexportDYLD_LIBRARY_PATH..# Linux 用 LD_LIBRARY_PATHgo run.外面Clientcdnas-remote-query/client go run.Agent 侧大致会看到Client 侧会在回调里打印带free_gb和entries的 JSON。两端跨网家宽 手机热点测一次比同机两个进程更有说服力。三个容易踩的坑回调里同步 SendData 会卡死读循环——回包务必放 goroutine。OpenSession返回 0——Agent 没在线、Token 填反、或网络策略太严先同机验证逻辑。JSON 和业务权限——示例为了短没做鉴权真实 NAS 产品要在 Agent 里校验 caller、限制可列目录的路径避免dir.list被扫全盘。还能怎么扩展disk.usage换成真实syscall.StatfsLinux或unix.StatfsmacOS加file.pullAgent 读小文件SendData分块回传大文件用SendFile请求里加token或 HMAC做一层轻量鉴权把 Client 封成 CLInasctl disk/nasctl ls /Photos小结webrpc 不会替你实现「列目录」的业务逻辑但它把无公网 IP 下的会话和收发收进了 SDK。这篇示例解决的是很具体的一刀在外面用 JSON 问家里 NAS 还剩多少空间、目录里有什么——比管道字符串更接近真实产品也比再开一条 frp 隧道更贴应用层。完整 API 与多语言示例见 https://www.webrpc.cn/ 开发文档。Personal 档约$5/年/2 Token够 Agent Client 各用一个。