Go gRPC 系列二:一元客户端与服务端
煎鱼啊 · · 1048 次点击 · · 开始浏览前言
大家好,我是煎鱼,本章节将使用 Go 来编写 gRPC Server 和 Client,让其互相通讯。在此之上会使用到如下库:
- google.golang.org/grpc
- github.com/golang/protobuf/protoc-gen-go
安装
gRPC
go get -u google.golang.org/grpc
复制代码Protocol Buffers v3
wget https://github.com/google/protobuf/releases/download/v3.5.1/protobuf-all-3.5.1.zip
unzip protobuf-all-3.5.1.zip
cd protobuf-3.5.1/
./configure
make
make install
复制代码检查是否安装成功
protoc --version
复制代码若出现以下错误,执行 ldconfig 命名就能解决这问题
protoc: error while loading shared libraries: libprotobuf.so.15: cannot open shared object file: No such file or directory
复制代码Protoc Plugin
go get -u github.com/golang/protobuf/protoc-gen-go
复制代码安装环境若有问题,可参考我先前的文章 《介绍与环境安装》 内有详细介绍,不再赘述
gRPC
本小节开始正式编写 gRPC 相关的程序,一起上车吧 ????
图示
目录结构
$ tree go-grpc-example
go-grpc-example
├── client
├── proto
│ └── search.proto
└── server.go
复制代码IDL
编写
在 proto 文件夹下的 search.proto 文件中,写入如下内容:
syntax = "proto3";
package proto;
service SearchService {
rpc Search(SearchRequest) returns (SearchResponse) {}
}
message SearchRequest {
string request = 1;
}
message SearchResponse {
string response = 1;
}
复制代码生成
在 proto 文件夹下执行如下命令:
$ protoc --go_out=plugins=grpc:. *.proto
复制代码- plugins=plugin1+plugin2:指定要加载的子插件列表
我们定义的 proto 文件是涉及了 RPC 服务的,而默认是不会生成 RPC 代码的,因此需要给出 plugins 参数传递给 protoc-gen-go,告诉它,请支持 RPC(这里指定了 gRPC)
- --go_out=.:设置 Go 代码输出的目录
该指令会加载 protoc-gen-go 插件达到生成 Go 代码的目的,生成的文件以 .pb.go 为文件后缀
- : (冒号)
冒号充当分隔符的作用,后跟所需要的参数集。如果这处不涉及 RPC,命令可简化为:
$ protoc --go_out=. *.proto
复制代码注:建议你看看两条命令生成的 .pb.go 文件,分别有什么区别
生成后
执行完毕命令后,将得到一个 .pb.go 文件,文件内容如下:
type SearchRequest struct {
Request string `protobuf:"bytes,1,opt,name=request" json:"request,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *SearchRequest) Reset() { *m = SearchRequest{} }
func (m *SearchRequest) String() string { return proto.CompactTextString(m) }
func (*SearchRequest) ProtoMessage() {}
func (*SearchRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_search_8b45f79ee13ff6a3, []int{0}
}
func (m *SearchRequest) GetRequest() string {
if m != nil {
return m.Request
}
return ""
}
复制代码通过阅读这一部分代码,可以知道主要涉及如下方面:
- 字段名称从小写下划线转换为大写驼峰模式(字段导出)
- 生成一组 Getters 方法,能便于处理一些空指针取值的情况
- ProtoMessage 方法实现 proto.Message 的接口
- 生成 Rest 方法,便于将 Protobuf 结构体恢复为零值
- Repeated 转换为切片
type SearchRequest struct {
Request string `protobuf:"bytes,1,opt,name=request" json:"request,omitempty"`
}
func (*SearchRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_search_8b45f79ee13ff6a3, []int{0}
}
type SearchResponse struct {
Response string `protobuf:"bytes,1,opt,name=response" json:"response,omitempty"`
}
func (*SearchResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_search_8b45f79ee13ff6a3, []int{1}
}
...
func init() { proto.RegisterFile("search.proto", fileDescriptor_search_8b45f79ee13ff6a3) }
var fileDescriptor_search_8b45f79ee13ff6a3 = []byte{
// 131 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x29, 0x4e, 0x4d, 0x2c,
0x4a, 0xce, 0xd0, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x05, 0x53, 0x4a, 0x9a, 0x5c, 0xbc,
0xc1, 0x60, 0xe1, 0xa0, 0xd4, 0xc2, 0xd2, 0xd4, 0xe2, 0x12, 0x21, 0x09, 0x2e, 0xf6, 0x22, 0x08,
0x53, 0x82, 0x51, 0x81, 0x51, 0x83, 0x33, 0x08, 0xc6, 0x55, 0xd2, 0xe1, 0xe2, 0x83, 0x29, 0x2d,
0x2e, 0xc8, 0xcf, 0x2b, 0x4e, 0x15, 0x92, 0xe2, 0xe2, 0x28, 0x82, 0xb2, 0xa1, 0x8a, 0xe1, 0x7c,
0x23, 0x0f, 0x98, 0xc1, 0xc1, 0xa9, 0x45, 0x65, 0x99, 0xc9, 0xa9, 0x42, 0xe6, 0x5c, 0x6c, 0x10,
0x01, 0x21, 0x11, 0x88, 0x13, 0xf4, 0x50, 0x2c, 0x96, 0x12, 0x45, 0x13, 0x85, 0x98, 0xa3, 0xc4,
0x90, 0xc4, 0x06, 0x16, 0x37, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0xf3, 0xba, 0x74, 0x95, 0xc0,
0x00, 0x00, 0x00,
}
复制代码而这一部分代码主要是围绕 fileDescriptor 进行,在这里 fileDescriptor_search_8b45f79ee13ff6a3 表示一个编译后的 proto 文件,而每一个方法都包含 Descriptor 方法,代表着这一个方法在 fileDescriptor 中具体的 Message Field
Server
这一小节将编写 gRPC Server 的基础模板,完成一个方法的调用。对 server.go 写入如下内容:
package main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
pb "github.com/EDDYCJY/go-grpc-example/proto"
)
type SearchService struct{}
func (s *SearchService) Search(ctx context.Context, r *pb.SearchRequest) (*pb.SearchResponse, error) {
return &pb.SearchResponse{Response: r.GetRequest() + " Server"}, nil
}
const PORT = "9001"
func main() {
server := grpc.NewServer()
pb.RegisterSearchServiceServer(server, &SearchService{})
lis, err := net.Listen("tcp", ":"+PORT)
if err != nil {
log.Fatalf("net.Listen err: %v", err)
}
server.Serve(lis)
}
复制代码- 创建 gRPC Server 对象,你可以理解为它是 Server 端的抽象对象
- 将 SearchService(其包含需要被调用的服务端接口)注册到 gRPC Server 的内部注册中心。这样可以在接受到请求时,通过内部的服务发现,发现该服务端接口并转接进行逻辑处理
- 创建 Listen,监听 TCP 端口
- gRPC Server 开始 lis.Accept,直到 Stop 或 GracefulStop
Client
接下来编写 gRPC Go Client 的基础模板,打开 client/client.go 文件,写入以下内容:
package main
import (
"context"
"log"
"google.golang.org/grpc"
pb "github.com/EDDYCJY/go-grpc-example/proto"
)
const PORT = "9001"
func main() {
conn, err := grpc.Dial(":"+PORT, grpc.WithInsecure())
if err != nil {
log.Fatalf("grpc.Dial err: %v", err)
}
defer conn.Close()
client := pb.NewSearchServiceClient(conn)
resp, err := client.Search(context.Background(), &pb.SearchRequest{
Request: "gRPC",
})
if err != nil {
log.Fatalf("client.Search err: %v", err)
}
log.Printf("resp: %s", resp.GetResponse())
}
复制代码- 创建与给定目标(服务端)的连接交互
- 创建 SearchService 的客户端对象
- 发送 RPC 请求,等待同步响应,得到回调后返回响应结果
- 输出响应结果
验证
启动 Server
$ pwd
$GOPATH/github.com/EDDYCJY/go-grpc-example
$ go run server.go
复制代码启动 Client
$ pwd
$GOPATH/github.com/EDDYCJY/go-grpc-example/client
$ go run client.go
2018年09月23日 11:06:23 resp: gRPC Server
复制代码总结
在本章节,我们对 Protobuf、gRPC Client/Server 分别都进行了介绍。希望你结合文中讲述内容再写一个 Demo 进行深入了解,肯定会更棒 ????
?
如果有任何疑问或错误,欢迎在 issues 进行提问或给予修正意见,如果喜欢或对你有所帮助,欢迎 Star,对作者是一种鼓励和推进。
我的公众号
参考
本系列示例代码
有疑问加站长微信联系(非本文作者)
入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889
关注微信- 请尽量让自己的回复能够对别人有帮助
- 支持 Markdown 格式, **粗体**、~~删除线~~、
`单行代码` - 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
- 图片支持拖拽、截图粘贴等方式上传
收入到我管理的专栏 新建专栏
前言
大家好,我是煎鱼,本章节将使用 Go 来编写 gRPC Server 和 Client,让其互相通讯。在此之上会使用到如下库:
- google.golang.org/grpc
- github.com/golang/protobuf/protoc-gen-go
安装
gRPC
go get -u google.golang.org/grpc
复制代码Protocol Buffers v3
wget https://github.com/google/protobuf/releases/download/v3.5.1/protobuf-all-3.5.1.zip
unzip protobuf-all-3.5.1.zip
cd protobuf-3.5.1/
./configure
make
make install
复制代码检查是否安装成功
protoc --version
复制代码若出现以下错误,执行 ldconfig 命名就能解决这问题
protoc: error while loading shared libraries: libprotobuf.so.15: cannot open shared object file: No such file or directory
复制代码Protoc Plugin
go get -u github.com/golang/protobuf/protoc-gen-go
复制代码安装环境若有问题,可参考我先前的文章 《介绍与环境安装》 内有详细介绍,不再赘述
gRPC
本小节开始正式编写 gRPC 相关的程序,一起上车吧 ????
图示
目录结构
$ tree go-grpc-example
go-grpc-example
├── client
├── proto
│ └── search.proto
└── server.go
复制代码IDL
编写
在 proto 文件夹下的 search.proto 文件中,写入如下内容:
syntax = "proto3";
package proto;
service SearchService {
rpc Search(SearchRequest) returns (SearchResponse) {}
}
message SearchRequest {
string request = 1;
}
message SearchResponse {
string response = 1;
}
复制代码生成
在 proto 文件夹下执行如下命令:
$ protoc --go_out=plugins=grpc:. *.proto
复制代码- plugins=plugin1+plugin2:指定要加载的子插件列表
我们定义的 proto 文件是涉及了 RPC 服务的,而默认是不会生成 RPC 代码的,因此需要给出 plugins 参数传递给 protoc-gen-go,告诉它,请支持 RPC(这里指定了 gRPC)
- --go_out=.:设置 Go 代码输出的目录
该指令会加载 protoc-gen-go 插件达到生成 Go 代码的目的,生成的文件以 .pb.go 为文件后缀
- : (冒号)
冒号充当分隔符的作用,后跟所需要的参数集。如果这处不涉及 RPC,命令可简化为:
$ protoc --go_out=. *.proto
复制代码注:建议你看看两条命令生成的 .pb.go 文件,分别有什么区别
生成后
执行完毕命令后,将得到一个 .pb.go 文件,文件内容如下:
type SearchRequest struct {
Request string `protobuf:"bytes,1,opt,name=request" json:"request,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *SearchRequest) Reset() { *m = SearchRequest{} }
func (m *SearchRequest) String() string { return proto.CompactTextString(m) }
func (*SearchRequest) ProtoMessage() {}
func (*SearchRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_search_8b45f79ee13ff6a3, []int{0}
}
func (m *SearchRequest) GetRequest() string {
if m != nil {
return m.Request
}
return ""
}
复制代码通过阅读这一部分代码,可以知道主要涉及如下方面:
- 字段名称从小写下划线转换为大写驼峰模式(字段导出)
- 生成一组 Getters 方法,能便于处理一些空指针取值的情况
- ProtoMessage 方法实现 proto.Message 的接口
- 生成 Rest 方法,便于将 Protobuf 结构体恢复为零值
- Repeated 转换为切片
type SearchRequest struct {
Request string `protobuf:"bytes,1,opt,name=request" json:"request,omitempty"`
}
func (*SearchRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_search_8b45f79ee13ff6a3, []int{0}
}
type SearchResponse struct {
Response string `protobuf:"bytes,1,opt,name=response" json:"response,omitempty"`
}
func (*SearchResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_search_8b45f79ee13ff6a3, []int{1}
}
...
func init() { proto.RegisterFile("search.proto", fileDescriptor_search_8b45f79ee13ff6a3) }
var fileDescriptor_search_8b45f79ee13ff6a3 = []byte{
// 131 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x29, 0x4e, 0x4d, 0x2c,
0x4a, 0xce, 0xd0, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x05, 0x53, 0x4a, 0x9a, 0x5c, 0xbc,
0xc1, 0x60, 0xe1, 0xa0, 0xd4, 0xc2, 0xd2, 0xd4, 0xe2, 0x12, 0x21, 0x09, 0x2e, 0xf6, 0x22, 0x08,
0x53, 0x82, 0x51, 0x81, 0x51, 0x83, 0x33, 0x08, 0xc6, 0x55, 0xd2, 0xe1, 0xe2, 0x83, 0x29, 0x2d,
0x2e, 0xc8, 0xcf, 0x2b, 0x4e, 0x15, 0x92, 0xe2, 0xe2, 0x28, 0x82, 0xb2, 0xa1, 0x8a, 0xe1, 0x7c,
0x23, 0x0f, 0x98, 0xc1, 0xc1, 0xa9, 0x45, 0x65, 0x99, 0xc9, 0xa9, 0x42, 0xe6, 0x5c, 0x6c, 0x10,
0x01, 0x21, 0x11, 0x88, 0x13, 0xf4, 0x50, 0x2c, 0x96, 0x12, 0x45, 0x13, 0x85, 0x98, 0xa3, 0xc4,
0x90, 0xc4, 0x06, 0x16, 0x37, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0xf3, 0xba, 0x74, 0x95, 0xc0,
0x00, 0x00, 0x00,
}
复制代码而这一部分代码主要是围绕 fileDescriptor 进行,在这里 fileDescriptor_search_8b45f79ee13ff6a3 表示一个编译后的 proto 文件,而每一个方法都包含 Descriptor 方法,代表着这一个方法在 fileDescriptor 中具体的 Message Field
Server
这一小节将编写 gRPC Server 的基础模板,完成一个方法的调用。对 server.go 写入如下内容:
package main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
pb "github.com/EDDYCJY/go-grpc-example/proto"
)
type SearchService struct{}
func (s *SearchService) Search(ctx context.Context, r *pb.SearchRequest) (*pb.SearchResponse, error) {
return &pb.SearchResponse{Response: r.GetRequest() + " Server"}, nil
}
const PORT = "9001"
func main() {
server := grpc.NewServer()
pb.RegisterSearchServiceServer(server, &SearchService{})
lis, err := net.Listen("tcp", ":"+PORT)
if err != nil {
log.Fatalf("net.Listen err: %v", err)
}
server.Serve(lis)
}
复制代码- 创建 gRPC Server 对象,你可以理解为它是 Server 端的抽象对象
- 将 SearchService(其包含需要被调用的服务端接口)注册到 gRPC Server 的内部注册中心。这样可以在接受到请求时,通过内部的服务发现,发现该服务端接口并转接进行逻辑处理
- 创建 Listen,监听 TCP 端口
- gRPC Server 开始 lis.Accept,直到 Stop 或 GracefulStop
Client
接下来编写 gRPC Go Client 的基础模板,打开 client/client.go 文件,写入以下内容:
package main
import (
"context"
"log"
"google.golang.org/grpc"
pb "github.com/EDDYCJY/go-grpc-example/proto"
)
const PORT = "9001"
func main() {
conn, err := grpc.Dial(":"+PORT, grpc.WithInsecure())
if err != nil {
log.Fatalf("grpc.Dial err: %v", err)
}
defer conn.Close()
client := pb.NewSearchServiceClient(conn)
resp, err := client.Search(context.Background(), &pb.SearchRequest{
Request: "gRPC",
})
if err != nil {
log.Fatalf("client.Search err: %v", err)
}
log.Printf("resp: %s", resp.GetResponse())
}
复制代码- 创建与给定目标(服务端)的连接交互
- 创建 SearchService 的客户端对象
- 发送 RPC 请求,等待同步响应,得到回调后返回响应结果
- 输出响应结果
验证
启动 Server
$ pwd
$GOPATH/github.com/EDDYCJY/go-grpc-example
$ go run server.go
复制代码启动 Client
$ pwd
$GOPATH/github.com/EDDYCJY/go-grpc-example/client
$ go run client.go
2018年09月23日 11:06:23 resp: gRPC Server
复制代码总结
在本章节,我们对 Protobuf、gRPC Client/Server 分别都进行了介绍。希望你结合文中讲述内容再写一个 Demo 进行深入了解,肯定会更棒 ????
?
如果有任何疑问或错误,欢迎在 issues 进行提问或给予修正意见,如果喜欢或对你有所帮助,欢迎 Star,对作者是一种鼓励和推进。