go-grpc Note1
参考链接:grpc.io
Note:记录这一部分也主要是因为过去资料大多失效,请注意版本时间(2023年7月10日)
Quick Start
Install Protocol Buffer Compiler
$ wget https://github.com/google/protobuf/releases/download/v3.11.2/protobuf-all-3.11.2.zip
$ unzip protobuf-all-3.11.2.zip && cd protobuf-3.11.2/
$ ./configure
$ make
$ make install
通过$ protoc --version
检查protoc版本>3.x
其他安装方法 Install Proto Buffer Compiler
Install Go plugins for the protocol compiler
针对不同的语言,还需要不同的运行时的 protoc 插件,那么对应 Go 语言就是 protoc-gen-go 插件
$ 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
通过export PATH="$PATH:$(go env GOPATH)/bin"
更新系统环境变量
go-rpc example
代码来源: 代码来源
名为helloworld的文件目录如下:
├── helloworld
│ ├── greeter_client
│ │ └── main.go
│ ├── greeter_server
│ │ └── main.go
│ ├── helloworld
│ │ ├── helloworld_grpc.pb.go #由helloworld.proto生成
│ │ ├── helloworld.pb.go #由helloworld.proto生成
│ │ └── helloworld.proto
│ └── README.md
helloworld.proto
syntax = "proto3";
package helloworld;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
// Sends another greeting
rpc SayHelloAgain (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
由helloworld.proto 生成.pb
$ protoc --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
helloworld/helloworld.proto
此时应当在helloworld文件下生成_.pb.go和_grpc.pb.go文件
gretter_client/main.go
package main
import (
"context"
"flag"
"log"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
pb "google.golang.org/grpc/examples/helloworld/helloworld"
)
const (
defaultName = "world"
)
var (
addr = flag.String("addr", "localhost:50051", "the address to connect to")
name = flag.String("name", defaultName, "Name to greet")
)
func main() {
flag.Parse()
// Set up a connection to the server.
conn, err := grpc.Dial(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewGreeterClient(conn)
// Contact the server and print out its response.
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: *name})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", r.GetMessage())
r, err = c.SayHelloAgain(ctx, &pb.HelloRequest{Name: *name})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", r.GetMessage())
}
gretter_server/main.go
package main
import (
"context"
"flag"
"fmt"
"log"
"net"
"google.golang.org/grpc"
pb "google.golang.org/grpc/examples/helloworld/helloworld"
)
var (
port = flag.Int("port", 50051, "The server port")
)
// server is used to implement helloworld.GreeterServer.
type server struct {
pb.UnimplementedGreeterServer
}
// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
log.Printf("Received: %v", in.GetName())
return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}
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()
pb.RegisterGreeterServer(s, &server{})
log.Printf("server listening at %v", lis.Addr())
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
func (s *server) SayHelloAgain(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
return &pb.HelloReply{Message: "Hello again " + in.GetName()}, nil
}
至此,我们应该有一个完整的示例文件。
RUN & Test
启动服务:
$ go run greeter_server/main.go
在另一个终端启动客户端:$ go run greeter_client/main.go --name=Alice
输出结果:
[root@xx helloworld] go run greeter_server/main.go
2023/07/11 15:26:24 server listening at [::]:50051
2023/07/11 15:26:30 Received: Alice
--------
[root@xx helloworld] go run greeter_client/main.go --name=Alice
2023/07/11 15:26:30 Greeting: Hello Alice
2023/07/11 15:26:30 Greeting: Hello again Alice
Understand
上面的步骤可以达成完整的grpc应用,那么如何理解Go Server&Client 利用ProtoBuffer工作(或者调用逻辑)的呢?(可能随版本更迭存在区别,@2023年7月11日)
About .proto
观察helloworld/helloworld.proto中服务声明部分代码:
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
// Sends another greeting
rpc SayHelloAgain (HelloRequest) returns (HelloReply) {}
}
... ... //The request & response message definition
包括两个rpc调用,即SayHello
和SayHelloAgain
,接收HelloRequest
然后返回HelloReply
消息(Message)
Ablout .go generated by .proto
观察生成的helloworld.pb.go
其定义了 HelloRequest
和HelloReply
的消息结构体和GetName方法(以请求消息为例):
// The request message containing the user's name.
type HelloRequest struct {
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
... ...
}
... //GetName method
func (m *HelloRequest) GetName() string {
if m != nil {
return m.Name
}
return ""
}
而另一个文件helloworld_grpc.pb.go
则包括服务注册RegisterGreeterServer
和New客户端NewGreeterClient
方法。
Client部分
Client结构与实例化相关代码如下:
type GreeterClient interface {
// Sends a greeting
SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error)
// Sends another greeting
SayHelloAgain(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error)
}
...
type greeterClient struct {
cc grpc.ClientConnInterface
}
...
func NewGreeterClient(cc grpc.ClientConnInterface) GreeterClient {
return &greeterClient{cc}
}
...
其中greeterClient结构体包括的cc是grpc.ClientConnInterface
,这里给出grpc.ClientConnInterface
的代码:
type ClientConnInterface interface {
// Invoke performs a unary RPC and returns after the response is received into reply.
Invoke(ctx context.Context, method string, args interface{}, reply interface{}, opts ...CallOption) error
// NewStream begins a streaming RPC.
NewStream(ctx context.Context, desc *StreamDesc, method string, opts ...CallOption) (ClientStream, error)
}
存在Invoke
和NewStream
两种方法,分别对应一元rpc与流rpc,将在client中被调用。
而Client中包括SayHello
和SayHelloAgain
两个方法,以其一为例代码如下:
func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) {
out := new(HelloReply)
err := c.cc.Invoke(ctx, "/helloworld.Greeter/SayHello", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
调用了ClientConnInterface
中的Invoke
方法,其中ctx上下文由Client/main.go中提供示例,method方法名则对应"/helloworld.Greeter/SayHello"
自动生成,in、out作为args、reply。
Server部分
Server结构与实例化相关代码如下:
type GreeterServer interface {
// Sends a greeting
SayHello(context.Context, *HelloRequest) (*HelloReply, error)
// Sends another greeting
SayHelloAgain(context.Context, *HelloRequest) (*HelloReply, error)
mustEmbedUnimplementedGreeterServer()
}
type UnsafeGreeterServer interface {
mustEmbedUnimplementedGreeterServer()
}
func RegisterGreeterServer(s grpc.ServiceRegistrar, srv GreeterServer) {
s.RegisterService(&Greeter_ServiceDesc, srv)
}
其中mustEmbedUnimplementedGreeterServer()
是为了前向兼容,不用过分在意,RegisterGreeterServer
将用于注册Server于Server/main.go中。而SayHello
方法与Client部分类似,不赘述。
About .go
Client部分
可以通过注释理解其中内容,主要调用有以下几点:
1.配置地址与安全相关信息:grpc.Dial
2.实例化Client:pb.NewGreeterClient(conn)
3.获取ctx:context.WithTimeout
,注意这里的withtime是其中一种,也有withCancel
之返回一个cancel.ctx
4.执行SayHello等动作
5.调用defer,结束调用,注意这里defer的顺序为:cancel->close
,从栈顶弹出cancel再close
...
pb "helloworld/helloworld" //常用别名
var (
addr = flag.String("addr", "localhost:50051", "the address to connect to")
name = flag.String("name", defaultName, "Name to greet")
)//flag接受参数
func main() {
flag.Parse()
// Set up a connection to the server.
conn, err := grpc.Dial(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
//注意,这里rpc.WithTransportCredentials(...)禁用了安全配置,为简化入门程序
if err != nil {...}
defer conn.Close()
c := pb.NewGreeterClient(conn)//实例化了一个Client
//给定一个处理超时配置,1s(个人理解)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: *name})//发送携带Name的rpc Request 通过SayHello
...
}
Server部分
主要代码如下:
// server is used to implement helloworld.GreeterServer.
type server struct {
pb.UnimplementedGreeterServer //结构体封装为server
}
//implements helloworld.GreeterServer 供Client调用,也就是rpc中的p
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
log.Printf("Received: %v", in.GetName())
return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}
func (s *server) SayHelloAgain(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
return &pb.HelloReply{Message: "Hello again " + in.GetName()}, nil
}
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()
pb.RegisterGreeterServer(s, &server{})
log.Printf("server listening at %v", lis.Addr())
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
以上存在个人理解,欢迎交流