在 Boost.Asio 中同时使用 SSL 套接字和非 SSL 套接字?

Posted

技术标签:

【中文标题】在 Boost.Asio 中同时使用 SSL 套接字和非 SSL 套接字?【英文标题】:Using SSL sockets and non-SSL sockets simultaneously in Boost.Asio? 【发布时间】: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 函数的用途。

【问题讨论】:

【参考方案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;

【讨论】:

以上是关于在 Boost.Asio 中同时使用 SSL 套接字和非 SSL 套接字?的主要内容,如果未能解决你的问题,请参考以下文章

在使用Boost :: asio之前从套接字读取之后是否可以执行async_handshake?

boost asio - 来自一个线程的 SSL async_read 和 async_write

boost::asio 扩展 TCP 套接字

使用 boost::asio 获取 UDP 套接字远程地址

我们可以用 boost.asio 创建未命名的套接字来模拟匿名管道吗?

如何在 Boost.ASIO 中分配已连接的本机套接字类型 (TCP)