【问题标题】:Writing a simple C++ protobuf streaming client/server编写一个简单的 C++ protobuf 流式客户端/服务器
【发布时间】:2016-10-23 08:08:37
【问题描述】:

我想使用 protobuf 在客户端和服务器之间来回发送消息。就我而言,我想从服务器向客户端发送任意数量的 protobuf 消息。如何在 C++ 中快速构建它?

注意:我在stackoverflow 上汇集了一个非常有用的Kenton Varda answerFulkerson answer 之后,连同我的答案一起写了这个问题。其他人也提出了类似的问题并遇到了类似的障碍 - 请参阅 hereherehere

我是 protobuf 和 asio 的新手,因此请随时纠正/提出改进建议,或提供您自己的答案。

【问题讨论】:

    标签: c++ client-server boost-asio protocol-buffers


    【解决方案1】:

    首先,C++ protobuf API 缺乏对通过单个流/连接发送多个 protobuf 消息的内置支持。 Java API 有它,但它还没有被添加到 C++ 版本中。 Kenton Varda(protobuf v2 的创建者)很高兴发布了C++ version。因此,您需要该代码来支持单个连接上的多条消息。

    然后,您可以使用 boost::asio 创建您的客户端/服务器。 不要尝试使用 asio 提供的 istream/ostream 风格接口;包装它并创建 protobuf 所需的流类型(ZeroCopyInputStream/ZeroCopyOutputStream)更容易,但它不起作用。我不完全明白为什么,但 Fulkerson 的 this answer 谈到了尝试这样做的脆弱性。它还提供了示例代码以将原始套接字适应我们需要的类型。

    将所有这些与基本的 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;
    }
    

    【讨论】:

    【解决方案2】:

    我建议使用 gRPC。它支持“流式传输”请求,其中客户端和服务器可以随着时间的推移向任一方向发送多个消息,作为单个逻辑请求的一部分,这应该适合您的需求。使用 gRPC 可以为您处理很多细节设置,您可以遵循大量文档和教程,内置 TLS 加密,您有跨语言支持,您可以轻松添加新类型的请求和并行流等。

    【讨论】:

    • 您好 Kenton,在 gRPC 方面需要您的帮助。我希望了解您所说的任一方向的多条消息是什么意思,这条消息是在使用线程产生的不同连接上串行发送还是并行发送?
    • @VinayShukla 在 gRPC 文档中查找“流”。抱歉,我没有更多信息,我自己没有使用过。
    • 感谢您的帮助,如果您能对我的这个问题发表评论,我将不胜感激。 stackoverflow.com/questions/49852761/…
    猜你喜欢
    • 2010-10-31
    • 2013-07-03
    • 1970-01-01
    • 2015-12-29
    • 1970-01-01
    • 2013-03-17
    • 2010-10-26
    • 1970-01-01
    • 2020-06-16
    相关资源
    最近更新 更多