在 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_t
的typedef
。另外,您能解释一下为什么会这样吗,即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_read
或async_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<boost::asio::ip::tcp::socket> Socket_t;
【讨论】:
以上是关于在 Boost.Asio 中同时使用 SSL 套接字和非 SSL 套接字?的主要内容,如果未能解决你的问题,请参考以下文章
在使用Boost :: asio之前从套接字读取之后是否可以执行async_handshake?
boost asio - 来自一个线程的 SSL async_read 和 async_write