一、protobuf简介
protobuf是由Google开发的一种序列化结构化数据的方法,旨在成为一种与编程语言无关、平台中立且可扩展的机制,用于高效地序列化和反序列化数据。Protobuf通常用于通信协议、数据存储以及其他需要高效数据交换的场景。
protobuf的关键组件和概念如下:
- .proto文件
- protobuf使用一种模式定义语言来定义数据的结构,这个定义被写在一个.proto文件中,它是一个简单的文本文件。
- .proto文件定义消息类型、字段以及其他可选设置。
- 消息类型
- 在protobuf中,数据被组织成消息类型,每个类型都有一个唯一的名称。
- 消息类型类似于面向对象编程中的类,包含一个或多个字段。
- 字段
- 每个消息类型中的字段都有一个唯一的标识符(标签)和一个数据类型(例如int32、string、bool等)。
- 字段可以是可选的、必需的或可重复的。可重复的字段允许数组或值的列表。
- 序列化
- 为了序列化消息(即将其转换为二进制格式),protobuf使用紧凑的二进制格式,快速又高效。
- 序列化后的数据大小比XML或JSON小,使其成为数据交换的更高效选择。
- 语言支持
- protobuf支持多种编程语言,包括C++、Java、Python、Go、C#等。
- 每种语言的生成代码根据该语言的约定和习惯进行了定制。
以下是一个.proto文件示例:
syntax = "proto3"; // 规定使用proto3的语法
message Message {
int32 type = 1;
string user = 2;
string msg = 3;
}
在本例中,定义了一个名为Message的消息类型,包含三个字段:type,user和msg。数字1,2和3是字段标签,在序列化过程中使用。
二、grpc简介
gRPC(gRPC Remote Procedure Calls)是一个由Google开发的开源远程过程调用(RPC)框架。它构建在protobuf之上,并提供了一种高效、跨语言的通信机制。gRPC允许你定义四类服务方法:
- 简单RPC(Simple RPC):客户端发送一个请求给服务端,从服务端获取一个应答,就像一次普通的函数调用。
rpc GetMsg (Message) returns (Message);
- 服务端流式RPC(Server-side streaming RPC):一个请求对象,服务端可以传回多个结果对象。即客户端发送一个请求给服务端,可获取一个数据流用来读取一系列消息。客户端从返回的数据流里一直读取直到没有更多消息为止。
rpc GetMsg (Message) returns (stream Message);
- 客户端流式RPC(Client-side streaming RPC):客户端传入多个请求对象,服务端返回一个响应结果。即客户端用提供的一个数据流写入并发送一系列消息给服务端。一旦客户端完成消息写入,就等待服务端读取这些消息并返回应答。
rpc GetMsg (stream Message) returns (Message);
- 双向流式RPC(Bidirectional streaming RPC):结合客户端流式rpc和服务端流式rpc,可以传入多个对象,返回多个响应对象。即两边都可以分别通过一个读写数据流来发送一系列消息。这两个数据流操作是相互独立的,所以客户端和服务端能按其希望的任意顺序读写,例如:服务端可以在写应答前等待所有的客户端消息,或者它可以先读一个消息再写一个消息,或者是读写相结合的其他方式。每个数据流里消息的顺序会被保持。
rpc GetMsg (stream Message) returns (stream Message);
这里说明一下实现流式RPC的原理:
- protobuf定义
- 使用protobuf语言在.proto文件中定义消息和服务。这包括定义请求和响应消息的结构,以及服务的方法和流类型。
- IDL编译
- 使用protobuf编译器(protoc)和gRPC插件,将.proto文件编译成相应语言的源代码。
- 客户端实现
- 客户端使用生成的存根(stub)发起RPC调用,使用ClientReaderWriter实现读取和写入。
- 服务器实现
- 服务器端使用生成的存根实现服务,使用ServerReaderWriter实现读取和写入。
- HTTP/2传输
- gRPC使用HTTP/2协议进行通信,其中包括帧(Frames)和流(Streams)的概念。
- 在流式RPC中,每个消息被分割成帧,并通过一个或多个流传输。
- 多路复用
- HTTP/2支持多路复用,允许多个流同时共享一个连接。
- 异步通信
- 流式RPC允许异步通信,客户端和服务器可以并发地读取和写入数据。
- 流的结束
- 在流式RPC中,流的结束通常由一个或多个帧的特殊标志来标识。客户端和服务器都可以明确地关闭流,表示不再有数据传输。
以下是一个简单的gRPC服务和消息类型的示例:
// msg.proto
syntax = "proto3"; // 规定使用proto3的语法
message Message {
int32 type = 1;
string user = 2;
string msg = 3;
}
// 定义服务
service MsgService {
rpc GetMsg (stream Message) returns (stream Message);
}
在本例中,定义了一个消息类型(Message)和一个服务(MsgService),服务包含一个方法(GetMsg)。
三、简易聊天室实现
- 客户端
// client.cc
#include <iostream>
#include <memory>
#include <string>
#include <thread>
#include <grpcpp/grpcpp.h> // 引入头文件
// protobuf生成的头文件,包含两个信息和应用的头文件
#include "msg.grpc.pb.h"
#include "msg.pb.h"
// 这是通用工具
using grpc::Channel;
using grpc::ClientContext;
using grpc::Status;
class MsgServiceClient {
public:
MsgServiceClient(std