用Golang开始gRPC
简而言之
gRPC是一个现代化且高性能的开源RPC(远程过程调用)框架,可在任何环境中运行。它使用Protocol Buffers来序列化数据,从而实现快速通信。它能够适应各种语言和平台,并支持基于http/2的双向流传输。使用Protocol Buffers可以简单地定义服务(用于通信的数据和函数),并可以明确定义API规范。
※本章内容参考自gRPC。
Git:k-washi/example-golang-gRPC
开始项目
VSCode的设置
- vscode-proto3 & Clang-Format extensions をインストール
如果你想要自动格式化文件,你还需要设置以下内容。
- Clang-Formatをインストール
brew install clang-format #MacOSX
#other OS
#http://www.codepool.biz/vscode-format-c-code-windows-linux.html
Protocol buffers设置
安装Protocol Buffers
# MacOSX
brew install protobuf #MacOSX
# Linux
#https://github.com/google/protobuf/releases
#example
# Make sure you grab the latest version
curl -OL https://github.com/google/protobuf/releases/download/v3.5.1/protoc-3.5.1-linux-x86_64.zip
# Unzip
unzip protoc-3.5.1-linux-x86_64.zip -d protoc3
# Move protoc to /usr/local/bin/
sudo mv protoc3/bin/* /usr/local/bin/
# Move protoc3/include to /usr/local/include/
sudo mv protoc3/include/* /usr/local/include/
# Optional: change owner
sudo chown [user] /usr/local/bin/protoc
sudo chown -R [user] /usr/local/include/google
Golang的配置
安装软件包。
go get -u google.golang.org/grpc
go get -d -u github.com/golang/protobuf/protoc-gen-go
确认执行
首先,定义服务。
syntax = "proto3";
package greet;
option go_package="greetpb";
service GreetService{}
通过以下命令,将此定义文件转换为适用于golang的协议缓冲区(greet.pb.go)。通过在golang的客户端和服务器中使用此转换后的库文件(greet.pb.go)中的函数,可以实现通过gRPC进行通信。
protoc greet/greetpb/greet.proto --go_out=plugins=grpc:.
客户端通过以下方式建立连接并使用客户端API(NewGreetServiceClient)进行通信。
package main
import (
"fmt"
"log"
//protocol buffersライブラリー
"github.com/k-washi/example-golang-gRPC/greet/greetpb"
"google.golang.org/grpc"
)
func main() {
fmt.Println("Hello. I'm a client")
conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
if err != nil {
log.Fatalf("Could not connect: %v", err)
}
defer conn.Close()
c := greetpb.NewGreetServiceClient(conn)
fmt.Printf("Created client %f", c)
}
服务器定义了一个server结构体,将服务注册到服务器上,然后启动服务器。
package main
import (
"fmt"
"log"
"net"
"github.com/k-washi/example-golang-gRPC/greet/greetpb"
"google.golang.org/grpc"
)
type server struct{}
func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("Failed to listen: %v", err)
}
s := grpc.NewServer()
greetpb.RegisterGreetServiceServer(s, &server{})
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
通过运行以下命令,启动服务器并执行客户端以进行确认。
#server setup
go run greet/greet_server/server.go
#client setup
go run greet/greet_client/client.go
通信方式
gRPC提供多种通信方式,根据数据特性需要灵活选择。
-
- Unary – もっとも一般的なgRPCのAPIで、クライアントからのメッセージ1つに対して、サーバーは1回のメッセージを返す
-
- Server streaming – HTTP/2の恩恵を受けたAPIで、クライアントからのメッセージ1つに対して、サーバーは複数回のメッセージを返す
- Client streaming – HTTP/2の恩恵を受けたAPIで、クライアントからの複数のメッセージに対して、サーバーは1回のメッセージを返す
服务的定义
-
- 定义发送消息的类型
-
- 定义一元、服务器流式传输、客户端流式传输的请求和响应消息的类型
-
- 将服务定义为 rpc 服务名称(流式客户端类型)returns(流式响应类型){}。对于需要多次发送的服务消息,添加 stream。
- 使用以下命令将其转换为协议缓冲区。
protoc streaming/greetpb/greet.proto --go_out=plugins=grpc:.
syntax = "proto3";
package greet;
option go_package="greetpb";
//リクエストメッセージの型を定義
message Greeting {
string first_name = 1;
string last_name = 2;
}
// Unary リクエスト
message GreetRequest {
Greeting greeting = 1;
}
// Unary レスポンス
message GreetResponse {
string result = 1;
}
// Server streaming リクエスト
message GreetManyTimesRequest {
Greeting greeting = 1;
}
// Server streaming レスポンス
message GreetManyTimesResponse {
string result = 1;
}
// Client streaming リクエスト
message LongGreetRequest {
Greeting greeting = 1;
}
// Client streaming レスポンス
message LongGreetResponse {
string result = 1;
}
// サービスの定義
service GreetService{
//Unary
rpc Greet(GreetRequest) returns (GreetResponse) {};
//Server Streaming
rpc GreetManyTimes(GreetManyTimesRequest) returns (stream GreetManyTimesResponse) {};
//Client Streaming
rpc LongGreet(stream LongGreetRequest) returns (LongGreetResponse) {};
}
客户
使用Golang实现Unary、Server streaming和Client streaming的客户端。基本上,根据服务定义创建数据类型,发送请求,然后等待响应的形式。这些操作需要使用protocol buffers库中的包。如果需要等待服务器的多个消息,可以使用循环语法(for)进行接收并应用。如果需要向服务器发送多个消息,也可以使用循环语法,但基本上只是按照每条消息发送。有关gRPC的错误,可以使用”google.golang.org/grpc/status”库进行提取和定义。
package main
import (
"context"
"fmt"
"io"
"log"
"time"
"google.golang.org/grpc/codes"
"github.com/k-washi/example-golang-gRPC/streaming/greetpb"
"google.golang.org/grpc"
"google.golang.org/grpc/status"
)
func main() {
fmt.Println("Hello. I'm a client")
conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
if err != nil {
log.Fatalf("Could not connect: %v", err)
}
defer conn.Close()
c := greetpb.NewGreetServiceClient(conn)
doUnary(c)
doServerStreaming(c)
doClientStreaming(c)
}
func doUnary(c greetpb.GreetServiceClient) {
fmt.Println("Starting to do a Unary RPC ...")
req := &greetpb.GreetRequest{
Greeting: &greetpb.Greeting{
FirstName: "tanaka",
LastName: "tarou",
},
}
res, err := c.Greet(context.Background(), req)
if err != nil {
respErr, ok := status.FromError(err)
if ok {
fmt.Println(respErr.Message())
fmt.Println(respErr.Code())
if respErr.Code() == codes.InvalidArgument {
fmt.Println("We probably sent a empty string")
} else if respErr.Code() == codes.DeadlineExceeded {
fmt.Println("Timeout was hit! Deadline was exceeded")
}
} else {
log.Printf("error while calling greet rpc: %v", err)
}
}
fmt.Printf("Response from greet server %v", res)
}
func doServerStreaming(c greetpb.GreetServiceClient) {
fmt.Println("Starting to do a Server Streaming RPC ...")
req := &greetpb.GreetManyTimesRequest{
Greeting: &greetpb.Greeting{
FirstName: "tanaka",
LastName: "tarou",
},
}
resStream, err := c.GreetManyTimes(context.Background(), req)
if err != nil {
log.Fatalf("error while calling GreetManyTImes RPC: %v", err)
}
for {
msg, err := resStream.Recv()
if err == io.EOF {
break
}
if err != nil {
log.Fatalf("error while reading stream: %v", err)
}
log.Printf("Response from GreetManyTImes: %v", msg.GetResult())
}
}
func doClientStreaming(c greetpb.GreetServiceClient) {
fmt.Println("Starting to do a Client Streaming RPC")
requests := []*greetpb.LongGreetRequest{
&greetpb.LongGreetRequest{
Greeting: &greetpb.Greeting{
FirstName: "one",
},
},
&greetpb.LongGreetRequest{
Greeting: &greetpb.Greeting{
FirstName: "two",
},
},
&greetpb.LongGreetRequest{
Greeting: &greetpb.Greeting{
FirstName: "three",
},
},
&greetpb.LongGreetRequest{
Greeting: &greetpb.Greeting{
FirstName: "four",
},
},
}
stream, err := c.LongGreet(context.Background())
if err != nil {
log.Fatalf("error while calling LongGreet %v", err)
}
for _, req := range requests {
fmt.Printf("Sending req: %v\n", req)
stream.Send(req)
time.Sleep(1000 * time.Millisecond)
}
res, err := stream.CloseAndRecv()
if err != nil {
log.Fatalf("error while receiving response from LongGreet: %v", err)
}
fmt.Printf("LongGreet Response: %v", res)
}
服务器
在 `server` 结构体中,以与 `GreetServiceServer` 在 protocol buffers 中定义的相同格式嵌入函数。
消息的发送和接收也使用了在 protocol buffers 中定义的消息。此外,还可以使用消息对应的 `Get` 函数来获取请求。
package main
import (
"context"
"fmt"
"io"
"log"
"net"
"strconv"
"time"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/k-washi/example-golang-gRPC/streaming/greetpb"
"google.golang.org/grpc"
)
//server greet.pb.go GreetServiceClient
type server struct{}
func (*server) Greet(ctx context.Context, req *greetpb.GreetRequest) (*greetpb.GreetResponse, error) {
fmt.Printf("Greet func was invoked with %v", req)
firstName := req.GetGreeting().GetFirstName()
if firstName == "" {
return nil, status.Errorf(
codes.InvalidArgument,
"Recived a empty string",
)
}
if ctx.Err() == context.Canceled {
return nil, status.Error(codes.Canceled, "the client canceld the request")
}
result := "Hello " + firstName
//client config deadline
/*
res := &greetpb.GreetWithDeadlineResponse{
Result: result,
}
*/
res := &greetpb.GreetResponse{
Result: result,
}
return res, nil
}
func (*server) GreetManyTimes(req *greetpb.GreetManyTimesRequest, stream greetpb.GreetService_GreetManyTimesServer) error {
fmt.Printf("GreetManyTimes func was invoked with %v", req)
firstName := req.GetGreeting().GetFirstName()
for i := 0; i < 10; i++ {
result := "Hello" + firstName + " number " + strconv.Itoa(i)
res := &greetpb.GreetManyTimesResponse{
Result: result,
}
stream.Send(res)
time.Sleep(1000 * time.Millisecond)
}
return nil
}
func (*server) LongGreet(stream greetpb.GreetService_LongGreetServer) error {
fmt.Println("LongGreet function was invoked with a streaming request")
result := ""
for {
req, err := stream.Recv()
if err == io.EOF {
//we have finished reading the client stream
return stream.SendAndClose(&greetpb.LongGreetResponse{
Result: result,
})
}
if err != nil {
log.Fatalf("Error while reading client stream: %v", err)
}
firstName := req.GetGreeting().GetFirstName()
result += "Hello " + firstName + "! "
}
}
func main() {
fmt.Println("Hello World!")
lis, err := net.Listen("tcp", "0.0.0.0:50051")
if err != nil {
log.Fatalf("Failed to listen: %v", err)
}
s := grpc.NewServer()
greetpb.RegisterGreetServiceServer(s, &server{})
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
文献引用
-
- gRPCって何?
-
- Goで始めるgRPC入門
- 今から学ぶgRPCの基礎