我想使用protobuf在客户端和服务器之间来回发送消息。在我的情况下,我想从服务器向客户端发送任意数量的protobuf消息。如何在C ++中快速构建它?
注意:我在stackoverflow上汇集了一个非常有用的Kenton Varda answer和Fulkerson answer之后,我写了这个问题和我的答案。其他人提出了类似的问题并遇到了类似的障碍 - 请参阅here,here和here。
我是protobuf和asio的新手,所以请随时纠正/提出改进建议,或提供自己的答案。
答案 0 :(得分:9)
首先,C ++ protobuf API缺乏内置支持,可通过单个流/连接发送多个protobuf消息。 Java API有它,但它仍然没有被添加到C ++版本中。 Kenton Varda(protobuf v2的创建者)足以发布C++ version。因此,您需要该代码才能在单个连接上获得对多条消息的支持。
然后,您可以使用boost :: asio创建客户端/服务器。 Don
将所有这些与基本的boost :: asio教程放在一起,这里是客户端和服务器,后面是支持代码。我们发送了一个名为persistence :: MyMessage的简单protobuf类的多个实例,它位于MyMessage.pb.h中。将其替换为您自己的。
客户端:
#include <boost/asio.hpp>
#include "ProtobufHelpers.h"
#include "AsioAdapting.h"
#include "MyMessage.pb.h"
using boost::asio::ip::tcp;
int main()
{
const char* hostname = "127.0.0.1";
const char* port = "27015";
boost::asio::io_service io_service;
tcp::resolver resolver(io_service);
tcp::resolver::query query(hostname, port);
tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
tcp::socket socket(io_service);
boost::asio::connect(socket, endpoint_iterator);
AsioInputStream<tcp::socket> ais(socket);
CopyingInputStreamAdaptor cis_adp(&ais);
for (;;)
{
persistence::MyMessage myMessage;
google::protobuf::io::readDelimitedFrom(&cis_adp, &myMessage);
}
return 0;
}
服务器:
#include <boost/asio.hpp>
#include "ProtobufHelpers.h"
#include "AsioAdapting.h"
#include "MyMessage.pb.h"
using boost::asio::ip::tcp;
int main()
{
boost::asio::io_service io_service;
tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), 27015));
for (;;)
{
tcp::socket socket(io_service);
acceptor.accept(socket);
AsioOutputStream<boost::asio::ip::tcp::socket> aos(socket); // Where m_Socket is a instance of boost::asio::ip::tcp::socket
CopyingOutputStreamAdaptor cos_adp(&aos);
int i = 0;
do {
++i;
persistence::MyMessage myMessage;
myMessage.set_myString("hello world");
myMessage.set_myInt(i);
google::protobuf::io::writeDelimitedTo(metricInfo, &cos_adp);
// Now we have to flush, otherwise the write to the socket won't happen until enough bytes accumulate
cos_adp.Flush();
} while (true);
}
return 0;
}
以下是Kenton Varda提供的支持文件:
ProtobufHelpers.h
#pragma once
#include <google/protobuf/io/coded_stream.h>
#include <google/protobuf/io/zero_copy_stream.h>
#include <google/protobuf/message_lite.h>
namespace google {
namespace protobuf {
namespace io {
bool writeDelimitedTo(
const google::protobuf::MessageLite& message,
google::protobuf::io::ZeroCopyOutputStream* rawOutput);
bool readDelimitedFrom(
google::protobuf::io::ZeroCopyInputStream* rawInput,
google::protobuf::MessageLite* message);
}
}
}
和
ProtobufHelpers.cpp
#include "ProtobufHelpers.h"
namespace google {
namespace protobuf {
namespace io {
bool writeDelimitedTo(
const google::protobuf::MessageLite& message,
google::protobuf::io::ZeroCopyOutputStream* rawOutput) {
// We create a new coded stream for each message. Don't worry, this is fast.
google::protobuf::io::CodedOutputStream output(rawOutput);
// Write the size.
const int size = message.ByteSize();
output.WriteVarint32(size);
uint8_t* buffer = output.GetDirectBufferForNBytesAndAdvance(size);
if (buffer != NULL) {
// Optimization: The message fits in one buffer, so use the faster
// direct-to-array serialization path.
message.SerializeWithCachedSizesToArray(buffer);
}
else {
// Slightly-slower path when the message is multiple buffers.
message.SerializeWithCachedSizes(&output);
if (output.HadError()) return false;
}
return true;
}
bool readDelimitedFrom(
google::protobuf::io::ZeroCopyInputStream* rawInput,
google::protobuf::MessageLite* message) {
// We create a new coded stream for each message. Don't worry, this is fast,
// and it makes sure the 64MB total size limit is imposed per-message rather
// than on the whole stream. (See the CodedInputStream interface for more
// info on this limit.)
google::protobuf::io::CodedInputStream input(rawInput);
// Read the size.
uint32_t size;
if (!input.ReadVarint32(&size)) return false;
// Tell the stream not to read beyond that size.
google::protobuf::io::CodedInputStream::Limit limit =
input.PushLimit(size);
// Parse the message.
if (!message->MergeFromCodedStream(&input)) return false;
if (!input.ConsumedEntireMessage()) return false;
// Release the limit.
input.PopLimit(limit);
return true;
}
}
}
}
和富勒克森的礼貌:
AsioAdapting.h
#pragma once
#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
using namespace google::protobuf::io;
template <typename SyncReadStream>
class AsioInputStream : public CopyingInputStream {
public:
AsioInputStream(SyncReadStream& sock);
int Read(void* buffer, int size);
private:
SyncReadStream& m_Socket;
};
template <typename SyncReadStream>
AsioInputStream<SyncReadStream>::AsioInputStream(SyncReadStream& sock) :
m_Socket(sock) {}
template <typename SyncReadStream>
int
AsioInputStream<SyncReadStream>::Read(void* buffer, int size)
{
std::size_t bytes_read;
boost::system::error_code ec;
bytes_read = m_Socket.read_some(boost::asio::buffer(buffer, size), ec);
if (!ec) {
return bytes_read;
}
else if (ec == boost::asio::error::eof) {
return 0;
}
else {
return -1;
}
}
template <typename SyncWriteStream>
class AsioOutputStream : public CopyingOutputStream {
public:
AsioOutputStream(SyncWriteStream& sock);
bool Write(const void* buffer, int size);
private:
SyncWriteStream& m_Socket;
};
template <typename SyncWriteStream>
AsioOutputStream<SyncWriteStream>::AsioOutputStream(SyncWriteStream& sock) :
m_Socket(sock) {}
template <typename SyncWriteStream>
bool
AsioOutputStream<SyncWriteStream>::Write(const void* buffer, int size)
{
boost::system::error_code ec;
m_Socket.write_some(boost::asio::buffer(buffer, size), ec);
return !ec;
}
答案 1 :(得分:6)
我建议使用gRPC。它支持“流”请求,其中客户端和服务器可以随时间在任一方向上发送多个消息,作为单个逻辑请求的一部分,这应该适合您的需要。使用gRPC为您提供了大量的细节设置,您需要有大量的文档和教程,内置TLS加密,您可以使用跨语言支持,可以轻松添加新类型的请求和并行溪流等。