环境:
Golang:go1.18.2 linux/amd64

1. RPC简介
RPC

为了有直观的认识,下面是通过json将request和response进行编解码后获取的信息

// request
{"method":"QueryService.GetAge","params":["bar"],"id":0}

// response 
{"id":0,"result":"The age of bar is 20","error":null}
2. 实践
/net/rpc

2.1 服务端

2.1.1 首先新建项目RPC,并创建Server目录,新建main.go

[root@tudou workspace]# mkdir -p RPC/Server && cd RPC/Server && touch main.go

2.1.2 新建一个字典,保存用户信息

// 用户信息
var userinfo = map[string]int{
	"foo": 18,
	"bar": 20,
}
QueryGetAge
// 实现查询服务,结构体Query实现了GetAge方法
type Query struct {
}

func (q *Query) GetAge(req string, res *string) error {
	*res = fmt.Sprintf("The age of %s is %d", req, userinfo[req])
	return nil
}

注:

GetAgereqresGetAge

2.1.4 注册服务的方法,并开启监听,等待rpc客户端的请求

func main() {
	// 注册服务方法
	if err := rpc.RegisterName("QueryService", new(Query)); err != nil {
		log.Println(err)
	}
	// 开启监听,接受来自rpc客户端的请求
	listener, _ := net.Listen("tcp", ":1234")
	rpc.Accept(listener)
}
RegisterNameRegister
func (server *Server) register(rcvr any, name string, useName bool) error {
	s := new(service)
	s.typ = reflect.TypeOf(rcvr)
	s.rcvr = reflect.ValueOf(rcvr)
	sname := reflect.Indirect(s.rcvr).Type().Name()
	if useName {
		sname = name
	}

2.1.5 运行服务

[root@tudou Server]# go build main.go && ./main

2.2 客户端

2.2.1在RPC/Server同级目录下创建Client目录,新建main.go

[root@tudou workspace]# mkdir -p RPC/Client && cd RPC/Client && touch main.go
rpc.Dialnet.Dialgob
client, _ := rpc.Dial("tcp", ":1234")

可以查看如下源码

// Dial connects to an RPC server at the specified network address.
func Dial(network, address string) (*Client, error) {
	conn, err := net.Dial(network, address)
	if err != nil {
		return nil, err
	}
	return NewClient(conn), nil
}

func NewClient(conn io.ReadWriteCloser) *Client {
	encBuf := bufio.NewWriter(conn)
	client := &gobClientCodec{conn, gob.NewDecoder(conn), gob.NewEncoder(encBuf), encBuf}
	return NewClientWithCodec(client)
}
Call
	var res string
	err := client.Call("QueryService.GetAge", "bar", &res)
	if err != nil {
		log.Println(err)
	}
	fmt.Println(res)

2.2.4 运行客户端,得到如下结果

[root@tudou Client]# go run main.go
The age of bar is 20
3. json序列化

本来打算用gob作编解码器显示下序列化后得到的数据,奈何笔者技术不到家,得到的结果不够清晰明了,所以下面就用json作编解码器作个演示,本质是一样的。(可以参考另一篇文章【Golang | RPC】利用json编解码器实现RPC)
首先利用nc工具监听1234端口,作为服务端,获取传入的数据

[root@tudou ~]# nc -l 1234

然后运行客户端,可以发现nc工具捕获到如下数据,这就是客户端利用json编码后发送给服务端的数据

{"method":"QueryService.GetAge","params":["bar"],"id":0}

同样,nc工具可以作为客户端,向RPC服务端发送数据,得到如下返回结果

[root@tudou ~]# echo -e '{"method":"QueryService.GetAge","params":["bar"],"id":0}' | nc localhost 1234
{"id":0,"result":"The age of bar is 20","error":null}
4. 总结
  • RPC是一个C/S架构,通过指定的编解码器发送request,获取response
  • 服务的方法名必须可导出,即首字母要大写;方法的第一个参数类型必须可导出,即首字母大写或内置类型, 方法的第二个参数类型也必须可导出,同时为指针类型

完整代码:
https://github.com/WanshanTian/GolangLearning
参考 GolangLearning/RPC/gobRPC目录