Существуют ли эквиваленты С++ для функций протокола ввода-вывода протокола Buffer в Java?
Я пытаюсь читать/записывать несколько сообщений Protocol Buffers из файлов как на C++, так и на Java. Google предлагает писать префиксы длины перед сообщениями, но по умолчанию этого не существует (что я видел).
Тем не менее, API Java в версии 2.1.0 получил набор функций ввода-вывода с разделителями, которые, очевидно, выполняют эту работу:
parseDelimitedFrom
mergeDelimitedFrom
writeDelimitedTo
Есть ли C++ эквиваленты? И если нет, то какой проводной формат для префиксов размера присоединяет Java API, чтобы я мог проанализировать эти сообщения в C++?
Обновить:
Теперь они существуют в google/protobuf/util/delimited_message_util.h
начиная с версии 3.3.0.
Ответы
Ответ 1
Я немного опаздываю на вечеринку здесь, но приведенные ниже реализации включают в себя некоторые оптимизации, отсутствующие в других ответах, и не будут терпеть неудачу после ввода 64 МБ (хотя он все еще обеспечивает ограничение 64 МБ для каждого отдельного сообщения, а не всего потока).
(Я являюсь автором библиотек protobuf на С++ и Java, но я больше не работаю для Google. Извините, что этот код никогда не попадал в официальную библиотеку. Это было бы так, как если бы оно было.)
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;
}
Ответ 2
Хорошо, поэтому я не смог найти функции С++ на верхнем уровне, реализующие то, что мне нужно, но некоторые функции spelunking через ссылку API Java появились в MessageLite:
void writeDelimitedTo(OutputStream output)
/* Like writeTo(OutputStream), but writes the size of
the message as a varint before writing the data. */
Таким образом, префикс размера Java является (протокольным буфером) varint!
Вооружившись этой информацией, я начал копаться через API С++ и нашел заголовок CodedStream, который имеет следующие значения:
bool CodedInputStream::ReadVarint32(uint32 * value)
void CodedOutputStream::WriteVarint32(uint32 value)
Используя эти возможности, я должен свернуть свои собственные функции на С++, которые выполняют эту работу.
Они должны действительно добавить это в основной API сообщений; он пропускает функциональные возможности, учитывая, что у него есть Java, и так же превосходный protobuf-net С# порт Marc Gravell (через SerializeWithLengthPrefix и DeserializeWithLengthPrefix).
Ответ 3
Я решил ту же проблему, используя CodedOutputStream/ArrayOutputStream, чтобы написать сообщение (с размером) и CodedInputStream/ArrayInputStream для чтения сообщения (с размером).
Например, следующий псевдокод записывает размер сообщения, следующий за сообщением:
const unsigned bufLength = 256;
unsigned char buffer[bufLength];
Message protoMessage;
google::protobuf::io::ArrayOutputStream arrayOutput(buffer, bufLength);
google::protobuf::io::CodedOutputStream codedOutput(&arrayOutput);
codedOutput.WriteLittleEndian32(protoMessage.ByteSize());
protoMessage.SerializeToCodedStream(&codedOutput);
При записи вы также должны проверить, что ваш буфер достаточно велик, чтобы соответствовать сообщению (включая размер). И при чтении вы должны проверить, что ваш буфер содержит целое сообщение (включая размер).
Это определенно было бы удобно, если бы они добавили удобные методы в API С++, аналогичные API Java.
Ответ 4
Здесь вы идете:
#include <google/protobuf/io/zero_copy_stream_impl.h>
#include <google/protobuf/io/coded_stream.h>
using namespace google::protobuf::io;
class FASWriter
{
std::ofstream mFs;
OstreamOutputStream *_OstreamOutputStream;
CodedOutputStream *_CodedOutputStream;
public:
FASWriter(const std::string &file) : mFs(file,std::ios::out | std::ios::binary)
{
assert(mFs.good());
_OstreamOutputStream = new OstreamOutputStream(&mFs);
_CodedOutputStream = new CodedOutputStream(_OstreamOutputStream);
}
inline void operator()(const ::google::protobuf::Message &msg)
{
_CodedOutputStream->WriteVarint32(msg.ByteSize());
if ( !msg.SerializeToCodedStream(_CodedOutputStream) )
std::cout << "SerializeToCodedStream error " << std::endl;
}
~FASWriter()
{
delete _CodedOutputStream;
delete _OstreamOutputStream;
mFs.close();
}
};
class FASReader
{
std::ifstream mFs;
IstreamInputStream *_IstreamInputStream;
CodedInputStream *_CodedInputStream;
public:
FASReader(const std::string &file), mFs(file,std::ios::in | std::ios::binary)
{
assert(mFs.good());
_IstreamInputStream = new IstreamInputStream(&mFs);
_CodedInputStream = new CodedInputStream(_IstreamInputStream);
}
template<class T>
bool ReadNext()
{
T msg;
unsigned __int32 size;
bool ret;
if ( ret = _CodedInputStream->ReadVarint32(&size) )
{
CodedInputStream::Limit msgLimit = _CodedInputStream->PushLimit(size);
if ( ret = msg.ParseFromCodedStream(_CodedInputStream) )
{
_CodedInputStream->PopLimit(msgLimit);
std::cout << mFeed << " FASReader ReadNext: " << msg.DebugString() << std::endl;
}
}
return ret;
}
~FASReader()
{
delete _CodedInputStream;
delete _IstreamInputStream;
mFs.close();
}
};
Ответ 5
Я столкнулся с той же проблемой как на С++, так и на Python.
Для версии С++ я использовал сочетание кода Kenton Varda, размещенное в этом потоке, и код из запроса на pull, который он отправил команде protobuf (поскольку версия, размещенная здесь, не обрабатывает EOF, а тот, который он отправил на github).
#include <google/protobuf/message_lite.h>
#include <google/protobuf/io/zero_copy_stream.h>
#include <google/protobuf/io/coded_stream.h>
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, bool* clean_eof)
{
// 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);
const int start = input.CurrentPosition();
if (clean_eof)
*clean_eof = false;
// Read the size.
uint32_t size;
if (!input.ReadVarint32(&size))
{
if (clean_eof)
*clean_eof = input.CurrentPosition() == start;
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;
}
И вот моя реализация python2:
from google.protobuf.internal import encoder
from google.protobuf.internal import decoder
#I had to implement this because the tools in google.protobuf.internal.decoder
#read from a buffer, not from a file-like objcet
def readRawVarint32(stream):
mask = 0x80 # (1 << 7)
raw_varint32 = []
while 1:
b = stream.read(1)
#eof
if b == "":
break
raw_varint32.append(b)
if not (ord(b) & mask):
#we found a byte starting with a 0, which means it the last byte of this varint
break
return raw_varint32
def writeDelimitedTo(message, stream):
message_str = message.SerializeToString()
delimiter = encoder._VarintBytes(len(message_str))
stream.write(delimiter + message_str)
def readDelimitedFrom(MessageType, stream):
raw_varint32 = readRawVarint32(stream)
message = None
if raw_varint32:
size, _ = decoder._DecodeVarint32(raw_varint32, 0)
data = stream.read(size)
if len(data) < size:
raise Exception("Unexpected end of file")
message = MessageType()
message.ParseFromString(data)
return message
#In place version that takes an already built protobuf object
#In my tests, this is around 20% faster than the other version
#of readDelimitedFrom()
def readDelimitedFrom_inplace(message, stream):
raw_varint32 = readRawVarint32(stream)
if raw_varint32:
size, _ = decoder._DecodeVarint32(raw_varint32, 0)
data = stream.read(size)
if len(data) < size:
raise Exception("Unexpected end of file")
message.ParseFromString(data)
return message
else:
return None
Возможно, это не лучший код, и я уверен, что он может быть реорганизован как честный бит, но, по крайней мере, это должно показать вам один из способов сделать это.
Теперь большая проблема: SLOW.
Даже при использовании Cython-реализации python-protobuf он на порядок медленнее, чем в чистом С++. У меня есть бенчмарк, где я читаю 10-мегапиксельные сообщения из ~ 30 байт из файла. Он занимает ~ 0.9 с на С++ и 35s в python.
Одним из способов сделать это немного быстрее было бы повторить реализацию декодера varint, чтобы заставить его читать из файла и декодировать за один раз, вместо того, чтобы читать из файла, а затем декодировать, как это делает текущий код. (профилирование показывает, что значительное количество времени расходуется в кодировщике/декодере). Но само собой разумеется, что одного недостаточно, чтобы закрыть разрыв между версией python и версией на С++.
Любая идея сделать это быстрее приветствуется:)
Ответ 6
IsteamInputStream очень хрупок для eofs и других ошибок, которые легко возникают при использовании вместе с std:: istream. После этого потоки protobuf полностью повреждены, и все уже используемые данные буфера уничтожены. Существует надлежащая поддержка чтения из традиционных потоков в protobuf.
Внедрите google::protobuf::io::CopyingInputStream
и используйте это вместе с CopyingInputStreamAdapter. Сделайте то же самое для выходных вариантов.
На практике сеанс разбора заканчивается в google::protobuf::io::CopyingInputStream::Read(void* buffer, int size)
, где указан буфер. Единственное, что осталось сделать, это как-то прочитать.
Здесь приведен пример использования с синхронизированными потоками Asio (SyncReadStream/SyncWriteStream):
#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;
}
Использование:
AsioInputStream<boost::asio::ip::tcp::socket> ais(m_Socket); // Where m_Socket is a instance of boost::asio::ip::tcp::socket
CopyingInputStreamAdaptor cis_adp(&ais);
CodedInputStream cis(&cis_adp);
Message protoMessage;
uint32_t msg_size;
/* Read message size */
if(!cis.ReadVarint32(&msg_size)) {
// Handle error
}
/* Make sure not to read beyond limit of message */
CodedInputStream::Limit msg_limit = cis.PushLimit(msg_size);
if(!msg.ParseFromCodedStream(&cis)) {
// Handle error
}
/* Remove limit */
cis.PopLimit(msg_limit);
Ответ 7
Также искали решение для этого. Здесь ядро нашего решения, предполагая, что некоторый код java написал много сообщений MyRecord с writeDelimitedTo
в файл. Откройте файл и цикл, выполнив:
if(someCodedInputStream->ReadVarint32(&bytes)) {
CodedInputStream::Limit msgLimit = someCodedInputStream->PushLimit(bytes);
if(myRecord->ParseFromCodedStream(someCodedInputStream)) {
//do your stuff with the parsed MyRecord instance
} else {
//handle parse error
}
someCodedInputStream->PopLimit(msgLimit);
} else {
//maybe end of file
}
Надеюсь, что это поможет.
Ответ 8
Работа с версией протоколов-буферов objective-c, я столкнулся с этой точной проблемой. При отправке с iOS-клиента на сервер на основе Java, который использует parseDelimitedFrom, который ожидает длину в качестве первого байта, мне сначала нужно было вызвать writeRawByte в CodedOutputStream. Проводя здесь, чтобы надежно помочь другим, которые сталкиваются с этой проблемой. Работая над этой проблемой, можно подумать, что прото-bufs Google будет иметь простой флаг, который сделает это за вас...
Request* request = [rBuild build];
[self sendMessage:request];
}
- (void) sendMessage:(Request *) request {
//** get length
NSData* n = [request data];
uint8_t len = [n length];
PBCodedOutputStream* os = [PBCodedOutputStream streamWithOutputStream:outputStream];
//** prepend it to message, such that Request.parseDelimitedFrom(in) can parse it properly
[os writeRawByte:len];
[request writeToCodedOutputStream:os];
[os flush];
}
Ответ 9
Так как мне не разрешено писать это как комментарий к Kenton Varda, ответьте выше; Я считаю, что есть ошибка в коде, который он опубликовал (а также в других ответах, которые были предоставлены). Следующий код:
...
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);
...
устанавливает неверный предел, потому что он не учитывает размер varint32, который уже был прочитан с ввода. Это может привести к потере/повреждению данных, поскольку дополнительные байты считываются из потока, который может быть частью следующего сообщения. Обычный способ правильной работы - удалить CodedInputStream, используемый для чтения размера и создания нового для чтения полезной нагрузки:
...
uint32_t size;
{
google::protobuf::io::CodedInputStream input(rawInput);
// Read the size.
if (!input.ReadVarint32(&size)) return false;
}
google::protobuf::io::CodedInputStream input(rawInput);
// Tell the stream not to read beyond that size.
google::protobuf::io::CodedInputStream::Limit limit =
input.PushLimit(size);
...
Ответ 10
Вы можете использовать getline для чтения строки из потока, используя указанный разделитель:
istream& getline ( istream& is, string& str, char delim );
(определен в заголовке)