├── rpc_demo ├── go.mod ├── .idea │ ├── vcs.xml │ ├── modules.xml │ ├── rpc_demo.iml │ └── workspace.xml ├── dataserial │ └── data.go ├── transport │ ├── transport_test.go │ └── transport.go ├── main.go ├── client │ └── client.go └── server │ └── server.go ├── images ├── dy.png └── dy1.png └── README.md /rpc_demo/go.mod: -------------------------------------------------------------------------------- 1 | module rpc_demo 2 | 3 | go 1.16 4 | -------------------------------------------------------------------------------- /images/dy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jetLuoxianjie/rpc_node/HEAD/images/dy.png -------------------------------------------------------------------------------- /images/dy1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jetLuoxianjie/rpc_node/HEAD/images/dy1.png -------------------------------------------------------------------------------- /rpc_demo/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /rpc_demo/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /rpc_demo/.idea/rpc_demo.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /rpc_demo/dataserial/data.go: -------------------------------------------------------------------------------- 1 | package dataserial 2 | 3 | import ( 4 | "bytes" 5 | "encoding/gob" 6 | ) 7 | 8 | // RPCdata 9 | type RPCdata struct { 10 | Name string // 调用方法名称 11 | Args []interface{} // 请求 或 响应 的数据 12 | Err string // 执行的远程服务的报错信息 13 | } 14 | 15 | //编码 16 | func Encode(data RPCdata) ([]byte, error) { 17 | var buf bytes.Buffer 18 | encoder := gob.NewEncoder(&buf) 19 | if err := encoder.Encode(data); err != nil { 20 | return nil, err 21 | } 22 | return buf.Bytes(), nil 23 | } 24 | 25 | // 解码 26 | func Decode(b []byte) (RPCdata, error) { 27 | buf := bytes.NewBuffer(b) 28 | decoder := gob.NewDecoder(buf) 29 | var data RPCdata 30 | if err := decoder.Decode(&data); err != nil { 31 | return RPCdata{}, err 32 | } 33 | return data, nil 34 | } -------------------------------------------------------------------------------- /rpc_demo/transport/transport_test.go: -------------------------------------------------------------------------------- 1 | package transport 2 | 3 | import ( 4 | "net" 5 | "sync" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestTransport_ReadWrite(t *testing.T) { 11 | addr := "localhost:3212" 12 | dataToSend := "Hello World" 13 | wg := sync.WaitGroup{} 14 | wg.Add(2) 15 | go func() { 16 | 17 | defer wg.Done() 18 | l, err := net.Listen("tcp", addr) 19 | if err != nil { 20 | t.Fatal(err) 21 | } 22 | defer l.Close() 23 | conn, _ := l.Accept() 24 | time.Sleep(100 * time.Millisecond) 25 | s := NewTransport(conn) 26 | err = s.Send([]byte(dataToSend)) 27 | t.Log("listen and accept") 28 | if err != nil { 29 | t.Fatal(err) 30 | } 31 | }() 32 | 33 | go func() { 34 | defer wg.Done() 35 | conn, err := net.Dial("tcp", addr) 36 | if err != nil { 37 | t.Fatal(err) 38 | } 39 | tp := NewTransport(conn) 40 | data, err := tp.Read() 41 | if err != nil { 42 | t.Fatal(err) 43 | } 44 | if string(data) != dataToSend { 45 | t.FailNow() 46 | } 47 | }() 48 | wg.Wait() 49 | } 50 | -------------------------------------------------------------------------------- /rpc_demo/transport/transport.go: -------------------------------------------------------------------------------- 1 | package transport 2 | 3 | import ( 4 | "encoding/binary" 5 | "io" 6 | "net" 7 | ) 8 | 9 | //前4个传输数据长度 10 | const headerLen = 4 11 | 12 | // Transport 传输结构体 13 | type Transport struct { 14 | conn net.Conn 15 | } 16 | 17 | // NewTransport 创建一个传输 18 | func NewTransport(conn net.Conn) *Transport { 19 | return &Transport{conn} 20 | } 21 | 22 | // Send 发生数据 23 | func (t *Transport) Send(data []byte) error { 24 | //我们将需要4个字节,然后加上数据的len 25 | buf := make([]byte, headerLen+len(data)) 26 | binary.BigEndian.PutUint32(buf[:headerLen], uint32(len(data))) 27 | copy(buf[headerLen:], data) 28 | _, err := t.conn.Write(buf) 29 | if err != nil { 30 | return err 31 | } 32 | return nil 33 | } 34 | 35 | // Read 读数据 36 | func (t *Transport) Read() ([]byte, error) { 37 | header := make([]byte, headerLen) 38 | _, err := io.ReadFull(t.conn, header) 39 | if err != nil { 40 | return nil, err 41 | } 42 | dataLen := binary.BigEndian.Uint32(header) 43 | data := make([]byte, dataLen) 44 | _, err = io.ReadFull(t.conn, data) 45 | if err != nil { 46 | return nil, err 47 | } 48 | return data, nil 49 | } 50 | -------------------------------------------------------------------------------- /rpc_demo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/gob" 5 | "fmt" 6 | "net" 7 | "rpc_demo/client" 8 | "rpc_demo/server" 9 | "time" 10 | 11 | ) 12 | 13 | type User struct { 14 | Name string 15 | Age int 16 | } 17 | 18 | var userDB = map[int]User{ 19 | 1: User{"Ankur", 85}, 20 | 9: User{"Anand", 25}, 21 | 8: User{"Ankur Anand", 27}, 22 | } 23 | 24 | func QueryUser(id int) (User, error) { 25 | if u, ok := userDB[id]; ok { 26 | return u, nil 27 | } 28 | return User{}, fmt.Errorf("id %d not in user db", id) 29 | } 30 | 31 | func main() { 32 | // 注册编码解码的 33 | gob.Register(User{}) 34 | addr := "localhost:3212" 35 | srv := server.NewServer(addr) 36 | 37 | // 注册服务 38 | srv.Register("QueryUser", QueryUser) 39 | //运行 40 | go srv.Run() 41 | 42 | // 等待一秒免得服务器还没有启动完毕就开始链接 43 | time.Sleep(1 * time.Second) 44 | 45 | // 客户端开始 46 | conn, err := net.Dial("tcp", addr) 47 | if err != nil { 48 | panic(err) 49 | } 50 | cli := client.NewClient(conn) 51 | 52 | var Query func(int) (User, error) 53 | //调用 54 | cli.CallRPC("QueryUser", &Query) 55 | 56 | u, err := Query(1) 57 | if err != nil { 58 | panic(err) 59 | } 60 | fmt.Println(u) 61 | 62 | u2, err := Query(8) 63 | if err != nil { 64 | panic(err) 65 | } 66 | fmt.Println(u2) 67 | } 68 | -------------------------------------------------------------------------------- /rpc_demo/client/client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "reflect" 7 | "rpc_demo/dataserial" 8 | "rpc_demo/transport" 9 | ) 10 | 11 | // Client rpc客户端结构体 12 | type Client struct { 13 | conn net.Conn 14 | } 15 | 16 | // NewClient 创建一个新的客户端 17 | func NewClient(conn net.Conn) *Client { 18 | return &Client{conn} 19 | } 20 | 21 | // CallRPC 调用rpc 22 | func (c *Client) CallRPC(rpcName string, fPtr interface{}) { 23 | container := reflect.ValueOf(fPtr).Elem() 24 | f := func(req []reflect.Value) []reflect.Value { 25 | //创建一个传输的 26 | cReqTransport := transport.NewTransport(c.conn) 27 | errorHandler := func(err error) []reflect.Value { 28 | outArgs := make([]reflect.Value, container.Type().NumOut()) 29 | for i := 0; i < len(outArgs)-1; i++ { 30 | outArgs[i] = reflect.Zero(container.Type().Out(i)) 31 | } 32 | outArgs[len(outArgs)-1] = reflect.ValueOf(&err).Elem() 33 | return outArgs 34 | } 35 | 36 | // 填充输入参数 37 | inArgs := make([]interface{}, 0, len(req)) 38 | for _, arg := range req { 39 | inArgs = append(inArgs, arg.Interface()) 40 | } 41 | 42 | // 请求rpc接口 43 | reqRPC := dataserial.RPCdata{Name: rpcName, Args: inArgs} 44 | b, err := dataserial.Encode(reqRPC) 45 | if err != nil { 46 | panic(err) 47 | } 48 | 49 | //发送请求 50 | err = cReqTransport.Send(b) 51 | if err != nil { 52 | return errorHandler(err) 53 | } 54 | // 等待 接受 远程服务的响应 55 | rsp, err := cReqTransport.Read() 56 | if err != nil { 57 | return errorHandler(err) 58 | } 59 | rspDecode, _ := dataserial.Decode(rsp) 60 | if rspDecode.Err != "" { 61 | return errorHandler(errors.New(rspDecode.Err)) 62 | } 63 | 64 | if len(rspDecode.Args) == 0 { 65 | rspDecode.Args = make([]interface{}, container.Type().NumOut()) 66 | } 67 | // 解包 68 | numOut := container.Type().NumOut() 69 | outArgs := make([]reflect.Value, numOut) 70 | for i := 0; i < numOut; i++ { 71 | if i != numOut-1 { 72 | if rspDecode.Args[i] == nil { // 符合为nil 就设置为 Zero 73 | outArgs[i] = reflect.Zero(container.Type().Out(i)) 74 | } else { 75 | outArgs[i] = reflect.ValueOf(rspDecode.Args[i]) 76 | } 77 | } else { // 最后一个是错误处理 78 | outArgs[i] = reflect.Zero(container.Type().Out(i)) 79 | } 80 | } 81 | 82 | return outArgs 83 | } 84 | container.Set(reflect.MakeFunc(container.Type(), f)) 85 | } 86 | -------------------------------------------------------------------------------- /rpc_demo/server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "log" 7 | "net" 8 | "reflect" 9 | "rpc_demo/dataserial" 10 | "rpc_demo/transport" 11 | ) 12 | 13 | // RPCServer rpc服务 14 | type RPCServer struct { 15 | addr string 16 | funcs map[string]reflect.Value //使用反射 value ,这个类型可以调用函数 17 | } 18 | 19 | // NewServer 创建一个rpc服务 20 | func NewServer(addr string) *RPCServer { 21 | return &RPCServer{addr: addr, funcs: make(map[string]reflect.Value)} 22 | } 23 | 24 | // Register 注册rpc服务 25 | func (s *RPCServer) Register(fnName string, fFunc interface{}) { 26 | if _, ok := s.funcs[fnName]; ok { 27 | return 28 | } 29 | s.funcs[fnName] = reflect.ValueOf(fFunc) 30 | } 31 | 32 | // Execute 如何函数存在就执行函数 33 | func (s *RPCServer) Execute(req dataserial.RPCdata) dataserial.RPCdata { 34 | //根据函数名字调用具体的方法 35 | f, ok := s.funcs[req.Name] 36 | if !ok { 37 | //没有该方法 38 | e := fmt.Sprintf("func %s not Registered", req.Name) 39 | log.Println(e) 40 | return dataserial.RPCdata{Name: req.Name, Args: nil, Err: e} 41 | } 42 | 43 | log.Printf("func %s is called\n", req.Name) 44 | //填充参数 45 | inArgs := make([]reflect.Value, len(req.Args)) 46 | for i := range req.Args { 47 | inArgs[i] = reflect.ValueOf(req.Args[i]) 48 | } 49 | 50 | // 调用方法 51 | out := f.Call(inArgs) 52 | //打包响应数据 53 | resArgs := make([]interface{}, len(out)-1) 54 | for i := 0; i < len(out)-1; i++ { 55 | resArgs[i] = out[i].Interface() 56 | } 57 | // 看打包的数据有没有报错的 58 | var er string 59 | if _, ok := out[len(out)-1].Interface().(error); ok { 60 | er = out[len(out)-1].Interface().(error).Error() 61 | } 62 | return dataserial.RPCdata{Name: req.Name, Args: resArgs, Err: er} 63 | } 64 | 65 | // rpc服务运行 66 | func (s *RPCServer) Run() { 67 | l, err := net.Listen("tcp", s.addr) 68 | if err != nil { 69 | log.Printf("listen on %s err: %v\n", s.addr, err) 70 | return 71 | } 72 | for { 73 | conn, err := l.Accept() 74 | if err != nil { 75 | log.Printf("accept err: %v\n", err) 76 | continue 77 | } 78 | go func() { 79 | connTransport := transport.NewTransport(conn) 80 | for { 81 | // 读数据 82 | req, err := connTransport.Read() 83 | if err != nil { 84 | if err != io.EOF { 85 | log.Printf("read err: %v\n", err) 86 | return 87 | } 88 | } 89 | 90 | //解码数据 91 | decReq, err := dataserial.Decode(req) 92 | if err != nil { 93 | log.Printf("Error Decoding the Payload err: %v\n", err) 94 | return 95 | } 96 | //运行 97 | resP := s.Execute(decReq) 98 | // 编码运行后的数据 99 | b, err := dataserial.Encode(resP) 100 | if err != nil { 101 | log.Printf("Error Encoding the Payload for response err: %v\n", err) 102 | return 103 | } 104 | //发送响应返回 105 | err = connTransport.Send(b) 106 | if err != nil { 107 | log.Printf("transport write err: %v\n", err) 108 | } 109 | } 110 | }() 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /rpc_demo/.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 16 | 17 | 18 | 19 | 21 | 22 | 24 | 25 | 26 | 27 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 78 | 79 | true 80 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: rpc解析和手写rpc框架(芜湖起飞🚀) 3 | renderNumberedHeading: true 4 | grammar_cjkRuby: true 5 | --- 6 | 7 | 8 | 9 | 10 | - [[1] 为什么要用rpc?](#为什么要用rpc) 11 | - [[2] 什么是rpc?](#什么是rpc) 12 | - [[3] 实现远程调用的一些思路?](#实现远程调用的一些思路) 13 | - [[4] GRPC框架](#GRPC框架) 14 | - - [[4.1] GRPC框架解析](#GRPC框架解析) 15 | - - [[4.2] GRPC框架优势](#GRPC框架优势) 16 | - - [[4.3] GRPC框架缺点](#GRPC框架缺点) 17 | - - [[4.4] 一个简单gRPC服务的golang实现](#一个简单gRPC服务的golang实现) 18 | - [[5] 从0到1实现简易RPC框架](#从0到1实现简易RPC框架) 19 | 20 | 21 | 22 | ## 为什么要用rpc 23 | 24 | 当项目越来越大时,**集中式**的服务(如:单一**game**服)缺点越来越明显。当然应对的方案是对项目的**拆分**(分为**logic,team,battle**等),分对多个独立的服务来开发,后随着网路请求越来越大,多个独立的服务也需要**独立部署**到**不同的物理机**上。 25 | 26 | >**这里就出现了一个问题** 27 | > --------- 28 | > 如果team服要实现一个功能,数据只存在logic服,而logic服也有对应接口。team服怎么办? 29 | > 30 | > #### 方法1:把logic的数据传输到team服,team服再写对应的函数。 31 | > **优点**: 32 | > 1.传输一次后,后续调用数据可以在进程内调用,快捷方便。 33 | > **缺点**: 34 | > 1.**logic**和**team**服都维护一样数据,容易出现数据不一致。 35 | > **总结**:**该方法适用于数据不易变的情况。** 36 | > 37 | > #### 方法2:team服发送一个消息到logic服,然后等待logic服返回消息在往下运行。 38 | > **优点**: 39 | > 1.调用方便。(框架实现的好类似调用本地函数一样) 40 | > **缺点**: 41 | > 1.每次都要调用都是一次请求,相对于本地调用会有额外网络开销会影响。 42 | > **总结**:**适用于数据易变的情况。** 43 | > #### 关于方法2,有一个RPC协议刚刚就可以解决这个问题。 44 | 45 | ## 什么是rpc 46 | 47 | RPC 是 1984 年代由 Andrew D. Birrell & Bruce Jay Nelson 提出的,所以并不是最近的概念,在二位大神的论文 "Implementing Remote Procedure Calls"。 (实现远程过程调用) 48 | 49 | > **论文连接** : https://pages.cs.wisc.edu/~sschang/OS-Qual/distOS/RPC.htm 50 | 51 | **论文简单概括**:就是让分布式系统更加简单,让开发人员把精力放到业务上,并且提供高效安全的通信。 52 | 53 | 54 | **再来看看比较常见的解释,了解下 RPC 是啥:** 55 | 56 | > RPC(Remote Procedure Call) 远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。也就是说两台服务器 A,B,一个应用部署在 A 服务器上,想要调用 B 服务器上应用提供的方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。 57 | 58 | **大白话理解这段话就是说**:RPC 让你调用其他服务器的函数和调用本地函数一样。 59 | 60 | ## 实现远程调用的一些思路 61 | 62 | 前面说了 RPC 远程过程调用就是让服务 A 像调用本地功能一样调用远端的服务 B 上的功能,不要把这个事情想的太悬乎。 63 | **想想我们本地调用时需要哪些东西:** 64 | **1.类或函数 65 | 2.类或函数的参数 66 | 3.类或函数的返回值。** 67 | 68 | ![](https://github.com/Jet-luoxianjie/rpc_node/blob/main/images/dy.png?raw=true) 69 | 70 | 远程调用肯定也不会缺少这三要素,唯一的区别在于这三要素是要被传输过去的,这其中就涉及协议编码和解码的过程。 71 | 72 | **远程调用时** 73 | 这样服务 team需要通过网络传输来告诉服务 logic,它想要获取玩家信息,传入的两个参数为 1,返回的结果放在 **playerInfo** 里面就可以。 74 | ![](https://github.com/Jet-luoxianjie/rpc_node/blob/main/images/dy1.png?raw=true) 75 | 76 | 传输的报文里面按照约定的协议格式给出了**函数名**和**参数**和**返回值**即可。 77 | 78 | 79 | > **看到网上会有人问 tcp,http,tcp的区别?(因为http也可以请求呀)** 80 | > rpc 应该是比 HTTP 更高层级的概念。完整的 RPC 实现包含有 传输协议 和 序列化协议,其中,传输协议既可以使用 HTTP,也可以使用 TCP 等,不同的选择可以适应不同的场景 81 | > rpc的框架是对 http ,tcp等协议进行的封装和优化,来实现一个**远程调用**,rpc**重点**是**远程调用**,而不是协议。 82 | 83 | ## GRPC框架 84 | 85 | ### GRPC框架解析 86 | 87 | gRPC 是一个现代化的开源 RPC 框架,一开始由 google 开发,是一款语言中立、平台中立、的 RPC 系统,与许多 RPC 系统类似,gRPC 也是基于以下理念:定义一个 **服务**,指定能够被远程调用的 **方法**(包含参数和返回类型)。在服务端实现这个接口,并运行一个gRPC 服务器来处理客户端调用,在客户端拥有一个 stub 连接服务端上的方法 88 | ![enter description here](https://img2020.cnblogs.com/blog/496803/202003/496803-20200321153703720-1047433603.png) 89 | 90 | ### GRPC框架优势 91 | **gRPC 的优势是它被越来越多人采用的关键所在,主要有以下几个方面:** 92 | 93 | > 1 .提供高效的进程间通信。使用一个基于 protocol buffers 的二进制协议而不是文本格式与客户端通信,同时在 HTTP2 上实现,拥有更好的性能。 94 | 95 | > 2. 具有简单且定义良好的服务接口。契约优先,必须首先定义服务接口,然后才能去处理细节,简单一致,可扩展。 96 | 97 | > 3. 强类型。服务契约清晰地定义了应用程序间通信所使用的类型,分布式应用程序的开发更加稳定。 98 | 99 | > 4. 支持多语言。基于 protocol buffers 的服务定义是语言中立的,可以选择任意一种语言具体实现。 100 | 101 | > 5 .支持双工流。与传统的 REST 相比,gRPC 能够同时构建传统的请求-响应风格的消息以及客户端流和服务端流。 102 | 103 | > 6 .具备内置的商业化特性。如认证、加密、弹性时间、元数据交换、压缩、负载均衡以及服务发现等。 104 | 105 | > 7. 与云原生生态进行了集成。gRPC 是 CNCF(云原生计算基金会)的一部分,大多数现代框架和技术都对 gRPC 提供了原生支持。 106 | 107 | > 8. 业界成熟。通过在谷歌进行的大量实战测试,gRPC 已经发展成熟,被许多公司采用。 108 | 109 | 110 | ### GRPC框架缺点 111 | 112 | **gRPC 也存在一定劣势,选择它用来构建应用程序时,需要注意以下三点:** 113 | 114 | > 1.gRPC 不太适合面向外部的服务。gRPC 具有契约驱动、强类型等特点,这会限制向外部暴露服务的灵活性,对客户端有诸多限制,所以更适合用在内部服务器之间通信。 115 | 116 | > 2.避免巨大的服务定义变更。如果出现巨大的服务定义变更,通常需要重新生成客户端代码和服务端代码,会让整个开发生命周期变得复杂,需要小心引入破坏性的变更。 117 | 118 | > 3.与REST等协议对比生态系统相对较小。 119 | 120 | 121 | ### 一个简单gRPC服务的golang实现 122 | **下载 protoc 编译器(如:protoc-3.20.1-win32.zip):[protobuf](https://github.com/protocolbuffers/protobuf/releases),选择合适的平台,解压后将可执行文件加入环境变量。** 123 | **go get google.golang.org/protobuf/cmd/protoc-gen-go** 124 | **go get google.golang.org/grpc/cmd/protoc-gen-go-grpc** 125 | 126 | 127 | 创建代码目录 **grpc_hero**,实现一个简单的从**队伍服**获取存在**逻辑服**中玩家英雄数据 rpc 服务,在其中新建三个文件夹 proto、server、client 分别存放服务定义文件和生成的目标代码、服务端程序实现、客户端程序实现,然后执行 go mod init grpc_hero 初始化模块。 128 | 129 | 130 | **工程目录如下** 131 | - grpc_hero 132 | - - logic 133 | - - team 134 | - - proto 135 | 136 | #### 服务器定义 137 | 开发 gRPC 应用程序时,要首先定义服务接口,然后生成服务端骨架和客户端 stub,客户端通过调用其中定义的方法来访问远程服务器上的方法,服务定义都以 protocol buffers 的形式记录,也就是 gRPC 所使用的服务定义语言 138 | **在 proto 目录下新建服务定义文件 hero.proto** 139 | 140 | ``` protobuf 141 | // 版本 142 | syntax = "proto3"; 143 | // proto文件所属包名 144 | package proto; 145 | // 声明生成的go文件所属的包,路径末尾为包名,相对路径是相对于编译生成目标代码时的工作路径 146 | option go_package = "./proto"; 147 | 148 | // 包含两个远程方法的 rpc 服务,远程方法只能有一个参数和一个返回值 (一个是请求 一个是返回) 149 | service Hero { 150 | rpc GetHero(Request) returns (Response); 151 | } 152 | 153 | // 自定义消息类型,用这种方法传递多个参数,必须使用唯一数字标识每个字段 154 | message Response { 155 | HeroInfo heroInfo = 1; 156 | } 157 | 158 | message Request { 159 | int32 playerId = 1; 160 | int32 heroId = 2; 161 | } 162 | 163 | message HeroInfo { 164 | int32 heroId = 1; 165 | int32 heroLevel = 2; 166 | string heroName = 3; 167 | } 168 | 169 | ``` 170 | 编译服务定义文件生成目标源代码,这一步之后在 **proto** 文件下生成了以下两个文件: 171 | **hero.pb.go**,包含用于填充、序列化、检索请求和响应消息类型的所有 protocol buffers 代码 172 | **hero_grpc.pb.go**,包含服务端需要继承实现和客户端进行调用的接口定义 173 | 174 | > go_out 和 go-grpc-out 目录是相对于服务定义文件中 go_package 指定的目录 175 | > protoc proto/hero.proto --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative 176 | 177 | 178 | #### 服务端(logic被调用端)实现 179 | 编译生成服务端骨架的时候,已经得到了建立 gRPC 连接、相关消息类型和接口的基础代码,接下来就是实现得到的接口,在 logic文件夹中新建服务端主程序 logic.go: 180 | 181 | ``` go 182 | package main 183 | 184 | import ( 185 | "context" 186 | "errors" 187 | "fmt" 188 | "log" 189 | "net" 190 | 191 | pb "grpc_hero/proto" 192 | 193 | "google.golang.org/grpc" 194 | ) 195 | 196 | const ( 197 | port = ":50051" 198 | ) 199 | 200 | // 对服务器的抽象,用来实现服务方法 201 | type server struct { 202 | pb.UnimplementedHeroServer 203 | } 204 | 205 | // 存放玩家的英雄数据 206 | var playerHero map[int32]map[int32]*pb.HeroInfo 207 | 208 | // GetHero 实现 GetHero 方法 209 | func (s *server) GetHero(ctx context.Context, re *pb.Request) (*pb.Response, error) { 210 | if re == nil { 211 | return nil, errors.New("request nil") 212 | } 213 | heroMap, ok := playerHero[re.GetPlayerId()] 214 | if !ok { 215 | return nil, errors.New("no heroMap") 216 | } 217 | heroInfo, ok := heroMap[re.GetHeroId()] 218 | if !ok { 219 | return nil, errors.New("no heroInfo") 220 | } 221 | return &pb.Response{HeroInfo: heroInfo}, nil 222 | } 223 | 224 | //填充测试数据 225 | func initTestData() { 226 | playerHero = make(map[int32]map[int32]*pb.HeroInfo) 227 | for i := int32(0); i < 10; i++ { 228 | playerHero[i] = make(map[int32]*pb.HeroInfo) 229 | playerHero[i][i] = &pb.HeroInfo{ 230 | HeroId: i, 231 | HeroLevel: i, 232 | HeroName: fmt.Sprintf("英雄:[%d]", i), 233 | } 234 | } 235 | 236 | } 237 | 238 | func main() { 239 | //填充测试数据 240 | initTestData() 241 | // 创建一个 tcp 监听器 242 | lis, err := net.Listen("tcp", port) 243 | if err != nil { 244 | log.Fatalf("failed to listen: %v", err) 245 | } 246 | // 创建一个 gRPC 服务器实例 247 | s := grpc.NewServer() 248 | // 将服务注册到 gRPC 服务器上 249 | pb.RegisterHeroServer(s, &server{}) 250 | // 绑定 gRPC 服务器到指定 tcp 251 | if err := s.Serve(lis); err != nil { 252 | log.Fatalf("failed to serve: %v", err) 253 | } 254 | } 255 | 256 | ``` 257 | 258 | #### 客户端(team调用端)实现 259 | 接下来创建客户端程序来与服务器对话,之前编译服务定义文件生成的目标源代码已经包含了访问细节的实现,我们只需要创建客户端实例就可以直接调用远程方法。在 team文件夹中创建客户端主程序 team.go: 260 | 261 | ``` go 262 | package main 263 | 264 | import ( 265 | "context" 266 | pb "grpc_hero/proto" 267 | "log" 268 | 269 | "google.golang.org/grpc" 270 | ) 271 | 272 | const ( 273 | // 服务端地址 274 | address = "localhost:50051" 275 | ) 276 | 277 | func main() { 278 | // 创建 gRPC 连接 279 | conn, err := grpc.Dial(address, grpc.WithInsecure()) 280 | if err != nil { 281 | log.Fatalf("did not connect: %v", err) 282 | } 283 | defer conn.Close() 284 | // 创建客户端 stub,利用它调用远程方法 285 | c := pb.NewHeroClient(conn) 286 | // 调用远程方法 287 | r, err := c.GetHero(context.Background(), &pb.Request{ 288 | PlayerId: 1, 289 | HeroId: 1, 290 | }) 291 | if err != nil { 292 | log.Fatalf("getHero err : %v", err) 293 | } 294 | log.Printf("Response [%+v]", r) 295 | } 296 | ``` 297 | #### 构建运行 298 | 分别构建运行服务端和客户端程序,go build 或者直接 go run 299 | **启动logic服务端:go run ./logic/logic.go** 300 | **启动team客户端:go run ./team/team.go** 301 | 302 | 303 | ## 从0到1实现简易RPC框架 304 | 305 | **当前简易RPC 的目的是以最少的代码,实现 RPC 框架中最为重要的部分,帮助大家理解 RPC 框架在设计时需要考虑什么。代码简洁是第一位的,功能是第二位的。** 306 | 307 | * 308 | 309 | [见RPC_Demo (200多行代码)](https://github.com/Jet-luoxianjie/rpc_node/tree/main/rpc_demo) 310 | 311 | * 312 | 313 | 314 | 315 | 316 | --------------------------------------------------------------------------------