請注意這是 libev 而不是 libevent 的文章!程序員
這篇文章是第三篇,主要講 libev 裏基本集中的 watcher。segmentfault
本文地址:http://www.javashuo.com/article/p-nyfhulii-by.html後端
這個 watcher 負責檢測文件描述符(如下簡稱fd)是否可寫入數據或者是讀出數據。最好是將fd設置爲非阻塞的。
注意有時候在調用read
時是沒有數據的(返回0),此時一個一個非阻塞的read
會獲得EAGAIN
錯誤。服務器
(如下兩個特殊問題,是 libev 文檔中特別提到的,可是我看不太懂……)併發
部分系統須要顯式地調用close
(如kqueue
、epoll
),不然當一個 fd 消失、而新的 fd 進入,佔用同一個 fd 號時,libev
不知道這是一個新的fd。
libev 一側解決的辦法是每次調用ev_io_set
時,都假定這是一個新的 fd。異步
dup
操做 fd 的特殊問題一些後端(backend)不能註冊普通的 fd 事件,只能註冊underlying file descriptions
,這意味着使用dup()
或其餘奇怪操做的fd,只能由其中一個被接收到。
這沒有有效的解決辦法,除非將後端設置爲BACKEND_SELECT
或EVBACKEND_POLL
函數
ev_io
對於文件淚說沒有什麼用,只要文件存在,就當即會有時間。對於stdin
和stdout
,請謹慎使用,確保這二者沒有被重定向至文件。oop
記得使用ev_loop_fork
,而且使用EVFLAG_FORKCHECK
。不過對於epoll
和kqueue
以外的無需擔憂。學習
只是提醒一下:記得處理SIGPIPE
事件。大數據
accept
一個沒法接受的鏈接大多數 POSIX accpet 實現中在刪除由於錯誤而致使的鏈接時(如 fd 到達上限)都回產生一個錯誤的操做,好比使 accept 失敗但不拒絕鏈接,只產生ENFILE
錯誤。但這個會致使 libev 仍是將其標記爲 ready 狀態。
推薦方法是列出全部的錯誤並記錄下來,或者是暫時關閉 watchers。
void ev_io_init (ev_io *, callback, int fd, int events) void ev_io_set (ev)io *, int fd, int events)
其中 events 能夠是EV_WRITE
和EV_READ
的組合。
static void stdin_readable_db (struct ev_loop *loop, ev_io *w, int revents) { ev_io_stop (loop, w) ...... // 從 w->fd 中進行read } ...... some_init_func () { ...... struct ev_loop *loop = ev_default_init (0); ev_io stdin_readable; ev_io_init (&stdin_readable, stdin_readable_db , STDIN_FILENO, EV_READ); ev_io_start (loop, &stdin_readable); ev_run (loop, 0); ... }
Libev 提供了一個相對超時機制的定時器。所謂的「相對」,就是說這個定時器的參數是:指定以當前時間爲基準,延遲多久出發事件。這個定時器與基於萬年曆的日期/時間是無關的,只基於系統單調時間。
下面列出一個以60秒爲單位的循環定時器做爲例子,來講明使用ev_timer的不一樣策略
ev_timer_init (timer, callback, 60.0, 6.0); ev_timer_start (loop, timer)
標準設置。或——
ev_timer_stop (loop, timer); ev_timer_set (timer, 60.0, 0.0); ev_timer_start (loop, timer)
這樣的設置,當每次有活躍時間時,中止timer,而且重啓它。第一個參數是首次超時,第二個參數是第二次開始的固定超時時間。
可是這樣的方法雖然比較簡易,可是時間不穩定,並且開銷較大
ev_timer_again
重設使用ev_timer_again
,能夠忽略ev_timer_start
ev_init (timer, callback); timer->repeat = 60.0; ev_timer_again (loop, start);
上面的初始化完成後,在 callback 裏調用:
timer->repeat = 60.0; ev_timer_again (loop, timer);
能夠改變 timeout 值,無論 timer 是否 active
這個方式的基本思路是由於許多 timeout 時間都比 interval 大不少,此時要記住上一次活躍的時間,而後再 callback 中檢查真正的 timeout
ev_tstamp g_timeout = 60.0; ev_tstamp g_last_activity; ev_timer g_timer; static void callback (EV_P_ev_timer *w, int revents) { ev_tstamp after = g_last_activity - ev_now(EV_A) + g_timeout; // 若是小於零,表示時間已經發生了,已超時 if (after < 0.0) { ...... // 執行 timeout 操做 } else { // callback 被調用了,可是卻有一些最近的活躍操做,說明未超時 // 此時就按照須要設置的新超時事件來處理 ev_timer_set (w, after, 0.0); ev_timer_start (loop, g_timer); } }
啓用這種模式,記得初始化時將g_last_activity
設置爲ev_now
,而且調用一次callback (loop, &g_timer, 0)
;當活躍時間到來時,只需修改全局的 timeout 變量便可,而後再調用一次 callback
g_timeout = new_value ev_timer_stop (loop, &timer) callback (loop, &g_timer, 0)
使用場景:有成千上萬個請求,而且都須要 timeouts
當 timeout 開始前,計算 timeout 的值,而且將 timeout 放在鏈表末尾。而後當鏈表前面的項須要 fire 時。使用ev_timer
來將其 fire 掉。
當有 activity 時,將 timer 從 list 中一處,重算 timeout,而且再附到 list 末尾,確保若是ev_timer
已經被 list 的第一項取出時,更新它
假設在500.9秒的時候請求延時1秒,那麼當501秒到來時,可能致使 timeout,這就是「太早」問題。Libev的策略是對於這種狀況,在502秒時才執行 timeout。可是這又有「太晚」的問題,請程序員注意.
Suspenged animation,也稱爲休眠,指的是將機子置於休眠狀態。注意不一樣的機子不一樣的系統這個行爲可能不同。
其中一種休眠是使得全部程序感受只是通過了很小的一段時間通常(時間跳躍)
推薦在SIGTSTP
處理中調用ev_suspend
和ev_resume
ev_now_update()
的開銷很大,請謹慎使用
Libev使用的時一個內部的單調時鐘而不是系統時鐘,而ev_timer
則是基於系統時鐘的,因此在作比較的時候二者不一樣步。
void ev_timer_init (ev_timer *, callback, ev_tstamp after, ev_tstamp repeat); void ev_timer_set (ev_timer *, ev_tstamp after, ev_tstamp repeat);
若是repeat爲正,這個timer會重複觸發,不然只觸發一次。
void ev_timer_again (loop, ev_timer *)
ev_tstamp ev_timer_remaining (loop, ev_timer *)
void ev_periodic_init (ev_periodic *, callback, ev_tstamp offset, ev_tstamp interval, reschedule_cb) void ev_periodic_set (ev_periodic *, ev_tstamp offset, ev_tstamp interval, reschedule_cb)
如下是幾種不一樣應用場景的設置方法:
ev_tstamp (*reschedule_cb)(ev_periodic *w, ev_tstamp now);
例程是:
static ev_tstamp my_scheduler (...) { return now + 60.0; }
相似於 Linux 內核的jiffies
,返回下一個時間點。
這個timer很是便於用來提供諸如「下一個正午12點」之類的定時器。
void ev_periodic_again (loop, ev_periodic *)
關閉並重啓 watcher,參見前文。
ev_tstamp ev_periodic_at (ev_periodic *)
返回下一次觸發的絕對時間。
在哦你跟一個 loop 能夠屢次觀測同一個 signal,可是沒法在多個 loop 中觀測同一個 signal。此外,SIGCHILD
只能在 default loop 中監聽。
在子進程調用 exec
以前,應當將 signal mask 重設爲你所需的默認值。最簡單的方法就是子進程作一個pthread_atfork()
來重設。
POSIX 的很多功能(如sigwait)只有在進程中的全部線程屏蔽了 signal 時才真正生效
爲了解決這個問題,若是真的要使用這些功能的話,建議在建立線程以前屏蔽全部的 signal,而且在建立 loops 的時候指定EVFLAG_NOSIGMASK
,而後制定一個 thread 用來接收 signals。
void _ev_signal_init (ev_signal *, callback, int signum) void ev_signal_set (ev_signal *, int signum)
當接收到SIGCHILD
事件時,child watcher 觸發。大部分狀況下,子進程退出或被殺掉。只要這個 watcher 的 loop 未開始,你甚至能夠在 shild 被 fork 以後才加入 child watcher。
Ev_child 的優先級固定是EV_MAXPRI
。
void ev_chile_init (ev_child *, callback, int pid, int trace) void ev_child_set (ev_child *, int pid, int trace)
Pid 若是指定0的話,表示任意子進程。能夠在 ev_child 中觀察rstatus
成員來了解子進程狀態。
int pid;
表示監控中的 pid,只讀。
int rpid;
可讀寫,表示檢測到狀態變化的 pid
int tstatus;
可讀寫,表示由 rpid 致使的進程的 exit/trace 狀態值。
使用 ev_stat 時,監控目標位置上無需存在文件,由於文件從「不存在」變爲存在也是一種狀態變化。
文件路徑必須是絕對路徑,不能存在「./
」或「../
」。
Ev_stat 的實現其實只是按期調用stat()
來判斷文件屬性的變化,因此能夠指定檢查週期。指定0的話會使用默認事件週期。
正由於這是輪詢操做,因此這個功能不適合作大數據量或者是大併發檢測;同時,ev_stat 是異步的。
默認關閉大文件支持(使用32位的stat
)。若是要使用大文件支持(ABI),libev 的做者在這裏吐槽,說你要遊說操做系統的發佈方去支持……囧rz
有些系統的文件時間僅精確到秒,這就意味着 ev_stat 沒法區分秒如下的變更。
void ev_stat_init (ev_stat *, callback, const char *path, ev_tstamp interval); void ev_stat_set (ev_stat *, const char *path, ev_tstamp interval); void ev_stat_stat (loop, ev_stat *);
第三個函數使用新的文件 stat 值去更新 stat buffer,使用此函數來使得你作的一些配置更改不會被觸發。
ev_statdata attr
只讀,表明文件最近一次的狀態。ev_statdata
和struct stat
基本是相通的。
ev_statdata prev
文件上一次的狀態
ev_tstamp interval const char *path
都是隻讀,字面意義上的意思。
void ev_idle_init (ev_idle *, callback)
這個功能沒有研究過,暫記着把。
void ev_once (loop, int fd, int events, ev_tstamp timeout, callback)
從指定的f fd 中指定一個超時事件,這個函數的方便之處在於無需作 alloc
/conf
/start
/stop
/free
。
Fd 能夠小於0,這樣就沒有 I/O 監控,而且「events」會被忽略。
void ev_feed_event (loop, int fd, int revents);
向一個 fd 發送事件。須要注意的是,這個功能貌似是隻能在 loop 內調用纔有效,異步地在 loop 的另外一個線程直接調用是無效的。
void ev_feed_signal_event (loop, signum)
向一個 loop 模擬 signal。參見 ev_feed_signal
。
Libev 官方文檔學習筆記(1)——概述和 ev_loop
Libev 官方文檔學習筆記(2)——watcher 基礎
Libev 官方文檔學習筆記(3)——經常使用 watcher 接口(本文)
使用 libev 構建 TCP 響應服務器的簡單流程