protobuf 介绍,Python 和 Go 编写 rpc 服务(gRPC) (5)

然后是客户端:

package main import ( "fmt" "net/rpc" ) func main() { // 1. 建立连接 client, err := rpc.Dial("tcp", "localhost:9999") if err != nil { fmt.Println("客户端创建失败,失败原因:", err) return } // 调用 var reply string // 通过 client.Call 来实现,第一个参数就是服务端在注册结构体的时候使用的名字(不是结构体的名字),调用的时候在后面加上 .方法名即可 // 所以我们在服务端注册的时候,起名字还是要规范一些,尽管我们这里出现了空格也没有什么问题 // 这里我们调用了里面的 Hello 方法,第二个参数和第三个参数就是服务端的方法接收的参数 if err := client.Call("Hello Service.Hello", "古明地觉", &reply); err != nil { fmt.Println("调用失败,失败原因:", err) return } // reply 就是返回值(对于当前逻辑而言),我们传递指针进去,执行完毕之后发现被修改了 fmt.Println(reply) // hello 古明地觉 }

整体还是很简单的,但是我们观察一下服务端的最后两个代码:

// 程序一直阻塞在这里,等待连接 conn, _ := listener.Accept() // 到来之后进行处理,处理完毕之后程序结束 rpc.ServeConn(conn)

所以我们这里服务端是一次性的,如果想要一直监听的话应该这么做:

for { conn, _ := listener.Accept() go rpc.ServeConn(conn) }

无限循环,每一个连接单独开启一个 goroutine 去处理。

替换 rpc 的序列化协议为 json

我们演示了基于 Go 的 rpc 实现,但是它存在一些问题,就是客户端在调用的时候必须要知道服务端在注册结构体时指定的名字,所以它相对于 Python 的开发就显得不是太好用了。

另外,Go 和 rpc 可不可以实现跨语言调用呢,它的序列化协议是什么,能否替换成常见的 json 呢。

首先 Go 采用的序列化协议是其独有的 Gob 协议,因此 Python 是无法调用的,但它是可以被替换成 json 的,下面我们就来实现一下。

package main import ( "fmt" "net" "net/rpc" "net/rpc/jsonrpc" ) type HelloService struct { } func (server *HelloService) Hello (request string, reply *string) error { *reply = fmt.Sprintf("hello %s", request) return nil } func main() { listener, _ := net.Listen("tcp", ":9999") _ = rpc.RegisterName("Hello Service", &HelloService{}) for { conn, _ := listener.Accept() // 其它部分不变,这里改成如下 go rpc.ServeCodec(jsonrpc.NewServerCodec(conn)) } }

然后是客户端,也需要做一些略微的改动:

package main import ( "fmt" "net" "net/rpc" "net/rpc/jsonrpc" ) func main() { // 这里需要使用 net.Dial 进行连接,之前使用 rpc 是因为需要采用 Gob 协议,但是现在我们不需要了 // 而 net.Dial 的返回结果是连接,就不再是客户端了,当然我们后面要根据这个连接来创建客户端 conn, err := net.Dial("tcp", "localhost:9999") if err != nil { fmt.Println("连接建立失败,失败原因:", err) return } var reply string // 服务端是 NewServerCodec,客户端是 NewClientCodec 来进行数据的编解码 client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn)) // 然后调用的方式不变,但是序列化之后的数据变了,因为不是同一种协议 if err := client.Call("Hello Service.Hello", "古明地觉", &reply); err != nil { fmt.Println("调用失败,失败原因:", err) return } fmt.Println(reply) // hello 古明地觉 }

所以此时也是可以实现的,但是问题来了, 既然 Go 的服务端在接收数据和返回数据采用的都是 json,那么就意味着任何支持 json 的语言都是可以调用的。只要我们知道数据的转换格式即可,我们客户端在调用的时候使用的是 client.Call,但是在传输的时候会进行编码,如果能知道数据在编码之后的格式,那么任何支持 json 的语言都可以调用。

而编码格式如下:

{"method": "Hello Service.Hello", "params": ["古明地觉"], "id": 0}

method 是方法;params 是参数,即便只有一个元素也要写成数组,而 id 就是 Call id 了。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zwfsgj.html