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在上面的段落中已經講到了。