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,客户端发一条信息,服务端将这个信息返回。

 

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐