rpc

12/3/2022

# RPC

# 什么是RPC

Remote Procedure Call Protocol —— 远程过程调用协议

IPC: 进程间通信

RPC:远程进通信 —— 应用层协议(http协议同层)。底层使用 TCP 实现。

回顾:

OSI 7 层模型架构:物、数、网、传、会、表、应

TCP/IP 4 层架构:链路层、网络层、传输层、应用层

  • 理解RPC:

    • ==像调用本地函数一样,去调用远程函数。==
      • 通过rpc协议,传递:函数名、函数参数。达到在本地,调用远端函数,得返回值到本地的目标。
  • 为什么微服务使用 RPC:

    1. 每个服务都被封装成 进程。彼此”独立“。

    2. 进程和进程之间,可以使用不同的语言实现。

# socket回顾

-远程网络通信-

回顾:Go语言 一般性 网络socket通信

server端:

​ net.Listen() —— listener 创建监听器

​ listener.Accpet() —— conn 启动监听,建立连接

​ conn.read()

​ conn.write()

​ defer conn.Close() / listener.Close()

client端:

​ net.Dial() —— conn

​ conn.Write()

​ conn.Read()

​ defer conn.Close()

# rpc使用步骤

---- 服务端:

  1. 注册 rpc 服务对象。给对象绑定方法( 1. 定义类, 2. 绑定类方法 )

    rpc.RegisterName("服务名",回调对象)
    
    1
  2. 创建监听器

    listener, err := net.Listen()
    
    1
  3. 建立连接

    conn, err := listener.Accept()
    
    1
  4. 将连接 绑定 rpc 服务。

    rpc.ServeConn(conn)
    
    1

---- 客户端:

  1. 用 rpc 连接服务器。

    conn, err := rpc.Dial()
    
    1
  2. 调用远程函数。

    conn.Call("服务名.方法名", 传入参数, 传出参数)
    
    1

# 代码

Server

package main

import (
   "fmt"
   "net"
   "net/rpc"
   "strings"
)

type World struct {
}

func (w World) HelloWorld(name string, resp *string) error {
   *resp = name + strings.ToUpper(name)
   return nil
}
func main() {
   err := rpc.RegisterName("world", new(World))
   if err != nil {
      fmt.Println("Register world", err)
      return
   }

   listen, err := net.Listen("tcp", "127.0.0.1:8088")
   if err != nil {
      fmt.Println("net.Listen", err)
   }
   defer listen.Close()

   fmt.Println("开始监听...")
   conn, err := listen.Accept()
   if err != nil {
      fmt.Println("accept error - >", err)
   }
   defer conn.Close()

   fmt.Println("连接建立成功...")
   rpc.ServeConn(conn)

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

client

package main

import (
   "fmt"
   "net/rpc"
)

func main() {
  // gob序列化
   client, err := rpc.Dial("tcp", "127.0.0.1:8088")
   if err != nil {
      fmt.Println("rpc.Dial", err)
   }
   defer client.Close()
   var resp = new(string)
   err = client.Call("world.HelloWorld", "feng", &resp)
   if err == nil {
      fmt.Println("resp ===> ", *resp)
   }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# json版

rpc替换为jsonrpc即可

echo -e '{"method":"world.HelloWorld","params":["李白"],"id":0}' | nc 127.0.0.1 8800
1

image-20221129195941071

如果,绑定方法返回值的 error 不为空? 无论传出参数是否有值,服务端都不会返回数据。

# rpc封装

package main

import (
   "net/rpc"
   "net/rpc/jsonrpc"
)

type IHelloWorld interface {
   HelloWorld(name string, resp *string) error
}

func registerHelloWorld(helloWorld IHelloWorld) error {
   return rpc.RegisterName("world", helloWorld)
}

type IClient struct {
   c *rpc.Client
}

func InitClient(network string, address string) (IClient, error) {
   client, err := jsonrpc.Dial(network, address)
   return IClient{client}, err
}

func (client *IClient) HelloWorld(arg string, resp *string) error {
   return client.c.Call("world.HelloWorld", arg, resp)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# Protobuf

--- Google

# 编写的注意事项

  1. message 成员编号, 可以不从1开始, 但是不能重复. -- 不能使用 19000 - 19999
  2. 可以使用 message 嵌套。
  3. 定义数组、切片 使用 repeated 关键字
  4. 可以使用枚举 enum
  5. 可以使用联合体。 oneof 关键字。成员编号,不能重复。

# 编译protobuf

回顾:C++ 编译 命令:

protoc --cpp_out=./ *.proto ---> xxx.pb.cc 和 xxx.pb.h 文件

  • go 语言中 编译命令:

protoc --go_out=./ *proto ---> xxx.pb.go 文件。

# https://github.com/protocolbuffers/protobuf-go/releases/tag/v1.28.1
export GOPATH=$HOME/sdk/go1.17.1/
export GOBIN=$GOPATH/bin
export PATH="$GOBIN:$PATH"
mv protoc-gen-go to $GOPATH/bin
# google.golang.org
mkdir google.golang.org
cd google.golang.org
git clone git@github.com:protocolbuffers/protobuf-go.git
mv protobuf-go protobuf
1
2
3
4
5
6
7
8
9
10

# 添加rpc服务

  • 语法
service 服务名 {
		rpc 函数名(参数,消息体) returns (返回值,消息)
}
1
2
3

Mac配置gRpc

brew install go #运行完可不修改环境变量

brew install automake
 
brew install libtool
 
brew install protobuf
1
2
3
4
5
6
7

# brew


brew install grpc
brew install protobuf
brew install protoc-gen-go
brew install protoc-gen-go-grpc
1
2
3
4
5

# gitclone/go get


git clone git@github.com:golang/protobuf
# 编译protoc-gen-go
go install /src/google.golang.org/protobuf
# 获取package
go get -u google.golang.org/grpc
# 失败的话
git clone https://github.com/grpc/grpc-go.git $env:GOPATH\src\google.golang.org\grpc
git clone https://github.com/golang/net.git $env:GOPATH\src\golang.org\x\net
git clone https://github.com/golang/text.git $env:GOPATH\src\golang.org\x\text
git clone https://github.com/google/go-genproto.git $env:GOPATH\src\google.golang.org\genproto
# 编译protoc-gen-go
protoc-gen-go-grpc
# 编译
protoc --go-grpc_out=./ *.proto  
protoc --go_out=./ *proto
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

image-20221130233159156

编译完成将exec文件放goroot/bin目录即可

运行protoc --go-grpc_out=./ *.protoimage-20221130233308622

# grpc-server

package main

import (
   "context"
   "fmt"
   "google.golang.org/grpc"
   "net"
   "pb"
)

type World2 struct {
   pb.UnimplementedHelloServer
}

func (this *World2) HelloWorld(c context.Context, x *pb.Teacher) (*pb.Teacher, error) {
   x.Name = "xx"
   x.Age = "1"
   return x, nil
}

func main() {
   var grpcServer = grpc.NewServer()

   pb.RegisterHelloServer(grpcServer, new(World2))

   listen, err := net.Listen("tcp", "127.0.0.1:8800")
   if err != nil {
      fmt.Println("net.Listen", err)
   }
   defer listen.Close()

   fmt.Println("开始监听...")
   //conn, err := listen.Accept()
   //if err != nil {
   // fmt.Println("accept error - >", err)
   //}
   //defer conn.Close()

   //fmt.Println("连接建立成功...")
   //rpc.ServeConn(conn)
   grpcServer.Serve(listen)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

# grpc-client

package main

import (
   "context"
   "fmt"
   "google.golang.org/grpc"
   "pb"
)

func main() {
   dial, err := grpc.Dial("127.0.0.1:8800", grpc.WithInsecure())
   if err != nil {
      fmt.Println("net.Listen", err)
   }
   defer dial.Close()

   client := pb.NewHelloClient(dial)

   teacher, err := client.HelloWorld(context.TODO(), new(pb.Teacher))
   fmt.Println(teacher.Name)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21