golang RPC入门
1. RPC简介
RPC是远程系统调用的缩写,通俗地讲就是调用远处的一个函数,可能是一个文件内的不同函数,也可能是一个机器上另一个进程的函数,也可能是远处机器上的函数。
RPC是分布式系统中不同节点之间的通信方式,Go的标准库也实现了一个简单的RPC。
2. RPC简单使用
首先构造一个HelloService类型,其中的Hello方法用于实现打印功能,Login实现简单的用户验证
其中RPC方法必须满足golang的RPC规则:
- 方法只能有两个可序列化的参数,其中第二个参数是指针类型
- 返回一个error
- 必须是公开的方法,首字母大写
type HelloService struct { conn net.Conn isLogin bool } // Hello: func (p *HelloService) Hello(request string, reply *string) error { if !p.isLogin { return fmt.Errorf("please login") } *reply = "hello:" + request + ",from" + p.conn.RemoteAddr().String() return nil } // Login: 提供用户登录验证 func (p *HelloService) Login(request string, reply *string) error { if request != "user:password" { return fmt.Errorf("auth failed") } log.Println("login ok") p.isLogin = true return nil }
然后我们可以将HelloService类型的对象注册为一个RPC服务:
func main() { // 开启监听 listener, err := net.Listen("tcp", ":1234") if err != nil { log.Fatal("ListenTCP error:", err) } for { conn, err := listener.Accept() if err != nil { log.Fatal("Accept error:", err) } go func() { defer conn.Close() p := rpc.NewServer() // RegisterName调用会将对象类型中所有满足RPC规则的对象方法注册为RPC函数 // 所有注册的方法会放在HelloService服务空间之下 p.Register(&HelloService{conn: conn}) // ServeConn函数在conn这个TCP连接上为对方提供RPC服务 p.ServeConn(conn) }() } }
下面是客户端请求HelloService服务的代码:
func main() { // 拨号RPC服务 client, err := rpc.Dial("tcp", "localhost:1234") if err != nil { log.Fatal("Dail error:", err) } // 通过Call()调用RPC的具体方法 var reply string err = client.Call("HelloService.Login", "user:password", &reply) if err != nil { log.Fatal(err) } err = client.Call("HelloService.Hello", "client", &reply) if err != nil { log.Fatal(err) } fmt.Println(reply) }
我们在终端开启server和client,看看会发生什么:
go run server.go go run client.go ################## hello:client,from[::1]:56769
3. 跨语言的RPC
标准库的RPC默认采用go语言特有的Gob编码,因此从其他语言调用Go语言实现的RPC服务比较困难。
go语言的RPC框架有两个比较有特色的设计:一个是RPC数据打包时可以通过插件实现自定义的编码和解码;另一个是RPC建立在抽象的io.ReadWriteCloser接口之上,我们可以将RPC架设在不同的通信协议之上。
我们利用net/rpc/jsonrpc实现一个跨语言的RPC。
func main() { rpc.RegisterName("HelloService", new(HelloService)) listener, err := net.Listen("tcp", ":1234") if err != nil { log.Fatal(err) } for { conn, err := listener.Accept() if err != nil { log.Fatal(err) } // 使用rpc.ServeCodec代替rpc.ServeConn函数 go rpc.ServeCodec(jsonrpc.NewServerCodec(conn)) } }
func main() { conn, err := net.Dial("tcp", "localhost:1234") if err != nil { log.Fatal(err) } client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn)) var reply string err = client.Call("HelloService.Hello", "hello", &reply) if err != nil { log.Fatal(err) } fmt.Println(reply) }
无论采用什么样的语言,只要遵循一致的json映射结构,以同样的流程就可以实现和go语言编写的RPC服务进行通信
4. HTTP上的RPC服务
我们尝试在HTTP协议上提供jsonrpc服务
新的RPC服务其实就是一个类似于REST规范的接口,接收请求并采用相应的处理流程:
type HelloService struct{} func (p *HelloService) Hello(request string, reply *string) error { *reply = "hello: " + request return nil } func main() { rpc.RegisterName("HelloService", new(HelloService)) http.HandleFunc("/jsonrpc", func(w http.ResponseWriter, r *http.Request) { var conn io.ReadWriteCloser = struct { io.Writer io.ReadCloser }{ ReadCloser: r.Body, Writer: w, } rpc.ServeRequest(jsonrpc.NewServerCodec(conn)) }) http.ListenAndServe(":1234", nil) }
RPC服务架设在/jsonrpc路径,在处理函数中基于http.ResponseWriter和*http.Request类型的参数构造一个io.ReadWriteCloser类型的conn通道,然后基于conn构建针对服务器端的json编码解码器,最后通过rpc.ServeRequest( )函数为每次请求处理一次RPC方法调用。
让我们启动RPC服务,并打开postman,测试我们的RPC服务:
可以清楚的看到我们POST请求的body和response信息,都是json格式的,其实这两种格式基本都是固定写法。
因为在内部都是使用类似的结构体来封装的:
type clientRequest struct { Method string `json:"method"` Params []interface{} `json:"params"` Id uint64 `json:"id"` }
type serverResponse struct { Id *json.RawMessage `json:"id"` Result interface{} `json:"result"` Error interface{} `json:"error"` }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· spring官宣接入deepseek,真的太香了~