打算基于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_base
,socket_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,不同的系统会是不同的实现,但是保持统一的对外接口,大概如下图:
我们从最底下向上看,在每个操作系统底层其实有两个部分,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.