天天看點

boost::asio::tcp

Proactor/Reactor:

 簡單來說就是 Reactor是在epoll等到事件發生時就通知回調函數,然後在回調函數裡面調用read/write進行實際的I/O操作。Proactor 是在epoll等到事件發生時根據具體事件調用read/write函數進行實際的I/O操作,等這些I/O操作完成的在調用回調函數通知應用層。

io_service:

class io_service

  : private noncopyable

{

private:

  typedef detail::io_service_impl impl_type; //類型為task_io_service

.....

impl_type& impl_;

}

這個類的所有操作都最終轉移到task_io_service類的操作如run,post等。

Boost ASIO proactor 淺析前情提要:

Boost asio 的socket的異步非阻塞模式才有的是proactor模式,當IO操作介紹後回調相應的處理函數。ASIO在Linux平台下的實作基于epoll,但是epoll隻支援reactor模式,ASIO通過封裝在epoll上實作了proactor。提到ASIO proactor,ASIO中的所有異步操作都是基于io_service實作的,io_service是ASIO中的任務隊列,并且他負責調用epoll_wait等待IO事件到來,對io_service的實作參加前邊的blog:http://www.cnblogs.com/zhiranok/archive/2011/09/04/boost_asio_io_service_CPP.html 

Proactor 和 Rector:

兩種設計模式網上已經有很多種解釋,這兩種模式都是針對IO操作的,我的了解是Rector隻是告訴調用者什麼時候事件到來,但是需要進行什麼操作,需要調用者自己處理。Preactor不是當事件到來時通知,而是針對此事件對應的操作完成時,通知調用者,一般通知方式都是異步回調。舉例,Reactor中注冊讀事件,那麼檔案描述符可讀時,需要調用者自己調用read系統調用讀取資料,若工作在Preactor模式,注冊讀事件,同時提供一個buffer用于存儲讀取的資料,那麼Preactor通過回調函數通知使用者時,使用者無需在調用系統調用讀取資料,因為資料已經存儲在buffer中了。顯然epoll是Reactor的。

ASIO 的實作: Epoll的封裝:

l boost/asio/detail/epoll_reactor.hpp 是epoll_reatcor的封裝,class epoll_reactor有兩個作用,任務隊列和調用epoll_wait,支援的操作類型有read、write、connect、except。其實作檔案為boost/asio/etail/impl/epoll_reactor.ipp,主要的實作邏輯有run和start_op。

n Run函數的邏輯是:調用一次epoll_wait,得到相應的IO事件

n 周遊相應IO事件,若是專門用于中斷epoll操作的檔案描述符那麼跳過

n 若是用于定時器的檔案描述符,則設定标志變量check_timers為true

n 若是基本IO事件,

void epoll_reactor::run(bool block, op_queue<operation>& ops)

{

    else

    {

      // The descriptor operation doesn't count as work in and of itself, so we

      // don't call work_started() here. This still allows the io_service to

      // stop if the only remaining operations are descriptor operations.

      descriptor_state* descriptor_data = static_cast<descriptor_state*>(ptr);

      descriptor_data->set_ready_events(events[i].events);

      ops.push(descriptor_data); //descriptor_data會被插入到task_io_service::op_queue_之後task_io_service::do_run_one函數中

  o->complete(*this, ec, task_result)會調用descriptor_state::do_complete()->perform_io()->op->perform(). perform函數就是 真正的I/O操作了。注意這裡的descriptor_data是在start_op函數中注冊在epoll中的

            epoll_reactor::start_op(){

                 ev.data.ptr = descriptor_data;                      

                       epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, descriptor, &ev);             

                  }

    }

}

依次檢查其IN、OUT事件,except事件會首先檢測,将此次事件對應的隊列上的操作全部執行完畢(先調用io_servie::post,然後被調用)。

n 若check_timers标志變量被設定,那麼将已經逾時的操作通過io_service::post調用

l start_op的實作:

n Start_op需要事件的類型、檔案描述符、回調函數做參數,首先調用perform,也就是直接send,send若成功直接調用io_service::post調用回調函數,是以有些I/O是在使用者線程就執行了,有些是在io_service.run線程中執行的。

n 如果檔案描述符沒有注冊到epoll_wait,那麼EPOLLIN | EPOLLERR | EPOLLHUP | EPOLLOUT | EPOLLPRI | EPOLLET 全部注冊到epoll_wait。

n 每個檔案描述符有自己的隊列,該事件的回調函數會被添加到隊列中。

epoll_reactor::start_op(){

.....

  descriptor_data->op_queue_[op_type].push(op);

}

boost::asio::ip::tcp::socket中的異步方法的實作

l Socket中有async_打頭的許多異步方法,這裡已async_send為例

l boost/asio/ip/tcp.hpp 聲明了tcp::socket的原型,實際原型是

typedef basic_stream_socket<tcp> socket;

l basic_stream_socket是模闆類,聲明在boost/asio/basic_stream_socket.hpp檔案中,async_send操作隻是簡單的為

this->service.async_send(this->implementation, buffers, 0, handler);

而service的原型是什麼呢?

basic_stream_socket繼承于basic_socket<Protocol, stream_socket_service>,而 stream_socket_service聲明檔案為boost/asio/stream_socket_service.hpp,L60中

typedef detail::reactive_socket_service<Protocol> service_impl_type;告訴我們service的原 型是detail::reactive_socket_service<tcp>,其聲明檔案為

boost/asio/detail/reactive_socket_service.hpp

l async_send操作實作邏輯為:

n 先配置設定一個回調函數,調用start_op,start_op的實作在detail/reactive_socket_service_base.ipp檔案中,隻是簡單的向epoll_reactor調用start_op方法注冊write_op。start_op在上面的段落中已經講到了。