libeasy是基於libev實現的一套高並發、異步非阻塞的事件庫,與libev相比又做了一層封裝,更加方便處理各種網絡請求。目前已經被阿里多個對性能有較高要求的核心軟件采用(數據庫,動態、靜態數據cache等),我寫的log_pipe的網絡部分同樣也是采用libeasy的,不過跟核心就沒有關系了,哈哈。今天我要寫的是一次最簡單的網絡請求事件要在libeasy中經歷的流程。后續會介紹涉及到磁盤io等需要異步響應的流程。
1、easy_eio_create
初始化線程池的各個線程相應的數據結構,並對listen_watcher添加一個Timer事件,每0.1秒觸發一次,對應的回調函數是easy_connection_on_listen。easy_connection_on_listen定時起來去搶listen的鎖。
如果搶到鎖,則把當前的listen線程改成自己,並添加這個線程的EV_READ事件,回調函數同樣是easy_connection_on_accept;
如果搶不到鎖,則會停止自己對listen的EV_READ事件的監聽,即在接下來的0.1秒,所有的accept讀事件都由搶到鎖的這個線程來觸發。
2、easy_connection_add_listen
打開監聽的端口,把線程池的每個線程數據結構都加上對這個listenfd(位於easy_eio_t結構的listen結構題)的EV_READ事件,其中的回調函數設置為easy_connection_on_accept。這意味着在開始階段,線程池的每個線程都會響應accept事件。
3、easy_connection_on_accept
由線程池的某個線程觸發,對listenfd做accept操作,成功后會創建easy_connection_t對象;並且注冊對這個fd的讀寫超時事件easy_connection_on_(readable/writable/timeout_conn)。正常情況下該請求后續所有的事件都會注冊在這個線程的ev_loop上,除非達到設置的負載條件,會停掉該鏈接在這個線程的事件,並把這個線程的所有事件注冊到另一個線程ev_loop上。
調用easy_handler_t->on_connect回調(第1個我們自己要實現的回調函數,非必需)
調用easy_switch_listen,這個函數會把自己的listen_watcher重新設置為0.5秒,然后重新啟動這個timer,並且釋放listen的鎖;這就意味此時其它線程可以通過easy_connection_on_listen來獲得listen鎖了,而且因為其它線程都是0.1秒,而當前線程設置的是0.5秒,這樣就等於把listen的機會讓給了其它線程,有利於線程之間的負載均衡。剛開始的時候我誤以為當前線程的time是easy_connection_on_listen函數中設置的60秒,實際這個60秒的作用只是相當於停掉當前線程的timer,因為當自己獲得listen鎖期間是不需要這個timer的。
4、easy_connection_on_readable
當socket有數據到來時,會觸發讀事件的回調函數,它首先會創建一個easy_message_t,一個decode單位對應一個easy_message_t。所以一個easy_message_t到底包含一個或多個request或者其他什么東西,是取決於我們自己的實現的。
數據全部讀到easy_message_t后會有判斷,如果是當前是server端會調用easy_connection_do_request,如果是客戶端會調用easy_connection_do_response。我今天講的的是服務器端,所以我們接下來看easy_connection_do_request
5、easy_connection_do_request
恩,數據已經讀完了,連傻姑都猜到下面該解析數據了,哈哈。此時會調用easy_connnect_t->easy_handler_t->decode(第2個我們自己要實現的回調函數,一般都需要實現這個函數),decode可能會調用若干次,知道把easy_message_t中接收的buffer解析完。對於每一次decode調用,創建一個easy_request_t對象r,並把r加到request列表,把decode返回的packet掛到r->ipacket上面,然后調用easy_connection_process_request
6、easy_connection_process_request
調用easy_connect_t->easy_handle_t->process(第3個我們自己要實現的回調函數,一般都需要實現),對一個請求單位(我之所以是一個請求單位,是因為這里並不一定是一個完整的請求,准確的說應該是decode出來的一個buf單位)做處理。在process中,process有兩種正常的返回值:EASY_OK表示我們已經知道要響應什么數據,或者不需要響應client。EASY_AGAIN表示目前我們還沒有准備好響應數據。下一篇文章我們將介紹EASY_AGAIN的情況。下面介紹EASY_OK時候的情況。
調用easy_connection_request_done:如果存在r->opacket,調用easy_connect_t->easy_handle_t->encode(第4個我們自己要實現的回調函數,一般都需要實現,除非客戶端不需要響應),並且設置此次請求對應的cleanup方法
調用easy_connection_write_socket,把r->output數據通過socket寫出去
至此,一次最簡單的tcp請求處理流程就走完了。
