boost.asio代码学习

本文详细探讨了Boost.Asio库的内部实现原理,包括placement new内存重用、hash_map的具体实现方式、async_send/async_receive如何处理buffers序列、reactive_socket_service如何通过reactor模拟proactor行为等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、placement new进行内存重用

boost/asio/detail/reactive_socket_service_base.hpp中,async_receive需要创建一个reactive_socket_recv_op,该对象不是直接从系统new出来的,而是先查找空闲列表,找到一块能够放得下该op对象的内存(boost_asio_handler_alloc_helper::allocate),然后对该内存进行placement new构造对象。

// Allocate and construct an operation to wrap the handler.
    typedef reactive_socket_recv_op<MutableBufferSequence, Handler> op;
    typename op::ptr p = { boost::asio::detail::addressof(handler),
      boost_asio_handler_alloc_helpers::allocate(
        sizeof(op), handler), 0 };
    p.p = new (p.v) op(impl.socket_, impl.state_, buffers, flags, handler);

2、hash_map的实现

在boost/detail/hash_map.hpp中,利用std::list实现了hash_map。作者并没有为每个bucket建一个容器来存放拉链的值,而是只用了一个std::list用于存放所有的值,每个bucket存放的是该bucket中元素在list中的起始与终止iterator。hash_map在select_reactor.ipp中被用到,用于存储socket到对应op的映射。

3、async_send/async_receive中的传入的buffers

 template <typename ConstBufferSequence, typename Handler>
  void async_send(base_implementation_type& impl,
      const ConstBufferSequence& buffers,
      socket_base::message_flags flags, Handler& handler)

async_send/async_receive是模板方法,发送的内容是一个buffers的序列,最终底层调用

ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

或者

int WSAAPI WSASend (

  SOCKET s,

  LPWSABUF lpBuffers,

  DWORD dwBufferCount,

  LPDWORD lpNumberOfBytesSent,

  int iFlags,

  LPWSAOVERLAPPED lpOverlapped,

  LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine

  );

这样多个buffer不用先合成一个大的buffer,需是直接交给底层发送,类似于writev/readv。

这里的BufferSequence可以是简单的const_buffers_1/mutable_buffers_1,底层只包含一个const_buffer或mutable_buffer。用boost.asio.buffer对char*,len包装生成const_buffers_1或mutable_buffers_1。BufferSequence也可以是std::vector<const_buffer> 或是boost::array<mutable_buffer, 3>等。所有这些BufferSequence需要支持begin()与end()。

由于 typedef reactive_socket_send_op<ConstBufferSequence, Handler> op也是模板定义,不同的ConstBufferSequence会具现化不同的reactive_socket_sendop,在reactive_socket_sendop中会存放ConstBufferSequence,当socket可写时,回调reactive_socket_send_op_base的perform。

  static bool do_perform(reactor_op* base)
  {
    reactive_socket_send_op_base* o(
        static_cast<reactive_socket_send_op_base*>(base));

    buffer_sequence_adapter<boost::asio::const_buffer,
        ConstBufferSequence> bufs(o->buffers_);

    return socket_ops::non_blocking_send(o->socket_,
          bufs.buffers(), bufs.count(), o->flags_,
          o->ec_, o->bytes_transferred_);
  }

由模板类buffer_sequence_adapter使用偏特化机制,将BufferSequence中每个Buffer的指针、长度信息写入LPWSABUF或是struct iovec*中,再由non_blocking_send调用WSASend或sendmsg发送数据。

4、  win_iocp_io_service        --------  task_io_service

       win_iocp_socket_service --------  reactive_socket_service   

       其中reactive_socket_service使用reactor来模拟proactor效果。

       (reactor主要有epoll_reactor/select_reactor/dev_poll_reactor/kqueue_reactor)

5、asio中的超时

      async_send/async_receive中都不能带超时,只能用另外一个deadline_timer来实现,这样造成超时的代码与发送接收的回调代码只能分开来写,很不方便。 实际上,在reactor上加上超时还是比较容易的,但可能是windows的iocp却没有什么好的办法,我们不能在iocp上面自由地取消一个操作,而只能取消一个socket上的所有操作或是关闭套节字,所以只能取交集了。

    windows下的超时使用CreateWaitableTimer/SetWaitableTimer并用独立线程来实现超时机制。(是否可以用RegisterWaitForSingleObject与UnregisterWaitEx函数来实现或者用timeSetEvent/timeKillEvent来实现?)

6、epoll_reactor中的per_descriptor_data

每个套节字会在epoll_reactor中注册,由allocate_descriptor_state分配一个per_descriptor_data,存放在object_pool<descriptor_state> registered_descriptors_中; 而object_pool中包含两个list,live_list_一个用于存放当前已分配的descriptor_state,free_list_用于存放释放的descriptor_state,实现循环利用。

int epoll_reactor::register_descriptor(socket_type descriptor,
    epoll_reactor::per_descriptor_data& descriptor_data)
{
  descriptor_data = allocate_descriptor_state();

  {
    mutex::scoped_lock descriptor_lock(descriptor_data->mutex_);

    descriptor_data->reactor_ = this;
    descriptor_data->descriptor_ = descriptor;
    descriptor_data->shutdown_ = false;
  }

  epoll_event ev = { 0, { 0 } };
  ev.events = EPOLLIN | EPOLLERR | EPOLLHUP | EPOLLPRI | EPOLLET;
  descriptor_data->registered_events_ = ev.events;
  ev.data.ptr = descriptor_data;
  int result = epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, descriptor, &ev);
  if (result != 0)
    return errno;

  return 0;
}
epoll_reactor::descriptor_state* epoll_reactor::allocate_descriptor_state()
{
  mutex::scoped_lock descriptors_lock(registered_descriptors_mutex_);
  return registered_descriptors_.alloc();
}
 epoll_event ev = { 0, { 0 } };
          ev.events = descriptor_data->registered_events_ | EPOLLOUT;
          ev.data.ptr = descriptor_data;
          if (epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, descriptor, &ev) == 0)
          {
            descriptor_data->registered_events_ |= ev.events;
          }

当epoll_wait返回时,直接将ev.data.ptr作为descriptor_data,然后进行处理。 这在单线程下没什么问题。但如果在多线程环境下,epoll_wait期间,该descriptor_data可能先被被释放(如调用tcp.socket.close)存入free_list_,然后再被另一个tcp.socket分配,这样会造成另一个socket产生错误的回调。这应该是个bug吧。。。

7、boost.asio.write与async_write_some

boost.asio.write循环调用async_write_some实现数据发送. asio/impl/write.hpp中的write_op的代码用于回调,operator()中第一次是start=1,以后的start都是0。这里的代码有点奇怪,switch里的default在for循环中。

    void operator()(const boost::system::error_code& ec,
        std::size_t bytes_transferred, int start = 0)
    {
      switch (start_ = start)
      {
        case 1:
        buffers_.prepare(this->check_for_completion(ec, total_transferred_));
        for (;;)
        {
          stream_.async_write_some(buffers_,
              BOOST_ASIO_MOVE_CAST(write_op)(*this));
          return; default:
          total_transferred_ += bytes_transferred;
          buffers_.consume(bytes_transferred);
          buffers_.prepare(this->check_for_completion(ec, total_transferred_));
          if ((!ec && bytes_transferred == 0)
              || buffers_.begin() == buffers_.end())
            break;
        }

如果是自己写的话,估计是这样(boost的代码中可以节省一行async_write_some代码,大牛的思想果然与众不同):

    void operator()(const boost::system::error_code& ec,
        std::size_t bytes_transferred, int start = 0)
    {
      switch (start_ = start)
      {
	    case 1:
        	buffers_.prepare(this->check_for_completion(ec, total_transferred_));
        	stream_.async_write_some(buffers_,BOOST_ASIO_MOVE_CAST(write_op)(*this));
          	return; 
	    default:
        	total_transferred_ += bytes_transferred;
        	buffers_.consume(bytes_transferred);
        	buffers_.prepare(this->check_for_completion(ec, total_transferred_));
        	total_transferred_ += bytes_transferred;
          	if ((!ec && bytes_transferred == 0)|| buffers_.begin() == buffers_.end())
			    break;
        	stream_.async_write_some(buffers_,BOOST_ASIO_MOVE_CAST(write_op)(*this));
          	return; 
        }
    }

[to be continue]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值