Go语言程序开发gRPC服务攻略
什么是gRPC
gRPC是一个高性能、开源并且通用的RPC框架,由Google开源并贡献至Cloud Native Computing Foundation(CNCF)。gRPC支持超过10种语言,并且它底层使用的是HTTP2协议,以提高网络性能。
gRPC基于Protocol Buffers(proto)序列化机制,比JSON更快、更小,使用起来也更加方便。
gRPC的基础概念
- Service:gRPC定义了一种叫做Service的概念,Service由多个方法组成。
- Method:Service中的一个方法,可以用来接收参数、处理请求、返回结果。
- Message:传输的数据结构,通过protobuf生成,用于在RPC过程中传输数据。
gRPC的安装
在Go语言中使用gRPC,需要安装gRPC和protobuf。
- 安装gRPC
go get -u google.golang.org/grpc
- 安装protobuf
首先需要安装protobuf compiler
sudo apt-get install protobuf-compiler
然后安装protobuf的Go语言支持
go get -u github.com/golang/protobuf/protoc-gen-go
基于Go语言实现gRPC服务
第一步:定义消息
gRPC客户端与服务器之间的通信是通过消息进行的。所以我们需要先定义两个消息类型,一个是表示请求消息,一个是表示响应消息。
下面是一个简单的示例,定义了一个AddRequest、AddResponse消息。
syntax = "proto3";
package calculator;
message AddRequest {
int32 a = 1;
int32 b = 2;
}
message AddResponse {
int32 result = 1;
}
第二步:定义服务
接下来我们需要在.proto文件中定义服务,一个服务是多个方法的组合。
syntax = "proto3";
package calculator;
service Calculator {
rpc Add(AddRequest) returns (AddResponse) {}
}
第三步:生成Go语言代码
定义好.proto文件后,我们需要使用protobuf编译器生成对应的Go语言代码。生成命令如下:
protoc --go_out=plugins=grpc:. calculator.proto
第四步:编写服务器代码
package main
import (
"context"
"net"
"google.golang.org/grpc"
pb "path/to/calculator"
)
type server struct{}
func (s *server) Add(ctx context.Context, req *pb.AddRequest) (*pb.AddResponse, error) {
a := req.GetA()
b := req.GetB()
result := a + b
return &pb.AddResponse{Result: result}, nil
}
func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterCalculatorServer(s, &server{})
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
第五步:编写客户端代码
package main
import (
"context"
"log"
"google.golang.org/grpc"
pb "path/to/calculator"
)
func main() {
conn, err := grpc.Dial(":50051", grpc.WithInsecure())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewCalculatorClient(conn)
req := &pb.AddRequest{A: 2, B: 3}
res, err := c.Add(context.Background(), req)
if err != nil {
log.Fatalf("could not add: %v", err)
}
log.Printf("Result: %d", res.GetResult())
}
示例说明
以上就是一个简单的gRPC服务的搭建流程,当然这只是最简单的示例,实际情况下可能会涉及到更多的消息类型和方法,需要根据具体需求进行开发。
下面通过两个具体的例子,进一步讲解如何使用gRPC。
示例一:文件上传服务
我们需要实现一个上传文件的服务,客户端需要上传一个文件,并返回一个唯一的文件ID,服务端保存上传文件,并根据文件ID返回文件下载链接。
首先定义上传请求消息:
syntax = "proto3";
package file;
message UploadRequest {
bytes data = 1;
}
定义上传响应消息:
syntax = "proto3";
package file;
message UploadResponse {
string fileId = 1;
}
定义下载请求消息:
syntax = "proto3";
package file;
message DownloadRequest {
string fileId = 1;
}
定义下载响应消息:
syntax = "proto3";
package file;
message DownloadResponse {
bytes data = 1;
}
定义文件服务:
syntax = "proto3";
package file;
service FileService {
rpc Upload(UploadRequest) returns (UploadResponse) {}
rpc Download(DownloadRequest) returns (DownloadResponse) {}
}
生成Go语言代码:
protoc --go_out=plugins=grpc:. file.proto
服务端代码:
package main
import (
"bytes"
"context"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"time"
"google.golang.org/grpc"
pb "path/to/file"
)
type server struct{}
func (s *server) Upload(ctx context.Context, in *pb.UploadRequest) (*pb.UploadResponse, error) {
data := in.GetData()
fileId := fmt.Sprintf("%x", time.Now().UnixNano())
filePath := filepath.Join("./uploads/", fileId)
if err := ioutil.WriteFile(filePath, data, os.ModePerm); err != nil {
return nil, err
}
url := fmt.Sprintf("http://localhost:8080/uploads/%s", fileId)
return &pb.UploadResponse{
FileId: fileId,
Url: url,
}, nil
}
func (s *server) Download(ctx context.Context, in *pb.DownloadRequest) (*pb.DownloadResponse, error) {
fileId := in.GetFileId()
filePath := filepath.Join("./uploads/", fileId)
data, err := ioutil.ReadFile(filePath)
if err != nil {
return nil, err
}
return &pb.DownloadResponse{Data: data}, nil
}
func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterFileServer(s, &server{})
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
客户端代码:
package main
import (
"context"
"fmt"
"io/ioutil"
"log"
"google.golang.org/grpc"
pb "path/to/file"
)
func main() {
conn, err := grpc.Dial(":50051", grpc.WithInsecure())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewFileClient(conn)
data, err := ioutil.ReadFile("/path/to/file")
if err != nil {
log.Fatal(err)
}
uploadRes, err := c.Upload(context.Background(), &pb.UploadRequest{Data: data})
if err != nil {
log.Fatal(err)
}
fmt.Println(uploadRes.FileId)
downloadRes, err := c.Download(context.Background(), &pb.DownloadRequest{FileId: uploadRes.FileId})
if err != nil {
log.Fatal(err)
}
ioutil.WriteFile(fmt.Sprintf("./downloads/%s", uploadRes.FileId), downloadRes.Data, 0666)
}
示例二:数据流服务
我们需要实现一个数据流服务,客户端向服务端发送数据流,服务端将收到的数据流反转并返回。
首先定义请求消息:
syntax = "proto3";
package stream;
message StreamRequest {
string data = 1;
}
定义响应消息:
syntax = "proto3";
package stream;
message StreamResponse {
string data = 1;
}
定义数据流服务:
syntax = "proto3";
package stream;
service StreamService {
rpc Stream(stream StreamRequest) returns (stream StreamResponse) {}
}
生成Go语言代码:
protoc --go_out=plugins=grpc:. stream.proto
服务端代码:
package main
import (
"context"
"log"
"strings"
"google.golang.org/grpc"
pb "path/to/stream"
)
type server struct{}
func (s *server) Stream(stream pb.StreamService_StreamServer) error {
var data []string
for {
req, err := stream.Recv()
if err != nil {
return err
}
data = append(data, req.GetData())
for i := len(data) - 1; i >= 0; i-- {
if err := stream.Send(&pb.StreamResponse{Data: reverseString(data[i])}); err != nil {
return err
}
}
}
}
func reverseString(s string) string {
var res string
for _, c := range s {
res = string(c) + res
}
return res
}
func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterStreamServiceServer(s, &server{})
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
客户端代码:
package main
import (
"context"
"io"
"log"
"google.golang.org/grpc"
pb "path/to/stream"
)
func main() {
conn, err := grpc.Dial(":50051", grpc.WithInsecure())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewStreamServiceClient(conn)
stream, err := c.Stream(context.Background())
if err != nil {
log.Fatalf("error: %v", err)
}
data := []string{"hello", "world", "grpc", "stream"}
for _, d := range data {
if err := stream.Send(&pb.StreamRequest{Data: d}); err != nil {
log.Fatalf("error: %v", err)
}
}
if err := stream.CloseSend(); err != nil {
log.Fatalf("error: %v", err)
}
for {
res, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
log.Fatalf("error: %v", err)
}
log.Println(res.GetData())
}
}
以上就是两个具体的示例,希望读者通过这些示例更好地掌握如何使用gRPC进行Go语言开发。