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"`
}

 

posted @   aganippe  阅读(270)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· spring官宣接入deepseek,真的太香了~
点击右上角即可分享
微信分享提示