Golang开发gRPC服务入门介绍
什么是gRPC?
gRPC是一种高性能、开源和通用的RPC框架,由Google推出,基于ProtoBuf序列化协议来实现,具有简单易用、跨语言、高效快速等特点。
gRPC工作原理是什么?
gRPC基于HTTP/2协议,利用protobuf进行序列化,传输效率极高,具体实现原理请参考官方文档
gRPC的优点
- 性能高:采用protobuf序列化协议和HTTP/2协议,传输效率极高。
- 跨语言:支持多种语言,如Go、Java、C++等。
- 易用性好:gRPC支持自动生成客户端和服务端的代码,用户开发时无需关心网络编程。
如何使用gRPC?
gRPC的使用分为三个步骤:
- 定义一个.proto文件来描述你要实现的服务。该文件定义了服务的接口、消息类型等信息。
- 根据.proto文件生成客户端和服务端的代码。gRPC支持多种代码生成语言,如Go、Java、C++等。
- 在服务端实现.proto文件中定义的接口,客户端调用生成的代码,即可实现远程调用。
示例一:使用gRPC实现简易计算器
我们将使用gRPC实现一个简易计算器服务,支持加、减、乘、除四种运算。
定义.proto文件
syntax = "proto3";
package calculator;
service Calculator {
rpc Add(Params) returns (Result) {}
rpc Subtract(Params) returns (Result) {}
rpc Multiply(Params) returns (Result) {}
rpc Divide(Params) returns (Result) {}
}
message Params {
int32 num1 = 1;
int32 num2 = 2;
}
message Result {
int32 res = 1;
}
根据.proto文件生成代码
使用protoc工具生成对应的Go代码。
protoc -I calculator/ calculator/calculator.proto --go_out=plugins=grpc:calculator
实现服务端
服务端代码如下:
package main
import (
"context"
"log"
"net"
pb "calculator"
"google.golang.org/grpc"
)
type server struct{}
func (s *server) Add(ctx context.Context, in *pb.Params) (*pb.Result, error) {
log.Printf("Add Received: %v\n", in)
return &pb.Result{Res: in.Num1 + in.Num2}, nil
}
func (s *server) Subtract(ctx context.Context, in *pb.Params) (*pb.Result, error) {
log.Printf("Subtract Received: %v\n", in)
return &pb.Result{Res: in.Num1 - in.Num2}, nil
}
func (s *server) Multiply(ctx context.Context, in *pb.Params) (*pb.Result, error) {
log.Printf("Multiply Received: %v\n", in)
return &pb.Result{Res: in.Num1 * in.Num2}, nil
}
func (s *server) Divide(ctx context.Context, in *pb.Params) (*pb.Result, error) {
log.Printf("Divide Received: %v\n", in)
return &pb.Result{Res: in.Num1 / in.Num2}, nil
}
func main() {
lis, err := net.Listen("tcp", ":8080")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
srv := grpc.NewServer()
pb.RegisterCalculatorServer(srv, &server{})
log.Printf("Listen to :8080...")
if err := srv.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
实现客户端
客户端代码如下:
package main
import (
"context"
"log"
"os"
"strconv"
pb "calculator"
"google.golang.org/grpc"
)
func main() {
conn, err := grpc.Dial(":9000", grpc.WithInsecure())
if err != nil {
log.Fatalf("failed to connect: %v", err)
}
defer conn.Close()
client := pb.NewCalculatorClient(conn)
cmd := os.Args[1]
num1, err := strconv.Atoi(os.Args[2])
num2, err := strconv.Atoi(os.Args[3])
if err != nil {
log.Fatalf("failed to parse args: %v", err)
}
var res *pb.Result
switch cmd {
case "add":
res, err = client.Add(context.Background(), &pb.Params{Num1: int32(num1), Num2: int32(num2)})
case "subtract":
res, err = client.Subtract(context.Background(), &pb.Params{Num1: int32(num1), Num2: int32(num2)})
case "multiply":
res, err = client.Multiply(context.Background(), &pb.Params{Num1: int32(num1), Num2: int32(num2)})
case "divide":
res, err = client.Divide(context.Background(), &pb.Params{Num1: int32(num1), Num2: int32(num2)})
default:
log.Fatalf("invalid command: %v", cmd)
}
if err != nil {
log.Fatalf("failed to call: %v", err)
}
log.Printf("Result: %v", res.Res)
}
运行
先启动服务端:
go run server.go
然后在另一个终端启动客户端:
go run client.go add 1 2
注意事项
- 代码中的
grpc.WithInsecure()
表示使用非安全的连接方式,实际应用中应使用安全的方式。 - 服务端代码启动时监听的端口号应与客户端代码中连接的端口号一致。
- 在调用远程函数时需要传入
context.Context
参数。
示例二:使用gRPC实现文件传输
这里介绍如何使用gRPC实现文件传输。
定义.proto文件
syntax = "proto3";
package filetransfer;
service FileTransfer {
rpc Upload(stream Chunk) returns (Response) {}
rpc Download(Request) returns (stream Chunk) {}
}
message Request {
string filename = 1;
}
message Chunk {
bytes content = 1;
}
message Response {
bool ok = 1;
}
根据.proto文件生成代码
使用protoc工具生成对应的Go代码。
protoc -I filetransfer/ filetransfer/filetransfer.proto --go_out=plugins=grpc:filetransfer
实现服务端
服务端代码如下:
package main
import (
"context"
"io"
"log"
"net"
pb "filetransfer"
"google.golang.org/grpc"
)
type server struct{}
func (s *server) Upload(stream pb.FileTransfer_UploadServer) error {
log.Printf("Upload Request\n")
dst, err := CreateFile("/tmp/"+stream.Context().Value("filename").(string), false)
if err != nil {
return err
}
for {
chunk, err := stream.Recv()
if err == io.EOF {
log.Printf("Upload Done\n")
return stream.SendAndClose(&pb.Response{Ok: true})
}
data := chunk.Content
dst.Write(data)
}
return nil
}
func (s *server) Download(req *pb.Request, stream pb.FileTransfer_DownloadServer) error {
log.Printf("Download Request, filename: %s\n", req.Filename)
file, err := OpenFile("/tmp/"+req.Filename, true)
if err != nil {
return err
}
defer file.Close()
for {
chunk := make([]byte, 1024)
n, err := file.Read(chunk)
if err == io.EOF {
log.Printf("Download Done\n")
break
} else if err != nil {
return err
}
stream.Send(&pb.Chunk{Content: chunk[:n]})
}
return nil
}
func OpenFile(filepath string, readonly bool) (*os.File, error) {
if readonly {
return os.Open(filepath)
} else {
return os.OpenFile(filepath, os.O_WRONLY|os.O_CREATE, 0666)
}
}
func CreateFile(filepath string, readonly bool) (*os.File, error) {
if readonly {
return os.Create(filepath)
} else {
return os.OpenFile(filepath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
}
}
func main() {
lis, err := net.Listen("tcp", ":9000")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
srv := grpc.NewServer()
pb.RegisterFileTransferServer(srv, &server{})
log.Printf("Listen to :9000...")
if err := srv.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
实现客户端
客户端代码如下:
package main
import (
"context"
"io"
"log"
"os"
"time"
pb "filetransfer"
"google.golang.org/grpc"
)
func main() {
conn, err := grpc.Dial(":9000", grpc.WithInsecure())
if err != nil {
log.Fatalf("failed to connect: %v", err)
}
defer conn.Close()
client := pb.NewFileTransferClient(conn)
filename := os.Args[2]
if os.Args[1] == "upload" {
stream, err := client.Upload(
context.Background(),
grpc.PerRPCCredentials(credentials.NewStaticCredentials("","***")),//添加Token
grpc.Header(&metadata.MD{"filename": []string{filename}}),
)
if err != nil {
log.Fatalf("failed to upload: %v", err)
}
file, err := os.Open(filename)
if err != nil {
log.Fatalf("failed to open file: %v", err)
}
defer file.Close()
for {
chunk := make([]byte, 1024)
n, err := file.Read(chunk)
if err == io.EOF {
break
} else if err != nil {
log.Fatalf("failed to read file: %v", err)
}
if err := stream.Send(&pb.Chunk{Content: chunk[:n]}); err != nil {
log.Fatalf("failed to send chunk: %v", err)
}
}
resp, err := stream.CloseAndRecv()
if err != nil {
log.Fatalf("failed to receive response: %v", err)
}
log.Printf("Upload Response: %v", resp.Ok)
} else if os.Args[1] == "download" {
stream, err := client.Download(context.Background(), &pb.Request{Filename: filename})
if err != nil {
log.Fatalf("failed to download: %v", err)
}
file, err := os.Create(filename)
if err != nil {
log.Fatalf("failed to create file: %v", err)
}
defer file.Close()
for {
chunk, err := stream.Recv()
if err == io.EOF {
break
} else if err != nil {
log.Fatalf("failed to receive chunk: %v", err)
}
_, err = file.Write(chunk.Content)
if err != nil {
log.Fatalf("failed to write file: %v", err)
}
}
log.Printf("Download Done")
} else {
log.Fatalf("invalid command: %v", os.Args[1])
}
}
运行
先启动服务端:
go run server.go
然后在另一个终端启动客户端上传文件:
go run client.go upload /path/to/file
或者下载文件:
go run client.go download filename
注意事项
- 异步流式传输可以大幅提高文件传输效率。
- 为了支持异步传输,在服务端和客户端的方法参数中,需要使用流式传输。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Golang开发gRPC服务入门介绍 - Python技术站