golang开发一个简单的grpc

0.1、索引

https://waterflow.link/articles/1665674508275

1、什么是 grpc

在 gRPC 中,客户端应用程序可以直接调用不同机器上的服务器应用程序上的方法,就像它是本地对象一样,使您更容易创建分布式应用程序和服务。 与许多 RPC 系统一样,gRPC 基于定义服务的思想,指定可以远程调用的方法及其参数和返回类型。 在服务端,服务端实现这个接口并运行一个 gRPC 服务器来处理客户端调用。 在客户端,客户端有一个 stub(在某些语言中仅称为客户端),它提供与服务器相同的方法。
http://image-1313007945.cos.ap-nanjing.myqcloud.com/image/1665674577.png

所以 grpc 是跨语言的。

2、什么是 Protocol Buffers

Protocol Buffers 提供了一种语言中立、平台中立、可扩展的机制,用于以向前兼容和向后兼容的方式序列化结构化数据。 它类似于 JSON,只是它更小更快,并且生成本地语言绑定。

可以通过 .proto 定义数据结构,然后就可以使用 Protocol Buffers 编译器 protoc 从。 proto 定义中生成我们喜欢的语言的数据访问类。 它们为每个字段提供简单的访问器,如 name() 和 set_name(),以及将整个结构序列化/解析到原始字节/从原始字节中提取的方法。

3、grpc 服务端

1、首先我们需要下载 go 对应的 protoc 插件

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

然后把 GOPATH 放到 PATH

export PATH="$PATH:$(go env GOPATH)/bin"

接着打印下看看有没有进去

echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:$GOPATH/bin:/usr/local/go/bin

接着开新窗口,执行下面命令看下 protoc 是否安装成功

protoc --version
libprotoc 3.19.1

2、接着我们创建一个 hello.proto

内容如下(不懂结构的可自行百度)

syntax = "proto3";

package helloservice;

option go_package = ".;helloservice"; // 指定包名

message String {
  string value = 1;
}

service HelloService {
  rpc Hello(String) returns (String); // 一元方法
  rpc Channel (stream String) returns (stream String); // 流式方法
}

目录结构如下

.
├── go.mod
├── go.sum
├── helloclient
│   └── main.go
├── helloservice
│   ├── hello.proto

3、接着命令行生成对应语言的类代码

cd helloservice
 protoc --go_out=./ --go-grpc_out=./ hello.proto

我们可以看下现在的目录结构(其他文件目录可忽略,后面会创建,现在只需要关注 hello_grpc.pb.go)

.
├── go.mod
├── go.sum
├── helloclient
│   └── main.go
├── helloservice
│   ├── hello.pb.go
│   ├── hello.proto
│   ├── hello_grpc.pb.go
│   ├── hello_service.go
│   └── main
│       └── main.go

4、实现自己的 hello service

在上面生成的 hello_grpc.pb.go 中我们可以看到这样的接口

// HelloServiceServer is the server API for HelloService service.
// All implementations must embed UnimplementedHelloServiceServer
// for forward compatibility
type HelloServiceServer interface {
	Hello(context.Context, *String) (*String, error)
	Channel(HelloService_ChannelServer) error
	mustEmbedUnimplementedHelloServiceServer()
}

翻译一下就是

// HelloServiceServer 是 HelloService 服务的服务端 API。
// 所有实现都必须嵌入 UnimplementedHelloServiceServer
// 为了向前兼容

所以我们在 helloservice 中创建一个 hello_service.go 文件,用来实现上面的接口

package helloservice

import (
	"context"
	"io"
	"time"
)

type HelloService struct {
}

func (h HelloService) mustEmbedUnimplementedHelloServiceServer() {
	panic("implement me")
}

func (h HelloService) Hello(ctx context.Context, args *String) (*String, error) {
	time.Sleep(time.Second)
	reply := &String{Value: "hello:" + args.GetValue()}
	return reply, nil
}

func (h HelloService) Channel(stream HelloService_ChannelServer) error {
	for {
		recv, err := stream.Recv()
		if err != nil {
			if err == io.EOF {
				return nil
			}
			return err
		}

		reply := &String{Value: "hello:" + recv.Value}
		err = stream.Send(reply)
		if err != nil {
			return err
		}
	}
}

上面的方法和简单,就是打印我们自定义的字符串。

然后我们在 main 中编写下服务启动的代码

package main

import (
	"google.golang.org/grpc"
	"grpcdemo/helloservice"
	"log"
	"net"
)

func main() {
	// NewServer 创建一个 gRPC 服务器,它没有注册服务,也没有开始接受请求。
	grpcServer := grpc.NewServer()
	// 注册服务
	helloservice.RegisterHelloServiceServer(grpcServer, new(helloservice.HelloService))

	// 开启一个tcp监听
	listen, err := net.Listen("tcp", ":1234")
	if err != nil {
		log.Fatal(err)
	}
	log.Println("server started...")
	// 在监听器 listen 上接受传入的连接,创建一个新的ServerTransport 和 service goroutine。 服务 goroutine读取 gRPC 请求,然后调用注册的处理程序来回复它们。
	log.Fatal(grpcServer.Serve(listen))
}

然后我们启动下看下效果

go run helloservice/main/main.go
2022/10/13 23:07:46 server started...

4、grpc 客户端

接着我们编写客户端的代码 helloclient/main.go

package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	"grpcdemo/helloservice"
	"io"
	"log"
	"time"
)

func main() {
	// 连接grpc服务端
	conn, err := grpc.Dial("localhost:1234", grpc.WithInsecure())
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()

	// 一元rpc
	unaryRpc(conn)
	// 流式rpc
	streamRpc(conn)

}

func unaryRpc(conn *grpc.ClientConn) {
	// 创建grpc客户端
	client := helloservice.NewHelloServiceClient(conn)
	// 发送请求
	reply, err := client.Hello(context.Background(), &helloservice.String{Value: "hello"})
	if err != nil {
		log.Fatal(err)
	}
	log.Println("unaryRpc recv: ", reply.Value)
}

func streamRpc(conn *grpc.ClientConn) {
	// 创建grpc客户端
	client := helloservice.NewHelloServiceClient(conn)
	// 生成ClientStream
	stream, err := client.Channel(context.Background())
	if err != nil {
		log.Fatal(err)
	}

	go func() {
		for {
			// 发送消息
			if err := stream.Send(&helloservice.String{Value: "hi"}); err != nil {
				log.Fatal(err)
			}
			time.Sleep(time.Second)
		}
	}()

	for {
		// 接收消息
		recv, err := stream.Recv()
		if err != nil {
			if err == io.EOF {
				break
			}
			log.Fatal(err)
		}

		fmt.Println("streamRpc recv: ", recv.Value)

	}
}