Go RPC
Go语言的标准库RPC实现:net/rpc
-
Socketa编程
-
序列化(json/xml/…)
RPC Server
下面是HelloService,提供了一个Hello方法
type HelloService struct { }
//Hello的逻辑就是将对方发送的消息前面添加一个He11o然后返还给对方
//由于我们是一个rpc服务,因此参数上面还是有约束:
//第一个参数是请求
//第二个参数是响应
//可以类比Http handler
func (p *HelloSepvice) Hello(request string,reply *string)error{
*reply = "hello:" + request
return nil
}
func main(){
//把我们的对象注册成一个rpc的receiver
//其中rpc.Register函数调用会将对象类型中所有满足RPC规则的对象方法注册为RPC函数,
//所有注册的方法会放在"HelloService"服务空间之下 rpc.RegisterName(HelloService",new(HelloService))
//然后我们建立一个唯一的TCP链接, 常见的socket编程的技巧
listener , err := net.Listen("tcp","1234")
if err != nil {
log.Fatal("ListenTCP error:",err)
}
// 通过rpc.ServeConnl函数在该TCP链接上为对方提供RPC服务. /没Accept-一个请求,就创建一个goroutie进行处理
for {
conn,err listener.Accept()
if err != nil {
log.Fatal("Accept error:",err)
}
//前面都是tcp的知识,到这个RPC就接管了
//因此你可以认为rPc帮我们封装消息到函数调用的这个逻辑,
//提升子王作效率,逻棉比校筒洁,可以看看他代码
gorpc.ServeConn(conn)
RPC Client
func main() {
// 首先是通过rpc.Dial拨号RPC服务,建立连接
client, err := rpc.Dial("tcp" , "localhost:1234")
if err != nil {
log.Fatal("dialing:", err)
}
//然后通过client.Call 调用具体的RPC方法
//在调用client.Call时:
// 第一个参数是用点号链接点PRC服务名字和方法名字。
// 第二个参数是 请求参数
// 第三个是请求响应,必须是一个指针,有底层的rpc服务帮你赋值
var reply string
err = client.Call("HellService.Hello" , "hello" , &reply)
if err != nil {
log.Fatal(err)
}
fmt.Println(reply)
}
## RPC的优点
- 可以像使用本地函数一样使用远程服务
- 简单
- 高效
## 谁能告诉我该怎么传参?
![请添加图片描述](https://img-blog.csdnimg.cn/9845e14837de4e699285037a520513c3.png)
**go原生的Call函数并不友好**
## 因此需要定义接口 ps:golang接口的用处大于其他语言,用于各个模块之间的结偶
````go
package service
const HelloServiceName = "HelloService"
type HelloService interface {
Hello(request string, reply *string) error//相当于一个契约spec,无论客户端还是服务端都必须满足这个条件才能正常的调用
}
约束服务端和客户端
约束服务端
//通过接口约束HelloService服务
var _ service.HelloService = (*HelloService)(nil)
封装客户端,让其满足HelloService接口约束
var _ service.HelloService = (*HelloServiceClient)(nil)
//创建一个客户端对象
type HelloServiceClient struct {
*rpc.Client
}
func DialHelloService(network, address string) (*HelloServiceClient, error) {
c,err := rpc.Dial(network, address)
if err != nil {
return nil,err
}
return &HelloServiceClient{Client : c}, nil
}
func (p *HelloServiceClient) Hello(request string, reply *string) error{
return p.Client.Call(service.HelloServiceName+".Hello",request,reply)
}
#### 基于接口约束后的客户端使用就要容易多了:
````go
func main() {
client, err := DialHelloService("tcp", "localhost:1234")
if err != nil {
log.Fatal("dialing:", err)
}
var reply string
err = client.Hello("hello", &reply)
if err != nil {
log.Fatal(err)
}
fmt.Println(reply)
}
JSON ON TCP
- PRC数据打包时可以通过插件实现自定义的编码和解码;
- PRC建立在抽象的io.ReadWriteCloser接口之上,我们可以将RPC架设在不同的通讯协议之上。
服务端:
func main() {
rpc.RegisterName("HelloService" , new(HelloService))
listenner, err := net.Listen("tcp", ":1234")
if err != nil {
log.Fatal("ListenTCP error:", err)
}
for {
conn, err := listenner.Accept()
if err != nil {
log.Fatal("Accept error:", errr)
}
//代码中最大的变化是用rpc.ServeCodec函数替代了rpc.ServeConn函数
//传入的参数是针对服务端的json编解码器
go rpc.ServeCodec(jsonrpc.NewServerCodec(conn))
//go语言的rpc是建立在网络层之上的,在网络层之上有一个编码层,可以选择不通的编解码接口,比如gob、json、xml等
客户端
func DialHelloService(network, address string) (*HelloServiceClient, error) {
//c,err := rpc.Dial(network, address)
//if err != nil {
// return nil, err
//}
//建立链接
conn, err := net.Dial("tcp", "localhost:1234")
if err != nil {
log.Fatal("net.Dial:", err)
}
//采用json编解码的客户端
c := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(coon))
return &HelloServiceClient{Client: c},nil
}
REST规范的Handler+RPC Call
服务端:
func main() {
rpc.RegisterName("HelloService", new(HelloService))
//RPC的服务架设在"/jsonrpc"路径,
//在处理函数中基于http.ResponseWriter和http.Request类型的参数构造一个io.ReadWriteCloser类型的conn通道。
//然后基于conn构建针对服务端的json编码解码器
//最后通过rpc.ServeRequest函数为每次请求处理一次RPC方法调用
http.HandleFunc("/jsonrpc", func(w http.ResponseWriter, r * http.Request)) {
conn := NewRPCReadWriteCloserFromHTTP(w,r)
rpc.ServeRequest(jsonrpc.NewServerCodec(conn))
})
http.ListenAndServe(":1234",nil)
}