开发环境:
系统: ubuntu20.04
go版本: 1.19
编辑器: goland
grpc:远程过程调用,使用场景很多,也是比较流行的技术之一。使用go开发grpc服务,除了必须的go语言开发环境之外,还需要安装grpc相关命令。
grpc环境配置
protoc安装
| 12
 3
 4
 
 | $ sudo apt install -y protobuf-compiler$ protoc --version
 
 libprotoc 3.6.1
 
 | 
如果是其他系统电脑,安装protoc可参考文档:Protocol Buffer Compiler Installation
protocol编译插件安装
| 12
 
 | $ go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28$ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2
 
 | 
安装完成后可以在bin目录下看到相关指令:
| 12
 3
 
 | $ ls $GOPATH/bin
 protoc-gen-go  protoc-gen-go-grpc
 
 | 
项目开发
项目源码地址
项目目录结构
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | ├── demo_3│   ├── client
 │   │   └── main.go
 │   ├── go.mod
 │   ├── go.sum
 │   ├── helloworld
 │   │   ├── helloworld_grpc.pb.go
 │   │   ├── helloworld.pb.go
 │   │   └── helloworld.proto
 │   ├── README.md
 │   └── server
 │       └── main.go
 
 | 
项目创建
| 12
 
 | $ mkdir demo_3 && cd demo_3$ go mod init
 
 | 
安装grpc依赖
| 1
 | $ go get -u google.golang.org/grpc
 | 
编写proto文件
流式rpc使用stream关键字定义
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
 | # helloworld/helloworld.proto
 syntax = "proto3";
 
 option go_package = "/helloworld";
 package helloworld;
 
 service Hello {
 rpc SayHello (stream HelloRequest) returns (HelloReply) {}
 }
 
 message HelloRequest {
 string name = 1;
 }
 
 message HelloReply {
 string message = 1;
 }
 
 | 
生成go代码
| 12
 3
 
 | $ protoc --go_out=. --go_opt=paths=source_relative \--go-grpc_out=. --go-grpc_opt=paths=source_relative \
 helloworld/helloworld.proto
 
 | 
命令执行成功之后会在helloworld目录下生成两个文件: helloworld_grpc.pb.go和helloworld.pb.go,注意: 不要手动编辑这两个文件。
编写服务端代码
flag用法可参考官方文档: https://pkg.go.dev/flag
| 12
 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
 43
 44
 45
 46
 47
 48
 49
 50
 51
 
 | 
 package main
 
 import (
 "flag"
 "fmt"
 "github.com/silenceboychen/gostudy/demo_3/helloworld"
 "google.golang.org/grpc"
 "google.golang.org/grpc/reflection"
 "io"
 "log"
 "net"
 )
 
 var (
 port = flag.Int("port", 8080, "The server port")
 )
 
 type server struct {
 helloworld.UnimplementedHelloServer
 }
 
 func (s *server) SayHello(stream helloworld.Hello_SayHelloServer) error {
 for {
 res, err := stream.Recv()
 if err == io.EOF {
 return stream.SendAndClose(&helloworld.HelloReply{Message: "over"})
 }
 
 if err != nil {
 return err
 }
 log.Println(res.Name)
 }
 }
 
 func main() {
 flag.Parse()
 lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
 if err != nil {
 log.Fatalf("failed to listen: %v", err)
 }
 s := grpc.NewServer()
 reflection.Register(s)
 helloworld.RegisterHelloServer(s, &server{})
 log.Printf("server listening at %v", lis.Addr())
 if err := s.Serve(lis); err != nil {
 log.Fatalf("failed to serve: %v", err)
 }
 }
 
 | 
编写客户端代码
| 12
 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
 43
 44
 45
 46
 47
 48
 49
 50
 
 | 
 package main
 
 import (
 "context"
 "flag"
 "github.com/silenceboychen/gostudy/demo_3/helloworld"
 "google.golang.org/grpc"
 "google.golang.org/grpc/credentials/insecure"
 "log"
 "strconv"
 "time"
 )
 
 var (
 addr = flag.String("addr", "localhost:8080", "the address to connect to")
 name = flag.String("name", "world", "Name to greet")
 )
 
 func main() {
 flag.Parse()
 conn, err := grpc.Dial(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
 if err != nil {
 log.Fatalf("did not connect: %v", err)
 }
 defer conn.Close()
 c := helloworld.NewHelloClient(conn)
 
 ctx, cancel := context.WithTimeout(context.Background(), time.Second)
 defer cancel()
 
 stream, err := c.SayHello(ctx)
 if err != nil {
 log.Fatalf("Upload list err: %v", err)
 }
 for n := 0; n < 5; n++ {
 
 err := stream.Send(&helloworld.HelloRequest{Name: "stream client rpc " + strconv.Itoa(n)})
 if err != nil {
 log.Fatalf("stream request err: %v", err)
 }
 }
 
 res, err := stream.CloseAndRecv()
 if err != nil {
 log.Fatalf("SayHello get response err: %v", err)
 }
 log.Println(res)
 }
 
 | 
项目运行
开启两个终端,分别运行服务端代码和客户端代码,服务端代码要先运行。
服务端
| 12
 3
 4
 5
 6
 7
 8
 
 | $ go run server/main.go
 2023/06/16 20:24:15 server listening at [::]:8080
 2023/06/16 20:24:22 stream client rpc 0
 2023/06/16 20:24:22 stream client rpc 1
 2023/06/16 20:24:22 stream client rpc 2
 2023/06/16 20:24:22 stream client rpc 3
 2023/06/16 20:24:22 stream client rpc 4
 
 | 
客户端
| 12
 3
 
 | $ go run client/main.go
 2023/06/16 20:24:22 message:"over"
 
 | 
下一篇将向大家介绍双向流式rpc。