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

C++ 在Boost.Asio中同时使用SSL套接字和非SSL套接字?,c++,boost-asio,C++,Boost Asio,我正在将一个库转换为Boost.Asio(到目前为止它工作得很好),但是我遇到了一些设计决策的绊脚石 Asio提供了对SSL的支持,但是套接字必须使用Boost::Asio::SSL::stream类型。我的库可以选择连接到SSL服务器或正常连接,因此我创建了一个包含两个套接字的类,如下所示: class client : public boost::enable_shared_from_this<client> { public: client(boost::asio::i

我正在将一个库转换为Boost.Asio(到目前为止它工作得很好),但是我遇到了一些设计决策的绊脚石

Asio提供了对SSL的支持,但是套接字必须使用
Boost::Asio::SSL::stream
类型。我的库可以选择连接到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_;
};
class客户端:public boost::从\u中启用\u共享\u
{
公众:
客户端(boost::asio::io_服务和io_服务,boost::asio::ssl::上下文和上下文):套接字(io_服务),secureSocket(io_服务,上下文){}
私人:
boost::asio::ip::tcp::socket;
boost::asio::ssl::streamsecuresocket;
};
其中有一组处理程序引用了
socket\uuu
。(例如,我在几个地方有
socket\uu.is\u open()
,对于另一个套接字,它需要成为
secureSocket\uu.lower\u layer().is\u open()

有人能建议最好的方法吗?我不希望仅仅为了这个目的而创建一个单独的类,因为这意味着要复制大量代码


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

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

if ( sslEnabled )
    boost::asio::async_write( secureSocket_ );
} else {
    boost::asio::async_write( secureSocket_.lowest_layer() );
}
如果有很多
if/else
语句,它很快就会变得一团糟。您还可以创建一个抽象类(伪代码-过于简化)


然后创建一个派生类
SecureSocket
,以在
SecureSocket\u
上操作,而不是在
socket\u
上操作。我不认为它会重复很多代码,而且它可能比
if/else
更干净,无论何时你需要
async\u读取
async\u写入
我回答这个问题已经晚了,但我希望这能帮助其他人。山姆的回答包含了一个想法的萌芽,但在我看来还不够

这个想法来自于asio在流中封装SSL套接字的观察。这个解决方案所做的就是类似地包装非SSL套接字

SSL和非SSL套接字之间具有统一外部接口的理想结果是使用三个类完成的。一个是基础,它有效地定义了接口:

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();
    }
};
每次调用asio函数时,请使用getSocketForAsio(),而不是传递对套接字对象的引用。例如:

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));
请注意,套接字存储为指针。我想不出还有什么办法可以隐藏多态性


惩罚(我认为不太好)是用于获取非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_;
};
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();
    }
};

typedef boost::asio::buffered_stream Socket\t

当然,问题是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...
}
模板
void dostuff with opensocket(SocketType socket){
boost::asio::write(套接字,…);
boost::asio::read(套接字,…);
boost::asio::读取(套接字,…);
//等等。。。
}
此函数将与普通tcp::sockets和安全SSL sockets一起使用:

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 ...
boost::asio::ip::tcp::socket;
//插座正常打开。。。
DOSTUFWITHOPENSOCKET(插座);//作品
boost::asio::ssl::streamsecuresocket;
//secureSocket_uu正常打开(包括握手)。。。
DOSTUFWITHOPENSOCKET(安全插座);//也可以使用(不同的)隐式实例化!
//完成后关闭ssl套接字。。。

第一种方法是我最终要做的,但我确实喜欢抽象类的想法。我来看看-谢谢。好像
Socket\u t
typedef
不见了。另外,你能解释一下为什么这样做吗,也就是说,
next_layer()
对每种类型的套接字到底做了什么?@yhager,我做这项工作的方式是复制SSL套接字从非SSL套接字派生的方式。Asio通过将SSL套接字包装在一个容器中来创建一个SSL套接字,该容器提供一些附加功能,并通过向某些活动添加SSL特性来拦截这些活动。使用这种SSL套接字的代码需要使用next_layer()来访问SSL和非SSL套接字共有的某些活动的底层套接字。我的建议是将套接字包装成一种可以被当作SSL套接字处理的方式,但不实现任何与SSL相关的活动。所有这些都不能正常工作。首先,因为您无法对ssl::stream的底层ip::tcp::socket执行原始读写操作。其次,它不能用于无堆栈或有堆栈的协同路由,因为您不支持通过async_结果机制定制返回值。使用此机制实例化
SslSocket
时,
boost::asio::async\u write
将尝试写入底层套接字,这会导致服务器上出现严重错误,如“SSL例程:SSL3\u GET\u记录:错误的版本号”(使用Node.JS服务器测试)。你们可能都是对的。当时我还在试验这种技术。我还没有发现它的局限性。