Grpc介绍: gRPC (https://grpc.io) 是一个由Google开发的高性能、开源、跨多种编程语言和通用的远程过程调用协议(RPC) 框架,用于客户端和服务器端之间的通信,使用HTTP/2协议并将 ProtoBuf (https://developers.google.com/protocol-buffers)作为序列化工具。
Grpc几种通信方式:
gRPC主要有4种请求/响应模式,分别是:
(1) 简单模式(Simple RPC)
这种模式最为传统,即客户端发起一次请求,服务端响应一个数据,这和大家平时熟悉的RPC没有什么大的区别,所以不再详细介绍。
(2) 服务端数据流模式(Server-side streaming RPC)
这种模式是客户端发起一次请求,服务端返回一段连续的数据流。典型的例子是客户端向服务端发送一个股票代码,服务端就把该股票的实时数据源源不断的返回给客户端。
(3) 客户端数据流模式(Client-side streaming RPC)
与服务端数据流模式相反,这次是客户端源源不断的向服务端发送数据流,而在发送结束后,由服务端返回一个响应。典型的例子是物联网终端向服务器报送数据。
(4) 双向数据流模式(Bidirectional streaming RPC)
顾名思义,这是客户端和服务端都可以向对方发送数据流,这个时候双方的数据可以同时互相发送,也就是可以实现实时交互。典型的例子是聊天机器人。
个人见解:
在项目中如果是提供接口这种,比如查询账号是否存在之类的可以使用简单模式。
简单代码如下:
func (s *server) UserLogin(con context.Context, re *Login.LoginRequest) (*Login.LoginReply, error) {
v, err := mysql.UserLogin(re.GetUser(), re.GetPassword())
if err != nil {
fmt.Println("error :%v", err)
return &Login.LoginReply{}, err
}
var l string
if err != nil {
l = err.Error()
}
return &Login.LoginReply{Result: v, Error: l}, err
}
func main() {
lis, err := net.Listen("tcp", "127.0.0.1:8081")
if err != nil {
fmt.Println(err)
}
s := grpc.NewServer()
Login.RegisterUserLoginServer(s, &server{})
s.Serve(lis)
}
上面是提供账号查询的代码,客户端调用接口服务器程序就从数据库中查询返回结果。
但是如果需要长连接、相互通信那么简单模式就不行了。
我做的项目中发现网关与大厅服务器之间通信频繁,网关发送信息给大厅服务器触发某种条件,这个时候大厅服务器可能需要对所有网关进行广播,简单模式只能由调用者调用服务提供的接口,服务提供者不能主动通知。因此就需要最后一种模式 双向数据流模式,这种模式有点类似于tcp,但是帮你解决了许多问题,比如粘包之类的。
在双向数据流模式中我尝试用一个TcpConn来监听两个不同类型的数据结构,结果只能同时监听一个,于是换了种思路。
传输的为一个数据的抽象结构里面包含了数据类型与真正数据,结构如下:
message Message {
string type = 1; //消息类型
bytes data = 2; //消息内容
}
接收后对消息类型进行判断,并选择对应的结构体使用protocal进行解码,最后得出最终数据。
以下为代码:
syntax = "proto3";
package mytest;
// 请求用户信息
message UserInfoRequest {
int64 uid = 1; // 用户ID
}
// 请求用户信息的结果
message UserInfoResponse {
string name = 1; // 用户姓名
uint32 age = 2; // 用户年龄
uint32 sex = 3; // 用户性别
uint32 count = 4; // 账户余额
}
message Message {
string type = 1; //消息类型
bytes data = 2; //消息内容
}
service Data {
//简单Rpc
// 获取用户数据
//rpc GetUserInfo(UserInfoRequest) returns (UserInfoResponse){}
// 修改用户 双向流模式
rpc Communite(stream Message) returns (stream Message){}
}
protoc --go_out=plugins=grpc:. hello.proto
使用protoc生成对应go文件
客户端:
func ChangeUserInfo(client DataClient) {
m := &UserInfoRequest{Uid: 666}
out, err := proto.Marshal(m)
if err != nil {
fmt.Println(err)
return
}
notes := []*Message{
{Type: "UserInfoRequest", Data: out},
}
stream, err := client.Communite(context.Background())
if err != nil {
fmt.Println("%v.RouteChat(_) = _, %v", client, err)
}
waitc := make(chan struct{})
go func() {
for {
time.Sleep(time.Second)
in, err := stream.Recv()
if err == io.EOF {
// read done.
fmt.Println("read done ")
close(waitc)
return
}
if err != nil {
fmt.Println("Failed to receive a note : %v", err)
}
if in.Type == "UserInfoRequest" {
mess := &UserInfoRequest{}
if err := proto.Unmarshal(in.Data, mess); err != nil {
fmt.Println("proto err:%v\n", err)
}
fmt.Println(mess.GetUid())
}
stream.Send(notes[0])
}
}()
fmt.Println("ChangeUserInfo", notes)
for _, note := range notes {
if err := stream.Send(note); err != nil {
fmt.Println("Failed to send a note: %v", err)
}
}
//stream.CloseSend()
<-waitc
}
func main() {
conn, err := grpc.Dial("127.0.0.1:8081", grpc.WithInsecure())
if err != nil {
fmt.Println("did not connect: %v", err)
}
defer conn.Close()
k := mytest.NewDataClient(conn)
mytest.ChangeUserInfo(k)
}
服务端:
func main() {
lis, err := net.Listen("tcp", "127.0.0.1:8081")
if err != nil {
fmt.Println(err)
}
s := grpc.NewServer()
mytest.RegisterDataServer(s, &mytest.Server{})
s.Serve(lis)
}
func (this *Server) Communite(stream Data_CommuniteServer) error {
for {
in, err := stream.Recv()
if err == io.EOF {
fmt.Println("read done")
return nil
}
if err != nil {
fmt.Println("ERR", err)
return err
}
fmt.Println("userinfo ", in)
if err := stream.Send(in); err != nil {
return err
}
}
}
很简单的Demo,客户端发一条信息,服务端将这个信息返回。