Go 语言标准库 net/rpc 框架实战:客户端与服务端开发指南
本文以 Go 语言标准库 net/rpc 为基础,详细演示如何构建一个支持 HTTP 协议的远程调用系统。通过模拟书籍阅读管理场景,文章完整梳理了从服务端方法封装、参数结构体定义、连接监听,到客户端实例化、方法调用及日志打印的全部流程,为开发者提供可参考的 RPC 基础架构实现。
核心需求与数据结构定义
为了实现远程调用,首先需要在服务器端公开一组业务方法。本例的需求是允许用户添加书籍信息、设置阅读进度并查询已读页数。为此,我们定义了以下核心结构体:
第 1 步:定义数据模型
目的: 定义用于存储和传输的书籍结构体(Book)及管理阅读状态的容器(ReadingList)。这些类型将作为 RPC 方法入参或返回值的载体。
入口: 新建 Go 文件进行类型定义。
动作: 创建 Book 结构体包含 ISBN、标题等字段;创建 ReadingList 使用切片模拟内存数据库。
代码示例:
// Book represents a book entry
type Book struct {
ISBN string
Title, Author string
Year, Pages int
}
// ReadingList keeps tracks of books and pages read
type ReadingList struct {
Books []Book
Progress []int
}
第 2 步:编写辅助与业务方法
目的: 在 ReadingList 上定义具体的增删改查逻辑,包括添加书籍、获取和设置进度。
动作: 实现内部查找索引的助手方法 bookIndex,以及对外公开的业务逻辑。注意处理错误情况(如 ISBN 为空、书籍重复或缺失)。
重要参数: 确保方法签名中第二个参数为指针类型,以便能够修改调用方传入的值。
// AddBook checks if the book is not present and adds it
func (r *ReadingList) AddBook(b Book) error {
if b.ISBN == "" {
return ErrISBN
}
if r.bookIndex(b.ISBN) != -1 {
return ErrDuplicate
}
r.Books = append(r.Books, b)
r.Progress = append(r.Progress, 0)
return nil
}
// GetProgress returns the progress of a book
func (r *ReadingList) GetProgress(isbn string) (int, error) {
if isbn == "" {
return -1, ErrISBN
}
i := r.bookIndex(isbn)
if i == -1 {
return -1, ErrMissing
}
return r.Progress[i], nil
}
// SetProgress sets the reading progress
func (r *ReadingList) SetProgress(isbn string, pages int) error {
if isbn == "" {
return ErrISBN
}
i := r.bookIndex(isbn)
if i == -1 {
return ErrMissing
}
if p := r.Books[i].Pages; pages > p {
pages = p // 限制最大页数为书籍总页数
}
r.Progress[i] = pages
return nil
}
构建 RPC 服务端
Go 的 rpc 包对公开方法有严格的要求:方法必须是导出的(首字母大写),接收者必须是引用类型,且必须包含两个参数和一个返回值(error)。由于 RPC 调用只能传递单个非参数类型的对象给函数,当需要多个输入时,需将其打包进一个结构体中。
第 3 步:包装 RPC 方法
目的: 将上一步的业务方法转换为符合 net/rpc 规范的 RPC 方法。这涉及处理返回值的填充(通过指针)以及错误的统一返回。
动作:
- 定义通用的错误返回辅助函数
setSuccess,用于根据业务结果更新输出参数的布尔值。 - 为
AddBook和GetProgress创建包装器,保持原有业务逻辑不变,但调整签名。 - 对于
SetProgress,由于有两个输入参数(ISBN 和页数),需创建中间结构体Progress来封装它们。
代码示例:
// 自定义错误定义
var (
ErrISBN = fmt.Errorf("missing ISBN")
ErrDuplicate = fmt.Errorf("duplicate book")
ErrMissing = fmt.Errorf("missing book")
)
// 辅助函数:根据错误设置 success 状态
func setSuccess(err error, b *bool) error {
*b = err == nil
return err
}
// RPC 服务的 Receiver 结构体
func (r *ReadingService) AddBook(b Book, success *bool) error {
return setSuccess(r.ReadingList.AddBook(b), success)
}
func (r *ReadingService) GetProgress(isbn string, pages *int) (err error) {
*pages, err = r.ReadingList.GetProgress(isbn)
return err
}
type Progress struct {
ISBN string
Pages int
}
func (r *ReadingService) SetProgress(p Progress, success *bool) error {
return setSuccess(r.ReadingList.SetProgress(p.ISBN, p.Pages), success)
}
第 4 步:注册服务并启动 HTTP 监听
目的: 初始化 net/http 服务器,监听特定端口以接受外部 RPC 请求。
动作:
- 使用
rpc.Register注册全局 RPC 服务实例。 - 使用
rpc.HandleHTTP()安装默认的路由处理器。 - 监听 TCP 地址,例如
:8900,并使用http.Serve启动服务。
完成状态判断: 控制台打印 "Server Started" 表示服务已成功运行。
func main() {
if err := rpc.Register(&book.ReadingService{}); err != nil {
log.Fatalln(err)
}
rpc.HandleHTTP()
l, err := net.Listen("tcp", ":8900")
if err != nil {
log.Fatalln(err)
}
log.Println("Server Started")
if err := http.Serve(l, nil); err != nil {
log.Fatal(err)
}
}
实现 RPC 客户端
为了验证服务端的功能,我们需要编写一个客户端程序。客户端也必须拥有与服务端相同的结构体定义(Book, Progress),以便序列化/反序列化数据。
第 5 步:建立连接与发起调用
目的: 连接到服务端并循环测试所有定义的远程方法。
入口: 新的 main 函数。
动作:
- 连接: 使用
rpc.DialHTTP("tcp", ":8900")建立连接,并确保最后关闭客户端。 - 通用调用封装: 定义
callClient函数,自动处理不同返回值类型(如*bool,*int)的解引用和日志打印,提高测试效率。 - 执行测试: 遍历预设的书籍列表,依次调用
AddBook、SetProgress和GetProgress。
代码示例:
// 共享的结构体定义
package main
import (
log "log"
"net/rpc"
)
type Book struct {
ISBN string
Title, Author string
Year, Pages int
}
type Progress struct {
ISBN string
Pages int
}
const hp = "H.P. Lovecraft"
var books = []Book{
{ISBN: "1540335534", Author: hp, Title: "The Call of Cthulhu", Pages: 36},
{ISBN: "1980722803", Author: hp, Title: "The Dunwich Horror ", Pages: 53},
{ISBN: "197620299X", Author: hp, Title: "The Shadow Over Innsmouth", Pages: 40},
{ISBN: "1540335536", Author: hp, Title: "The Case of Charles Dexter Ward", Pages: 176},
}
func callClient(client *rpc.Client, method string, in, out interface{}) {
var r interface{}
if err := client.Call(method, in, out); err != nil {
out = err
}
switch v := out.(type) {
case error:
r = v
case *int:
r = *v
case *bool:
r = *v
}
log.Printf("%s: [%+v] -> %+v", method, in, r)
}
func main() {
client, err := rpc.DialHTTP("tcp", ":8900")
if err != nil {
log.Fatalln(err)
}
defer client.Close()
for i, book := range books {
// 添加书籍
callClient(client, "ReadingService.AddBook", book, new(bool))
// 设置进度
callClient(client, "ReadingService.SetProgress", Progress{
ISBN: book.ISBN,
Pages: 10 + i*i,
}, new(bool))
// 获取进度
callClient(client, "ReadingService.GetProgress", book.ISBN, new(int))
}
}
以上代码完整实现了 Go 标准库 net/rpc 的基础通信流程。虽然在现代微服务架构中 gRPC 更为流行,但理解 net/rpc 有助于深入掌握 Go 语言的接口设计与反射机制。以上就是 Go 语言 RPC 框架具体使用的详细内容,更多关于 Go 远程调用的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













