Go语言下RPC的开发
一、rpc之HelloWorld
Go语言的rpc包的路径为net/rpc:
1、server.go
package main import ( "net" "net/rpc" ) type HelloWorldService struct { } func (s *HelloWorldService) HelloWorld(request string, response *string) error { *response = "hello" + request return nil } func main() { _ = rpc.RegisterName("HelloWorldService", &HelloWorldService{}) listener, err := net.Listen("tcp", ":8000") if err != nil { panic("监听端口失败!") } conn, err := listener.Accept() if err != nil { panic("建立连接失败!") } rpc.ServeConn(conn) }
- HelloWorld方法需要满足Go语言RPC规则,接收两个参数,第二个参数是指针类型,并且返回一个error类型,同时必须是公开方法
- HelloWorldService类型的对象注册到RPC服务
- rpc.register函数调用会将对象类型中满足RPC规则的对象方法注册为RPC函数,所有注册的方法会放置在HelloWorldService服务空间下
- 建立TCP链接,通过rpc.ServerConn函数在该链接上提供RPC服务
1、client.go
package main import ( "fmt" "log" "net/rpc" ) func main() { client, err := rpc.Dial("tcp", "127.0.0.1:8000") if err != nil { log.Fatal("dial", err) } var response string err = client.Call("HelloWorldService.HelloWorld", "world", &response) if err != nil { log.Fatal("caller", err) } fmt.Println(response) }
- 通过rpc.Dial进行RPC拨号服务
- 通过client.Call调用具体方法
- 方法中第一个参数是RPC服务名称和方法名称,第二个和第三个参数是方法中传入的实参
二、基于json实现RPC
上述RPC默认采用的是Go语言特有的gob编码,所以其它语言调用Go语言实现的RPC服务相对困难。比较常见的有基于json实现的RPC服务,所以如果使用json来替换gob将会使其通用性变的更强。在Go语言中可以通过net/rpc/jsonrpc来基于json实现RPC,实现跨语言调用。
1、server.go
package main import ( "net" "net/rpc" "net/rpc/jsonrpc" ) type HelloWorldService struct { } func (s *HelloWorldService) HelloWorld(request string, response *string) error { *response = "hello" + request return nil } func main() { _ = rpc.RegisterName("HelloWorldService", &HelloWorldService{}) listener, err := net.Listen("tcp", ":8000") if err != nil { panic("监听端口失败!") } // 不断的接收新的请求 for { conn, err := listener.Accept() if err != nil { panic("建立连接失败!") } go rpc.ServeCodec(jsonrpc.NewServerCodec(conn)) // 启动协程处理请求 } }
这与之前相比使用rpc.ServeCodec函数替代了rpc.ServeConn函数,传入符合json编码的参数。
2、client.go
package main import ( "fmt" "log" "net" "net/rpc" "net/rpc/jsonrpc" ) func main() { conn, err := net.Dial("tcp", "127.0.0.1:8000") if err != nil { log.Fatal("dial", err) } client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn)) var response string err = client.Call("HelloWorldService.HelloWorld", "world", &response) if err != nil { log.Fatal("caller", err) } fmt.Println(response) }
这与之前相比通过net进行拨号建立连接,然后通过NewClientWithCodec函数传入符合json编码的参数。
既然使用的是json进行编、解码,那么就具备一定的通用性,可以使用其它语言来进行调用,比如使用Python客户端进行调用,但是Go的RPC监听的是TCP连接,如果使用Python中的requests包是行不通的,它使用的是HTTP协议,会携带很多比如请求头之类的多余信息,所以使用socket编程发送请求:
3、client.py
import json import socket # 发送的数据格式必须满足这样的 """ method: 服务名称、方法名称 result:返回的结果 id:随意指定一个值,如果不指定,返回值id为None """ request = { "method": "HelloWorldService.HelloWorld", "params": ["bily"], "id": 0 } client = socket.create_connection(("127.0.0.1", 8000)) client.sendall(json.dumps(request).encode()) # 设置一次性接收的数据大小 response = client.recv(4096) response = json.loads(response.decode()) print(response) # 关闭连接 client.close()
请求结果:
{'id': 0, 'result': 'hellobily', 'error': None}
三、基于http实现RPC
1、server.go
package main import ( "io" "net/http" "net/rpc" "net/rpc/jsonrpc" ) type HelloWorldService struct { } func (s *HelloWorldService) HelloWorld(request string, response *string) error { *response = "hello" + request return nil } func main() { _ = rpc.RegisterName("HelloWorldService", new(HelloWorldService)) http.HandleFunc("/jsonrpc", func(writer http.ResponseWriter, request *http.Request) { var conn io.ReadWriteCloser = struct { io.Writer io.ReadCloser }{ ReadCloser: request.Body, Writer: writer, } _ = rpc.ServeRequest(jsonrpc.NewServerCodec(conn)) }) _ = http.ListenAndServe(":8000", nil) }
2、client.go
import requests request = { "method": "HelloWorldService.HelloWorld", "params": ["karry"], "id": 0 } res = requests.post("http://127.0.0.1:8000/jsonrpc", json=request) print(res.text)
四、封装代理
在之前的调用中,存在一些明显的问题:
- 服务端中,业务代码与RPC通信底层混在一起,需要一种结构层次,将其分离
- 客户端中,每次调用都需要知道业务端的服务名称与方法名称,将其单独封装
这样就引出服务端代理Server Stub和客户端代理Client Stub:
1、目录结构
├─client
│ client.go
│
├─client_stub
│ client_stub.go
│
├─handler
│ handler.go
│
├─server
│ server.go
│
└─server_stub
server_stub.go
2、client.go
package main import ( "fmt" "go_rpc_project/stub_rpc/client_stub" ) func main() { // 建立连接 client := client_stub.NewHelloWorldServiceClient("tcp", "127.0.0.1:8000") // 调用业务函数HelloWorld var response string _ = client.HelloWorld("harry", &response) fmt.Println(response) }
3、client_stub.go
package client_stub import ( "go_rpc_project/stub_rpc/handler" "log" "net/rpc" ) type HelloWorldServiceStub struct { *rpc.Client } func NewHelloWorldServiceClient(protcol string, address string) HelloWorldServiceStub { conn, err := rpc.Dial(protcol, address) if err != nil { log.Fatal("拨号错误", err) } return HelloWorldServiceStub{conn} } func (c *HelloWorldServiceStub) HelloWorld(request string, response *string) error { err := c.Call(handler.HelloWorldServiceName+".HelloWorld", request, response) if err != nil { log.Fatal("调用服务失败", err) } return nil }
4、handler.go
package handler const HelloWorldServiceName = "handler/HelloWorldService" type HelloWorldService struct { } func (s *HelloWorldService) HelloWorld(request string, response *string) error { *response = "hello" + request return nil }
5、server.go
package main import ( "go_rpc_project/stub_rpc/handler" "go_rpc_project/stub_rpc/server_stub" "net" "net/rpc" ) func main() { // 实例化一个server listener, _ := net.Listen("tcp", ":8000") // 注册,将业务逻辑代码注册到代理中 _ = server_stub.RegisterServicer(&handler.HelloWorldService{}) // 启动服务 for { conn, _ := listener.Accept() go rpc.ServeConn(conn) } }
6、server_stub.go
package server_stub import ( "go_rpc_project/stub_rpc/handler" "net/rpc" ) type HelloWorldServicer interface { HelloWorld(request string, response *string) error } func RegisterServicer(srv HelloWorldServicer) error { return rpc.RegisterName(handler.HelloWorldServiceName, srv) }
作者:iveBoy
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须在文章页面给出原文连接,否则保留追究法律责任的权利。