跳至正文

[Asio] 学习笔记1. 初识asio和tcp

打算基于asio写多种序列化库的测评,在底层用同一个asio构造函数的方式,然后上层测试脚本里切换序列化的实现。
但是最开始按着demo写逻辑就出现了问题,我想先纯面向过程,就没像demo里写一个connection类,然后就探究到一直会闪退的问题。

最后定位到时ip::tcp::socket析构的时候会断开连接。

socket的析构会断开连接

socket的大概是这样的结构:

typedef basic_stream_socket<tcp> socket;
template <typename Protocol, typename Executor>
class basic_stream_socket
  : public basic_socket<Protocol, Executor>;
{};

template <typename Protocol, typename Executor>
class basic_socket: public socket_base
{
// ....
  ~basic_socket()
  {
  }

#if defined(BOOST_ASIO_WINDOWS_RUNTIME)
  detail::io_object_impl<
    detail::null_socket_service<Protocol>, Executor> impl_;
#elif defined(BOOST_ASIO_HAS_IOCP)
  detail::io_object_impl<
    detail::win_iocp_socket_service<Protocol>, Executor> impl_;
#elif defined(BOOST_ASIO_HAS_IO_URING_AS_DEFAULT)
  detail::io_object_impl<
    detail::io_uring_socket_service<Protocol>, Executor> impl_;
#else
  detail::io_object_impl<
    detail::reactive_socket_service<Protocol>, Executor> impl_;
#endif
};

大概是这样的关系,虽然basic_socket, basic_stream_socket和tcp都没有在析构函数里做逻辑。但是实际实现操作系统连接句柄的impl_的析构函数里有做逻辑的,而且实现的方式还挺巧妙。
Windows的io_uring_socket_service和Linux的reactive_socket_service都没有在析构里做逻辑,这一部分是在detail::io_object_impl里做的。

template <typename IoObjectService,
    typename Executor = io_context::executor_type>
class io_object_impl
{
public:
  typedef IoObjectService service_type;
  // Construct an I/O object using an executor.
  explicit io_object_impl(int, const executor_type& ex)
    : service_(&boost::asio::use_service<IoObjectService>(
          io_object_impl::get_context(ex))),
      executor_(ex)
  {
    service_->construct(implementation_);
  }

  // Destructor.
  ~io_object_impl()
  {
    service_->destroy(implementation_);
  }

private:
  // The service associated with the I/O object.
  service_type* service_;
};

也就是io_object_impl本身实现的是IoObjectService的生命周期管理,对操作系统的io对象进行统一的封装,给上层提供统一的接口。然后通过模板类可以切换实际实现的方式。

同时如小标题所示,ip::tcp::socket这类对象在析构的时候会断开连接,所以在asio实际使用过程中,一定要抓住socket的生命周期。

ip::tcp::socket

顺便深入看一下tcp的连接实现细节,我们以Linux的视角看一下实现细节。

socket_base

我们从最底层看起,basic_socket继承自socket_basesocket_base是一个定义了全双工半双工状态,当前等待状态的抽象类。它的抽象体现在析构函数实现在protetecd里,只有子类对象可以被析构。

class socket_base
{
public:
  /// Different ways a socket may be shutdown.
  enum shutdown_type
  {
#if defined(GENERATING_DOCUMENTATION)
    /// Shutdown the receive side of the socket.
    shutdown_receive = implementation_defined,

    /// Shutdown the send side of the socket.
    shutdown_send = implementation_defined,

    /// Shutdown both send and receive on the socket.
    shutdown_both = implementation_defined
#else
    shutdown_receive = BOOST_ASIO_OS_DEF(SHUT_RD),
    shutdown_send = BOOST_ASIO_OS_DEF(SHUT_WR),
    shutdown_both = BOOST_ASIO_OS_DEF(SHUT_RDWR)
#endif
  };
  // ....
protected:
  /// Protected destructor to prevent deletion through this type.
  ~socket_base()
  {
  }
};

basic_socket

template <typename Protocol, typename Executor>
class basic_socket
  : public socket_base
{
  detail::io_object_impl<
    detail::reactive_socket_service<Protocol>, Executor> impl_;
};

这个对象是对套接字做的一个高级抽象,类似于Linux里万物都是socket,可以read and write,具体的上层协议本身是定义在Protocol里面,Protocol包含协议本身以及endpoint,也就是地址。

例如TCP的endpoint就是IP和地址,感觉如果未来KCP或者其他协议,可以直接定义一个新的endpoint类,增加多个channel就能实现很多东西,没必要用多个实际上的操作系统套接口?
这些实际操作系统的实现又落地在impl_里,impl_实际上是一个io_object,不同的系统会是不同的实现,但是保持统一的对外接口,大概如下图:
file

我们从最底下向上看,在每个操作系统底层其实有两个部分,service_implement_,他们互为一组向上和向下的关系抽象,implement_service_类里的一个struct,主要包含该操作系统API下的资源细节,通常包含套接口句柄,上层协议类型,以及其他的一些数据。
例如在Linux下,所有的socket都是用int类型的一个句柄,无论是Tcp还是文件还是Udp协议都是同一个句柄。所以implement_里存了套接口以及具体的Tcp还是Udp协议。
service_则是对操作系统的API提供的一层service抽象,将不同的操作系统的IO接口提供成统一的API,这里实现了创建、连接、收发消息和关闭连接。
而整个service_会被basic_socket用作操作系统无关的io对象的实现,而基于这些,basic_socket向上层提供了连接和异步的基本能力,创建、管理、异步等待等…
再进一步basic_stream_socket则是向上层提供了进一步的读写能力。

本次就先读到这里吧。

End.

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

目录