为什么没有asio::ssl::iostream?(以及如何实施)

Why is there no asio::ssl::iostream? (and how to implement it)

本文关键字:何实施 iostream asio ssl 为什么      更新时间:2023-10-16

我目前正在探索Asio库,并拥有用于常规TCP连接的工作代码。我使用了asio::ip::tcp::iostream对象,因为我想传输的东西已经可以从iostreams序列化/反序列化了,所以这真的很方便,对我来说效果很好

然后我尝试切换到SSL连接,这时一切都变得疯狂了。显然,没有内置的支持来获得与所有其他协议支持的安全连接相同的iostream接口。从设计的角度来看,这真的让我很困惑。有什么原因造成这种情况吗?

我知道"如何创建boost ssl iostream?"中的讨论?它以使用boost提供iostream功能的包装器类结束。除此之外,根据一条评论,该实现存在缺陷,这也没有提供与其他协议(basic_socket_iostream)相同的接口,后者也允许例如设置过期时间和关闭连接。(我也在非boost版本中使用asio,如果可能的话,我希望避免添加boost作为额外的依赖项)。

所以,我想我的问题是:

  1. 要获得SSL连接的basic_socket_ostream,我到底需要实现什么?我认为这将是asio::basic_streambufasio::basic_socket_streambuf的派生,但不知何故,我不知道它们是如何工作的,需要进行调整。。只是有一堆奇怪的指针移动和缓冲区分配,对我来说,文档不清楚什么时候会实现什么
  2. 为什么这一点一开始就不存在?让这一个协议的行为与任何其他协议完全不同似乎是非常不合理的,因此需要进行重大重构来更改基于tcp::iostream的项目以支持安全连接

好吧,我遇到的问题是ssl::流实际上两者都不起作用:我没有提供套接字,但它也没有提供与其他协议兼容的流接口,是的,从这个意义上说,它的行为与其他协议非常不同(没有明显的原因)

我认为流的行为与其他协议没有任何不同(请参阅https://www.boost.org/doc/libs/1_66_0/doc/html/boost_asio/overview/core/streams.html):

流、短读取和短写入

Boost中的许多I/O对象。Asio是以流为导向的。这意味着:

没有消息边界。正在传输的数据是一个连续的字节序列
读取或写入操作传输的字节数可能少于请求的字节数。这被称为短读或短写
提供面向流I/O的对象为以下一个或多个类型要求建模:

  • SyncReadStream,其中使用名为read_some()的成员函数执行同步读取操作
  • AsyncReadStream,其中使用名为async_read_some()的成员函数执行异步读取操作
  • SyncWriteStream,其中使用名为write_some()的成员函数执行同步写入操作
  • AsyncWriteStream,其中使用名为async_write_some()的成员函数执行同步写入操作

面向流的I/O对象的示例包括ip::tcp::socketssl::stream<>posix::stream_descriptorwindows::stream_handle等。

也许混淆的地方在于您将其与iostream接口进行比较,后者根本不是同一个概念(它来自标准库)。

对于如何为ssl流制作iostream兼容的流包装器的问题,如果不更多地查阅文档并使用编译器,我就无法找到答案,而我目前还没有。

我认为这里的库还有改进的空间。如果你阅读ip::tcp::iostream类(即basic_socket_iostream<ip::tcp>),你会发现它有两个基类:

  • private detail::socket_iostream_base<ip::tcp>
  • public std::basic_iostream<char>

前者包含一个basic_socket_streambuf<ip::tcp>(std::streambufbasic_socket<ip::tcp>的派生类),其地址在构造时传递给后者。

在大多数情况下,basic_socket_streambuf<ip::tcp>通过其basic_socket<ip::tcp>基类执行实际的套接字操作。然而,有一个connect_to_endpoints()成员函数跳过抽象,直接在socket().native_handle()上从detail::socket_ops命名空间调用几个低级函数。(这似乎是在Gitcommitb60e92b13e中引入的。)这些函数只能在TCP套接字上工作,即使该类是任何协议的模板。

在我发现这个问题之前,我将SSL支持集成为iostream/streambuf的计划是提供ssl协议类和basic_socket<ssl>模板专用化,以封装现有的ssl::contextssl::stream<ip::tcp::socket>类。类似这样的东西(不会编译):

#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/basic_socket.hpp>
#include <boost/asio/ssl.hpp>
namespace boost {
namespace asio {
namespace ip {
class ssl
: public tcp  // for reuse (I'm lazy!)
{
public:
typedef basic_socket_iostream<ssl> iostream;
// more things as needed ...
};
}  // namespace ip
template <>
class basic_socket<ip::ssl>
{
class SslContext
{
ssl::context ctx;
public:
SslContext() : ctx(ssl::context::sslv23_client)
{
ctx.set_options(ssl::context::default_workarounds);
ctx.set_default_verify_paths();
}
ssl::context & context() { return ctx; }
} sslContext;
ssl::stream<ip::tcp::socket> sslSocket;
public:
explicit basic_socket(const executor & ex)
: sslSocket(ex, sslContext.context())
{}
executor get_executor() noexcept
{
return sslSocket.lowest_layer().get_executor();
}
void connect(const ip::tcp::endpoint & endpoint_)
{
sslSocket.next_layer().connect(endpoint_);
sslSocket.lowest_layer().set_option(ip::tcp::no_delay(true));
sslSocket.set_verify_mode(ssl::verify_peer);
sslSocket.set_verify_callback(
ssl::rfc2818_verification("TODO: pass the domain here through the stream/streambuf somehow"));
sslSocket.handshake(ssl::stream<ip::tcp::socket>::client);
}
void close()
{
sslSocket.shutdown();
sslSocket.next_layer().close();
}
};
}  // namespace asio
}  // namespace boost

但由于设计问题,我还必须专门化basic_socket_streambuf<ip::ssl>,以避免使用detail::socket_ops例程。(我还应该避免将ssl协议类注入boost::asio::ip命名空间,但这是一个次要问题。)

我没有在这方面花太多时间,但这似乎是可行的。首先修复basic_socket_streambuf<>::connect_to_endpoints()应该会有很大帮助。