Go 标准库 RPC 实战:用 net/rpc 实现 HTTP、TCP 与 JSON-RPC 远程调用

发布时间:2026/10/4 7:17:24
Go 标准库 RPC 实战:用 net/rpc 实现 HTTP、TCP 与 JSON-RPC 远程调用 文档教程【免费下载链接】build-web-application-with-golangA golang ebook intro how to build a web with golang项目地址https://gitcode.com/gh_mirrors/bu/build-web-application-with-golang点击查看免费下载本篇技术指南以《build-web-application-with-golang》开源电子书第 8.4 节es/08.4.md为骨架系统讲解 Go 标准库net/rpc的三级远程过程调用实现HTTP RPC、TCP RPC 与 JSON RPC。读完本文你将掌握 RPC 的调用模型与十步工作流程、可被远程调用的 Go 函数必须满足的签名规则并能独立编写、编译、运行完整的 RPC 服务端与客户端程序为开发分布式 Web 应用打下基础。RPC让远程调用像本地函数一样简单在前面的小节中我们学习了如何基于 Socket 和 HTTP 编写网络应用。Socket 与 HTTP 都采用信息交换模式客户端发送一条消息到服务端服务端返回相应消息作为响应双方事先约定好交互信息的格式以便彼此解析通信内容详见本书第 8.1、8.2 节对 Socket 编程的讨论。然而很多独立应用并没有采用这种模式而是希望通过类似常规函数调用的方式完成功能。RPCRemote Procedure Call远程过程调用正是为实现函数调用模式的网络化而诞生客户端像调用本地函数一样发起调用将函数名与参数打包后通过网络传递到服务端服务端解包参数、执行处理过程再把执行结果反馈给客户端。RPC 是一种通过网络从远程计算机程序请求服务、而无需了解底层网络细节的协议。它假定存在 TCP 或 UDP 之类的传输协议为通信程序之间携带数据。在 OSI 网络通信模型中RPC 跨越了传输层和应用层在面向对象场景下RPC 又被称为远程调用或远程方法调用。借助 RPC开发包含网络分布式多程序在内的应用程序会变得更加容易。在第 8 章的整体架构中REST 与 SOAP 是 Web 服务的两种主流风格见 es/08.0.md 章节总览REST 基于 HTTP、语义清晰SOAP 规范庞大复杂本书不展开讨论。而 Go 对 RPC 提供了良好且易用的内置支持这正是本节8.4的主题。RPC 工作原理一次远程调用的十步流程运行时一次客户端对服务端的 RPC 调用其内部操作大致包含以下十步调用客户端句柄client stub执行传送参数调用本地系统内核发送网络消息消息传送到远程主机服务器句柄server stub得到消息并取得参数执行远程过程执行的过程将结果返回服务器句柄服务器句柄返回结果调用远程系统内核消息传回本地主机客户端句柄由内核接收消息客户端接收句柄返回的数据。从流程图可以直观看到客户端与服务端各有一个句柄stub负责参数打包、解包与结果回传真正的业务逻辑只在第 5 步执行远程过程中发生——这也是 RPC 能保持像调用本地函数体验的根本原因。Go 对 RPC 的内置支持与函数签名规则Go 标准库已经提供了对 RPC 的支持且支持三个级别的 RPCTCP、HTTP、JSON RPC。需要特别说明的是Go 的 RPC 包与传统的 RPC 系统不同它只支持 Go 开发的服务器与客户端之间的交互因为在内部它们统一采用Gob 编码encoding/gob。使用 Go RPC 时函数只有满足以下条件才能被远程访问否则调用会被忽略函数必须是导出的首字母大写必须有两个导出类型的参数第一个参数是接收客户端的参数第二个参数是返回给客户端的参数且必须是指针类型函数还要有一个返回值error。正确的 RPC 函数格式如下func (t *T) MethodName(argType T1, replyType *T2) error其中 T、T1、T2 类型必须能被encoding/gob包编解码。这也是上文所说只能用于 Go 客户端与服务端的约束来源——参数与返回值在网络上以 Gob 二进制格式传输只有 Go 双方才能彼此解析。任何 RPC 都需要通过网络传递数据Go RPC 可以利用 HTTP 和 TCP 两种传输层协议利用 HTTP 的好处是可以直接复用net/http包里的一些函数。下面分别给出三种实现的完整代码。HTTP RPC 实战服务端实现HTTP 服务端代码如下package main import ( errors fmt net/http net/rpc ) type Args struct { A, B int } type Quotient struct { Quo, Rem int } type Arith int func (t *Arith) Multiply(args *Args, reply *int) error { *reply args.A * args.B return nil } func (t *Arith) Divide(args *Args, quo *Quotient) error { if args.B 0 { return errors.New(divide by zero) } quo.Quo args.A / args.B quo.Rem args.A % args.B return nil } func main() { arith : new(Arith) rpc.Register(arith) rpc.HandleHTTP() err : http.ListenAndServe(:1234, nil) if err ! nil { fmt.Println(err.Error()) } }代码的关键点在于定义服务类型Arith是服务类型Multiply与Divide是注册到该服务上的方法二者都严格遵循两个导出类型参数第二个为指针 返回 error的 RPC 签名规则rpc.Register(arith)将Arith服务注册到 RPC 默认服务端default server服务名默认为类型名Arith这也是客户端调用时使用Arith.Multiply作为方法名的原因rpc.HandleHTTP()把 RPC 服务注册到 HTTP 协议上——它默认在 HTTP 路径/_goRPC_上提供 RPC 处理并在/debug/rpc提供调试页面http.ListenAndServe(:1234, nil)启动监听1234端口的 HTTP 服务nil表示使用默认的多路复用器DefaultServeMux此时 RPC 的 HTTP 处理已由HandleHTTP注册进去。注册完成之后我们就可以利用 HTTP 的方式来传递数据了。客户端实现客户端代码如下package main import ( fmt log net/rpc os ) type Args struct { A, B int } type Quotient struct { Quo, Rem int } func main() { if len(os.Args) ! 2 { fmt.Println(Usage: , os.Args[0], server) os.Exit(1) } serverAddress : os.Args[1] client, err : rpc.DialHTTP(tcp, serverAddress:1234) if err ! nil { log.Fatal(dialing:, err) } // 同步调用 args : Args{17, 8} var reply int err client.Call(Arith.Multiply, args, reply) if err ! nil { log.Fatal(arith error:, err) } fmt.Printf(Arith: %d*%d%d\n, args.A, args.B, reply) var quot Quotient err client.Call(Arith.Divide, args, quot) if err ! nil { log.Fatal(arith error:, err) } fmt.Printf(Arith: %d/%d%d remainder %d\n, args.A, args.B, quot.Quo, quot.Rem) }客户端的使用要点rpc.DialHTTP(tcp, serverAddress:1234)通过 TCP 连接服务端 HTTP 端口并建立基于 HTTP 的 RPC 客户端。注意服务端地址由命令行参数传入如localhost代码中拼上固定端口:1234client.Call(Arith.Multiply, args, reply)这是最重要的调用函数它有三个参数——第一个是要调用的函数名服务类型.方法名第二个是要传递的参数第三个是返回值注意必须是指针类型参数与返回值就是我们定义的 struct 类型在服务端它们作为调用函数的参数类型在客户端作为client.Call第 2、3 个参数的类型。编译运行与输出把上面的服务端和客户端代码分别编译先启动服务端再启动客户端即可看到如下输出$ ./http_c localhost Arith: 17*8136 Arith: 17/82 remainder 1从上面的代码例子可以发现使用 Go 的 RPC 实现相当简单、方便服务端只需注册 HandleHTTP ListenAndServe三步客户端只需DialHTTP Call两步。TCP RPC 实战上面我们实现了基于 HTTP 协议的 RPC接下来实现基于 TCP 协议的 RPC。服务端实现代码如下package main import ( errors fmt net net/rpc os ) type Args struct { A, B int } type Quotient struct { Quo, Rem int } type Arith int func (t *Arith) Multiply(args *Args, reply *int) error { *reply args.A * args.B return nil } func (t *Arith) Divide(args *Args, quo *Quotient) error { if args.B 0 { return errors.New(divide by zero) } quo.Quo args.A / args.B quo.Rem args.A % args.B return nil } func main() { arith : new(Arith) rpc.Register(arith) tcpAddr, err : net.ResolveTCPAddr(tcp, :1234) checkError(err) listener, err : net.ListenTCP(tcp, tcpAddr) checkError(err) for { conn, err : listener.Accept() if err ! nil { continue } rpc.ServeConn(conn) } } func checkError(err error) { if err ! nil { fmt.Println(Fatal error , err.Error()) os.Exit(1) } }与 HTTP 服务端相比TCP 版本的核心差异在于自己控制连接生命周期不再依赖http.ListenAndServe而是先用net.ResolveTCPAddr解析 TCP 地址:1234再用net.ListenTCP创建监听器rpc.ServeConn(conn)当有客户端连接上来后把这个连接交给 RPC 处理。ServeConn会在该连接上为每个请求运行默认服务端的ServeRequest处理逻辑。需要留意的是上面的写法是一个阻塞型的单用户程序for循环中Accept到连接后立即同步调用rpc.ServeConn(conn)在处理完当前连接之前无法接受新连接。如果想要实现多并发可以使用 goroutine 来并发处理每个连接——前面 Socket 小节8.1已经介绍过如何用 goroutine 处理并发这里只需把rpc.ServeConn(conn)放入go关键字即可例如for { conn, err : listener.Accept() if err ! nil { continue } go rpc.ServeConn(conn) // 并发处理每连接一个 goroutine }TCP 版本的客户端代码如下package main import ( fmt log net/rpc os ) type Args struct { A, B int } type Quotient struct { Quo, Rem int } func main() { if len(os.Args) ! 2 { fmt.Println(Usage: , os.Args[0], server:port) os.Exit(1) } service : os.Args[1] client, err : rpc.Dial(tcp, service) if err ! nil { log.Fatal(dialing:, err) } // 同步调用 args : Args{17, 8} var reply int err client.Call(Arith.Multiply, args, reply) if err ! nil { log.Fatal(arith error:, err) } fmt.Printf(Arith: %d*%d%d\n, args.A, args.B, reply) var quot Quotient err client.Call(Arith.Divide, args, quot) if err ! nil { log.Fatal(arith error:, err) } fmt.Printf(Arith: %d/%d%d remainder %d\n, args.A, args.B, quot.Quo, quot.Rem) }客户端代码与 HTTP 版本几乎一模一样唯一的区别是HTTP 客户端使用rpc.DialHTTP(tcp, ...)而 TCP 客户端使用rpc.Dial(tcp, service)且命令行参数直接传入完整的server:port形式如localhost:1234。其余的处理逻辑完全相同——同样的Args/Quotient结构、同样的同步Call调用、同样的输出格式。JSON RPC 实战JSON RPC 与前面两种 RPC 概念完全一致唯一区别在于数据编码采用 JSON 而不是 Gob。它基于 TCP 协议实现目前还不支持 HTTP 方式。下面演示如何使用 Go 提供的net/rpc/jsonrpc标准包。服务端实现代码package main import ( errors fmt net net/rpc net/rpc/jsonrpc os ) type Args struct { A, B int } type Quotient struct { Quo, Rem int } type Arith int func (t *Arith) Multiply(args *Args, reply *int) error { *reply args.A * args.B return nil } func (t *Arith) Divide(args *Args, quo *Quotient) error { if args.B 0 { return errors.New(divide by zero) } quo.Quo args.A / args.B quo.Rem args.A % args.B return nil } func main() { arith : new(Arith) rpc.Register(arith) tcpAddr, err : net.ResolveTCPAddr(tcp, :1234) checkError(err) listener, err : net.ListenTCP(tcp, tcpAddr) checkError(err) for { conn, err : listener.Accept() if err ! nil { continue } jsonrpc.ServeConn(conn) } } func checkError(err error) { if err ! nil { fmt.Println(Fatal error , err.Error()) os.Exit(1) } }与服务端代码与 TCP 版本几乎完全一致唯一的差异是导入net/rpc/jsonrpc包并把循环中的rpc.ServeConn(conn)换成jsonrpc.ServeConn(conn)。同样的该循环也是阻塞式单连接处理模式如需并发可用 goroutine 包装jsonrpc.ServeConn。服务注册仍走rpc.Register(arith)编码差异被封装在jsonrpc内部。客户端实现代码package main import ( fmt log net/rpc/jsonrpc os ) type Args struct { A, B int } type Quotient struct { Quo, Rem int } func main() { if len(os.Args) ! 2 { fmt.Println(Usage: , os.Args[0], server:port) log.Fatal(1) } service : os.Args[1] client, err : jsonrpc.Dial(tcp, service) if err ! nil { log.Fatal(dialing:, err) } // 同步调用 args : Args{17, 8} var reply int err client.Call(Arith.Multiply, args, reply) if err ! nil { log.Fatal(arith error:, err) } fmt.Printf(Arith: %d*%d%d\n, args.A, args.B, reply) var quot Quotient err client.Call(Arith.Divide, args, quot) if err ! nil { log.Fatal(arith error:, err) } fmt.Printf(Arith: %d/%d%d remainder %d\n, args.A, args.B, quot.Quo, quot.Rem) }JSON RPC 客户端的差异同样只有一处使用jsonrpc.Dial(tcp, service)建立客户端连接其余同步调用与输出逻辑完全一致。小结三种 RPC 的对比与选型将本节实现的三种 RPC 对比如下维度HTTP RPCTCP RPCJSON RPC传输层HTTP基于 TCPTCPTCP编码格式GobGobJSON服务端注册rpc.Registerrpc.HandleHTTPhttp.ListenAndServerpc.Registernet.ListenTCPrpc.ServeConnrpc.Registernet.ListenTCPjsonrpc.ServeConn客户端连接rpc.DialHTTP(tcp, addr)rpc.Dial(tcp, addr)jsonrpc.Dial(tcp, addr)连接管理由 HTTP 服务自动管理需自己Accept并ServeConn默认阻塞、可配合 goroutine同 TCP RPC适用场景可复用net/http能力、便于与其他 HTTP 组件集成更底层的长连接/自管连接场景需要跨语言或便于调试的 JSON 数据格式场景Go 已经提供了对 RPC 的良好支持通过上面 HTTP、TCP、JSON RPC 的实现可以很方便地开发很多分布式 Web 应用。需要说明的是目前 Go 标准库尚未提供对SOAP RPC的支持不过已有第三方的开源实现可以弥补这一缺口。需要注意的是本书各语言版本的code目录例如 es/code主要收录了第 15 章的示例程序本节8.4的 RPC 服务端与客户端示例以文档内完整代码形式呈现可直接复制编译运行与本仓库中其他章节的示例程序同样遵循标准 Go 程序 go run/go build的验证方式。延伸阅读本节对应中文版原文zh/08.4.md第 8 章总览REST / SOAP / RPC 的定位es/08.0.md上一节 REST 实现es/08.3.md下一节小结es/08.5.md全书目录es/preface.md赞分享文档教程【免费下载链接】build-web-application-with-golangA golang ebook intro how to build a web with golang项目地址https://gitcode.com/gh_mirrors/bu/build-web-application-with-golang点击查看免费下载相关推荐用 Go 构建远程过程调用服务net/rpc 的 HTTP、TCP 与 JSON-RPC 实战指南用 Go 构建远程过程调用服务net/rpc 的 HTTP、TCP 与 JSON RPC 实战指南 本文是《Build Web Application wit文档教程Go-Kit JSON-RPC 实战指南用 EndpointCodec 构建标准 JSON-RPC 2.0 服务Go Kit JSON RPC 实战指南用 EndpointCodec 构建标准 JSON RPC 2.0 服务 JSON RPC 是一种轻量级远程过程调用微服务后端RPC框架深度解析ManyMCM1/M2 Mac原生运行Minecraft的终极解决方案深度解析ManyMCM1/M2 Mac原生运行Minecraft的终极解决方案 你是否还在为M1/M2芯片Mac运行Minecraft时的卡顿、发热和兼容性问桌面应用游戏开发创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询