【问题标题】:Using SSL sockets and non-SSL sockets simultaneously in Boost.Asio?在 Boost.Asio 中同时使用 SSL 套接字和非 SSL 套接字?
【发布时间】:2011-06-10 20:17:00
【问题描述】:

我正在将一个库转换为 Boost.Asio(到目前为止运行良好),但在设计决策方面遇到了一些障碍。

Boost.Asio 提供对 SSL 的支持,但必须为套接字使用 boost::asio::ssl::stream<boost::asio::ip::tcp::socket> 类型。我的库可以选择连接到 SSL 服务器或正常连接,因此我创建了一个带有两个套接字的类,如下所示:

class client : public boost::enable_shared_from_this<client>
{
public:
    client(boost::asio::io_service & io_service, boost::asio::ssl::context & context) : socket_(io_service), secureSocket_(io_service, context) {}
private:
    boost::asio::ip::tcp::socket socket_;
    boost::asio::ssl::stream<boost::asio::ip::tcp::socket> secureSocket_;
};

其中有一堆引用socket_ 的处理程序。 (例如,我在几个地方有socket_.is_open(),对于另一个套接字,它需要变成secureSocket_.lowest_layer().is_open()。)

任何人都可以提出解决此问题的最佳方法吗?我宁愿不为此目的创建一个单独的类,因为那将意味着复制大量代码。

编辑:我重新表述了我原来的问题,因为我误解了 OpenSSL 函数的用途。

【问题讨论】:

    标签: c++ boost-asio


    【解决方案1】:

    我回答这个问题比较晚,但我希望这对其他人有帮助。 Sam 的回答包含了一个想法的萌芽,但在我看来还远远不够。

    这个想法源于观察到 asio 将 SSL 套接字包装在流中。这个解决方案所做的只是它类似地包装了非 SSL 套接字。

    在 SSL 和非 SSL 套接字之间拥有统一的外部接口的预期结果是通过三个类完成的。一、base,有效定义了接口:

    class Socket {
    public:
        virtual boost::asio::ip::tcp::socket &getSocketForAsio() = 0;
    
        static Socket* create(boost::asio::io_service& iIoService, boost::asio::ssl::context *ipSslContext) {
            // Obviously this has to be in a separate source file since it makes reference to subclasses
            if (ipSslContext == nullptr) {
                return new NonSslSocket(iIoService);
            }
           return new SslSocket(iIoService, *ipSslContext);
        }
    
        size_t _read(void *ipData, size_t iLength) {
            return boost::asio::read(getSocketForAsio(), boost::asio::buffer(ipData, iLength));
        }
        size_t _write(const void *ipData, size_t iLength) {
            return boost::asio::write(getSocketForAsio(), boost::asio::buffer(ipData, iLength));
        }
    };
    

    两个子类包装 SSL 和非 SSL 套接字。

    typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> SslSocket_t;
    class SslSocket: public Socket, private SslSocket_t {
    public:
        SslSocket(boost::asio::io_service& iIoService, boost::asio::ssl::context &iSslContext) :
            SslSocket_t(iIoService, iSslContext) {
        }
    
    private:
        boost::asio::ip::tcp::socket &getSocketForAsio() {
            return next_layer();
        }
    };
    

    class NonSslSocket: public Socket, private Socket_t {
    public:
        NonSslSocket(boost::asio::io_service& iIoService) :
                Socket_t(iIoService) {
        }
    
    private:
        boost::asio::ip::tcp::socket &getSocketForAsio() {
            return next_layer();
        }
    };
    

    每次调用 asio 函数时都使用 getSocketForAsio(),而不是传递对 Socket 对象的引用。例如:

    boost::asio::async_read(pSocket->getSocketForAsio(),
                boost::asio::buffer(&buffer, sizeof(buffer)),
                boost::bind(&Connection::handleRead,
                        shared_from_this(),
                        boost::asio::placeholders::error,
                        boost::asio::placeholders::bytes_transferred));
    

    注意 Socket 是作为指针存储的。我想不出还有什么办法可以隐藏多态性。

    惩罚(我认为不太好)是用于获取非 SSL 套接字的额外间接级别。

    【讨论】:

    • 似乎缺少Socket_ttypedef。另外,您能解释一下为什么会这样吗,即next_layer() 对每种类型的套接字究竟做了什么?
    • @yhager,我完成这项工作的方式是复制 SSL 套接字从非 SSL 套接字派生的方式。 Asio 通过将 SSL 套接字包装在一个容器中来创建一个 SSL 套接字,该容器提供一些额外的功能,并通过向它们添加 SSL 风格来拦截一些活动。使用这种 SSL 套接字的代码将需要使用 next_layer() 来访问底层套接字,以进行 SSL 和非 SSL 套接字共有的一些活动。我的所有建议都是以这样一种方式包装套接字,使其可以被视为 SSL 套接字,但不实施任何与 SSL 相关的活动。
    • 这些都不能正常工作。首先是因为您无法对 ssl::stream 的底层 ip::tcp::socket 进行原始读写。其次,它不适用于无堆栈或堆栈式协程,因为您不支持通过 async_result 机制自定义返回值。
    • 我可以确认维尼所说的话。当使用此机制实例化SslSocket 时,boost::asio::async_write 将尝试写入底层套接字,这会导致服务器上出现诸如“SSL 例程:SSL3_GET_RECORD:错误版本号”之类的严重错误(使用 Node.JS 服务器测试)。
    • 你们俩可能都是对的。当时我还在试验这种技术。我还没有发现它的局限性。
    【解决方案2】:

    有几种方法可以做到这一点。过去,我做过类似的事情

    if ( sslEnabled )
        boost::asio::async_write( secureSocket_ );
    } else {
        boost::asio::async_write( secureSocket_.lowest_layer() );
    }
    

    大量if/else 语句很快就会变得混乱。您还可以创建一个抽象类(伪代码 - 过于简单)

    class Socket
    {
        public:
           virtual void connect( ... );
           virtual void accept( ... );
           virtual void async_write( ... );
           virtual void async_read( ... );
        private:
            boost::asio::ip::tcp::socket socket_;
    };
    

    然后创建一个派生类SecureSocket 来操作secureSocket_ 而不是socket_。我不认为它会重复很多代码,而且当你需要async_readasync_write 时,它可能比if/else 更干净。

    【讨论】:

    • 第一种方法是我最终做的,但我确实喜欢抽象类的想法。我会看看它 - 谢谢。
    【解决方案3】:

    问题当然是 tcp::socket 和 ssl “socket”不共享任何共同的祖先。但是,一旦套接字打开,大多数使用套接字的函数都共享完全相同的语法。因此,最干净的解决方案是使用模板。

    template <typename SocketType>
    void doStuffWithOpenSocket(SocketType socket) {
       boost::asio::write(socket, ...);
       boost::asio::read(socket, ...);
       boost::asio::read_until(socket, ...);
       // etc...
    }
    

    这个函数可以和普通的 tcp::sockets 一起工作,也可以使用安全的 SSL 套接字:

    boost::asio::ip::tcp::socket socket_;
    // socket_ opened normally ...
    doStuffWithOpenSocket<boost::asio::ip::tcp::socket>(socket_); // works!
    
    boost::asio::ssl::stream<boost::asio::ip::tcp::socket> secureSocket_;
    // secureSocket_ opened normally (including handshake) ...
    doStuffWithOpenSocket(secureSocket_); // also works, with (different) implicit instantiation!
    // shutdown the ssl socket when done ...
    

    【讨论】:

      【解决方案4】:

      它会像这样编译:

      typedef boost::asio::buffered_stream&lt;boost::asio::ip::tcp::socket&gt; Socket_t;

      【讨论】:

        猜你喜欢
        • 2010-12-16
        • 2020-02-18
        • 1970-01-01
        • 2013-09-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-04-11
        • 1970-01-01
        相关资源
        最近更新 更多