Libev庫學習
https://www.cnblogs.com/wunaozai/p/3950249.html Libev庫學習(1)
https://www.cnblogs.com/wunaozai/p/3954131.html Libev庫學習(2)
https://www.cnblogs.com/wunaozai/p/3955156.html Libev庫學習(3)
https://www.cnblogs.com/wunaozai/p/3960494.html Zlib庫的安裝與使用
https://segmentfault.com/a/1190000006173864 01:概述和 ev_loop
https://segmentfault.com/a/1190000006200077 02:watcher 基礎
https://segmentfault.com/a/1190000006679929 03:常用 watcher 接口
https://www.cnblogs.com/gqtcgq/p/7247102.html Libev中的相對時間定時器 ev_timer
https://www.cnblogs.com/gqtcgq/p/7247100.html Libev中的絕對時間定時器 ev_periodic
https://www.cnblogs.com/gqtcgq/p/7247095.html Libev源碼分析08:Libev中的信號監視器
http://blog.chinaunix.net/uid-25203957-id-1753940.html Libev 初步
http://blog.chinaunix.net/uid-25203957-id-1760908.html 互斥鎖和條件變量
概述
Features
ev_io
:支持 Linux 的select
、poll
、epoll
;BSD 的kqueue
;Solaris 的event port mechanisms
ev_signal
:支持各種信號處理、同步信號處理ev_timer
:相對事件處理ev_periodic
:排程時間表ev_child
:進程狀態變化事件ev_start
:監視文件狀態ev_fork
:有限的fork事件支持
時間顯示
Libev 使用一個ev_tstamp
數據類型來表示1970年以來的秒數,實際類型是 C 里面的double
類型。
錯誤事件
Libev 使用三種層級的錯誤:
- 操作系統錯誤:調用
ev_set_syserr_cb
所設置的回調。默認行為是調用abort()
- 參數錯誤:調用
assert
- 內部錯誤(bug):內部調用
assert
全局(配置)函數
以下函數可以在任意時間調用,用於配置 libev 庫:
ev_tstamp ev_time ();
返回當前的時間。
void ev_sleep (ev_tstamp interval);
休眠一段指定的時間。如果interval
小於等於0,則立刻返回。最大支持一天,也就是86400秒
int ev_version_major ();
int ev_version_minor ();
可以調用這兩個函數,並且與系統與定義的EV_VERSION_MAJOR
和EV_VERSION_MINOR
作對比,判斷是否應該支持該庫
unsigned int ev_supported_backends ();
unsigned int ev_recommand_backends ();
unsigned int ev_embeddable_backends ();
返回該 libev 庫支持的和建議的后端列表
void ev_set_allocator ( void *(*cb)(void *ptr, long size)throw() );
重新設置realloc
函數。對於一些系統(至少包括 BSD 和 Darwin)的 realloc 函數可能不正確,libev 已經給了替代方案。
void ev_set_syserr_cb ( void (*cb)(const char *msg)throw() );
設置系統錯誤的 callback。默認調用perror()
並abort()
void ev_feed_signal (int signum)
模擬一個signal
事件出來
控制 event loops 的函數
Event loop 用一個結構體struct ev_loop *
描述。Libev 支持兩類 loop,一是 default loop,支持 child process event;動態創建的 event loops 就不支持這個功能
struct ev_loop *ev_default_loop (unsigned int flags);
初始化 default loops。如果已經初始化了,那么直接返回並且忽略 flags。注意這個函數並不是線程安全的。只有這個 loop 可以處理ev_child
事件。
struct ev_loop *ev_loop_new (unsigned int flags);
這個函數是線程安全的。一般而言,每個 thread 使用一個 loop。以下說明 flag 項的各個值:
EVFLAG_AUTO
:默認值,常用EVFLAG_NOENV
:指定 libev 不使用LIBEV_FLAGS
環境變量。常用於調試和測試EVFLAG_FORKCHECK
:與ev_loop_fork()
相關,本文暫略EVFLAG_NOINOTIFY
:在ev_stat
監聽中不使用inotify
APIEVFLAG_SIGNALFD
:在ev_signal
監聽中使用signalfd
APIEVFLAG_NOSIGMASK
:使 libev 避免修改 signal mask。這樣的話,你要使 signal 是非阻塞的。在未來的 libev 中,這個 mask 將會是默認值。EVBACKEND_SELECT
:通用后端EVBACKEND_POLL
:除了 Windows 之外的所有后端都可以用EVBACKEND_EPOLL
:Linux 后端EVBACKEND_KQUEUE
:大多數 BSD 的后端EVBACKEND_DEVPOLL
:Solaris 8 后端EVBACKEND_PORT
:Solaris 10 后端
void ev_loop_destroy (struct ev_loop *loop);
銷毀ev_loop
。注意這里要將所有的 IO 清除光之后再調用,因為這個函數並不中止所有活躍(active)的 IO。部分 IO 不會被清除,比如 signal。這些需要手動清除。這個函數一般和ev_loop_new
一起出現在同一個線程中。
void ev_loop_fork (struct ev_loop *loop);
這個函數導致ev_run
的子過程重設已有的 backend 的 kernel state。重用父進程創建的 loop。可以和pthread_atfork()
配合使用。
需要在每一個需要在 fork 之后重用的 loop 中調用這個函數。必須在恢復之前或者調用ev_run()
之前調用。如果是在fork
之后創建的 loop,不需要調用。
使用 pthread 的代碼例如下:
static void post_fork_chuild (void) {
ev_loop_fork (EV_DEFAULT);
}
...
pthread_atfork (NULL, NULL, post_fork_child);
int ev_is_default_loop (struct ev_loop *loop);
判斷當前 loop 是不是 default loop。
unsigned int ev_iteration (struct ev_loop *loop);
返回當前的 loop 的迭代數。等於 libev pool 新事件的數量(?)。這個值對應ev_prepare
和ev_check
調用,並在 prepare 和 check 之間增一。
unsigned int ev_depth (struct ev_loop *loop);
返回ev_run()
進入減去退出次數的差值。
注意,導致ev_run
異常退出的調用(setjmp / longjmp, pthread_cancel, 拋出異常等)均不會導致該值減一。
unsigned int ev_backend (struct ev_loop *loop);
返回EVBACKEND_*
值
ev_tstamp ev_now (loop)
得到當前的“event loop time”。在 callback 調用期間,這個值是不變的。
void ev_new_update (loop)
更新從ev_now()
中返回的時間。不必要的話,不要使用,因為這個函數的開銷相對是比較大的。
void ev_suspend (struct ev_loop *loop);
void ev_resume (struct ev_loop *loop);
暫停當前的 loop,使其刮起當前的所有工作。同時其 timeout 也會暫停。如果恢復后,timer 會從上一次暫停狀態繼續及時——這一點對於實現一些要連同時間也一起凍結的功能時,非常有用。
注意已經 resume 的loop不能再 resume,反之已經 suspend 的 loop 不能再 suspend。
bool ev_run (struct ev_loop *loop, int flags);
初始化 loop 結束后,調用這個函數開始 loop。如果 flags == 0,直至 loop 沒有活躍的時間或者是調用了 ev_bread 之后停止。
Loop 可以是異常使能的,你可以在 callback 中調用longjmp
來終端回調並且跳出 ev_run,或者通過拋出 C++ 異常。這些不會導致 ev_depth 值減少。
EVRUN_NOWAIT
會檢查並且執行所有未解決的 events,但如果沒有就緒的時間,ev_run 會立刻返回。EVRUN_ONCE
會檢查所有的 events,在至少每一個 event 都執行了一次事件迭代之后才返回。但有時候,使用ev_prepare
/ev_check
更好。
以下是ev_run
的大致工作流程:
- loop depth ++
- 重設
ev_break
狀態 - 在首次迭代之前,調用所有 pending watchers
LOOP:
- 如果置了
EVFLAG_FORKCHECK
,則檢查 fork,如果檢測到 fork,則排隊並調用所有的 fork watchers - 排隊並且調用所有 ready 的watchers
- 如果
ev_break
被調用了,則直接跳轉至 FINISH - 如果檢測到了 fork,則分離並且重建 kernel state
- 使用所有未解決的變化更新 kernel state
- 更新
ev_now
的值 - 計算要 sleep 或 block 多久
- 如果指定了的話,sleep
- loop iteration ++
- 阻塞以等待事件
- 排隊所有未處理的I/O事件
- 更新
ev_now
的值,執行 time jump 調整 - 排隊所有超時事件
- 排隊所有定期事件
- 排隊所有優先級高於 pending 事件的 idle watchers
- 排隊所有 check watchers
- 按照上述順序的逆序,調用 watchers (check watchers -> idle watchers -> 定期事件 -> 計時器超時事件 -> fd事件)。信號和 child watchers 視為 fd watchers。
- 如果
ev_break
被調用了,或者使用了EVRUN_ONCE
或者EVRUN_NOWAIT
,則如果沒有活躍的 watchers,則 FINISH,否則 continue
FINISH:
- 如果是
EVBREAK_ONE
,則重設 ev_break 狀態 - loop depth --
- return
void ev_break (struct ev_loop *loop, how);
中斷 loop。參數可以是 EVBREAK_ONE
(執行完一個內部調用后返回)或EVBREAK_ALL
(執行完所有)。
下一次調用 ev_run 的時候,相應的標志會清除
void ev_ref (struct ev_loop *loop);
void ev_unref (struct ev_loop *loop);
類似於 Objective-C 中的引用計數,只要 reference count 不為0,ev_run 函數就不會返回。
在做 start 之后要 unref;stop 之前要 ref。
void ev_set_io_collect_interval (struct ev_loop *loop, ev_tstamp interval);
void ev_set_timeout_collect_interval (struct ev_loop *loop, ev_tstamp interval);
兩個值均默認為0,表示盡量以最小的延遲調用 callback。但這是理想的情況,實際上,比如 select 這樣低效的系統調用,由於可以一次性讀取很多,所以可以適當地進行延時。通過使用比較高的延遲,但是增加每次處理的數據量,以提高 CPU 效率。
void ev_invoke_pending (struct ev_loop *loop);
調用所有的 pending 的 watchers。這個除了可以在 callback 中調用(少見)之外,更多的是在重載的函數中使用。參見下一個函數
void ev_set_invoke_pending_cb (struct ev_loop *loop, void (*invoke_pending_cb(EV_P)));
重載 ev_loop 調用 watchers 的函數。新的回調應調用 ev_invoke_pending
。如果要恢復默認值,則置喙 ev_invoke_pending 即可。
int ev_pending_count (struct ev_loop *loop);
返回當前有多少個 pending 的 watchers。
void ev_set_loop_release_cb (struct ev_loop *loop, void (*release)(EV_P)throw(), void (*acquire)(EV_P)throw());
這是一個 lock 操作,你可以自定義 lock。其中 release 是 unlock,acquire 是 lock。release 是在 loop 掛起以等待events 之前調用,並且在開始回調之前調用 acquire。
void ev_set_userdata (struct ev_loop *loop, void *data);
void *ev_userdata (struct ev_loop *loop);
設置 / 讀取 loop 中的用戶 data。這一點和 libevent 很不同,libevent 的參數 / 用戶數據是以 event 為單位的,而 libev 的原生用戶數據是以 loop 為單位的。
void ev_verify (struct ev_loop *loop);
驗證當前 loop 的設置。如果發現問題,則打印 error msg 並 abort()
。
Watcher 解析
以下是一段示意性的代碼,使用的是ev_io
:
static void my_cb (struct ev_loop *loop, ev_io *w, int revents)
{
ev_io_stop (w);
ev_break (loop, EVBREAK_ALL);
}
some_main()
{
...
struct ev_loop *loop = ev_default_loop (0);
ev_io stdin_watcher;
ev_init (&stdin_watcher, my_cb);
ev_io_set (&stdin_watcher, STDIN_FILENO, EV_READ);
ev_io_start (loop, &stdin_watcher);
ev_run (loop, 0);
...
}
每一個 watcher 類型有一個附屬的 watcher 結構體。(一般是struct ev_XXX
或ev_XXX
)
每一個 watcher 結構都需要用ev_init
初始化,每一個 watcher 都有對應的ev_XXX_set
函數、ev_XXX_start
函數、ev_XXX_stop
函數。在 ev_run 之前進行各個 watcher 的 ev_start。
只要 watcher 是 active,就不能再調用 init。
每個 callback 都有三個參數:loop, watcher, 事件的掩碼值。可能的掩碼值有:
EV_READ
EV_WRITE
EV_TIMER
:ev_timer 超時EV_PERIODIC
:ev_periodic 超時EV_SIGNAL
:某線程接收了 ev_signal 中指定的 signalEV_CHILD
:ev_child 中指定的 pid 獲得了一個狀態變化EV_STAT
:ev_stat 中指定的 path 的屬性修改了EV_IDLE
:ev_idle watcher 發現無事可做EV_PREPARE
,EV_CHECK
:所有 ev_prepare watchers 在 loop 開始收集事件前調用;所有ev_check watchers 則在以后調用。回調可在這兩個 watchers 中開始/停止相應的 watchers。EV_EMBED
:ev_embed watcherEV_CLEANUP
:event loop 即將被銷毀EV_ASYNC
:asuny watcher 已經被異步通知EV_CUSTOM
:不是 libev 發送的信號。參見ev_feed_event
EV_ERROR
:在 libev 內存不夠用時可能產生;fd 被外部關閉時也可能產生
通用 watcher 函數
void ev_init (ev_TYPE *watcher, callback)
使用這個宏初始化 watcher。此外還需要調用相應的 ev_XXX_set
函數。參見下文:
void ev_TYPE_set (ev_TYPE *watcher, [args])
設置指定類型的 wetaher。init 函數必須在此之前被調用一次,此后可以設置任意次的 set 函數。
不能對一個 active 的 watcher 調用此函數,但 pending 可以。比如:ev_io w;
ev_init (&w, my_cb);
ev_io_set (&w, STDIN_FILENO, EV_READ);
void ev_TYPE_set (ev_TYPE *watcher, callback, [args])
這個宏將 init 和 set 糅合在一起使用
void ev_TYPE_start (loop, ev_TYPE *watcher)
開始(激活)指定的 watcher。如果 watcher 已經是 active,則調用無效。
void ev_TYPE_stop (loop, ev_TYPE *watcher)
停止 watcher,並清空 pending 狀態。如果要釋放一個 Watcher,最好都顯式地調用 stop。
bool ev_is_active (ev_TYPE *watcher)
如果 watcher 被執行了一次 start,並且未被 stop,則返回 true。
bool ev_is_pending (ev_TYPE *watcher)
當且僅當 watcher pending 時返回 true。(如:有未決的事件,但是 callback 未被調用)
callback ev_cb (ev_TYPE *watcher)
void ev_set_cb (ev_TYPE *watcher, callback)
讀 / 寫 callback
void ev_set_priority (ev_TYPE *watcher, int priority)
int ev_priority (ev_TYPE *watcher)
Priority 是一個介於EV_MAXPRI
(默認2)和EV_MIN_PRI
(默認-2)之間的值。數值越高越優先被調用。但除了 ev_idle,每一個 watcher 都會被調用。
當 watcher 是 active 或 pending 時並不能修改。
實際上 priority 大於-2到2的范圍也是沒問題的。
void ev_invoke (loop, ev_TYPE *watcher, int revents);
使用指定的參數調用 callback
int ev_clear_pending (loop, ev_TYPE *watcher);
清除指定 watcher 的 pending 狀態,並且返回 revents 位。如果 watcher 不是 pending 則返回0
void ev_feed_event (loop, ev_TYPE *watcher, int revents)
模擬一個事件。參見ev_feed_fd_event
和ev_feed_signal_event
Watcher 狀態
除了前文提及的 active 和 pending 狀態之外,本小節描述了更加詳細的 watcher 狀態。
initialized
:通過調用ev_TYPE_init
對 watcher 進行初始化,這是注冊到 loop 之前的必要步驟。可以再次調用 ev_TYPE_init 進行操作。
started
/running
/active
:調用ev_TYPE_start
之后的狀態,並且開始等待事件。在這個狀態下,除了特別提及的少數情況之外,它不能存取、移動、釋放,只能維持着對它的指針。
pending
:當 watcher 是 active 並且一個讓 watcher 感興趣的事件到來,那么 watcher 進入 pending。這個狀態的 watcher 可以 access,但不能存取、移動、釋放。
stopped
:調用ev_TYPE_stop
,此時狀態與 initialized 相同。
ev_io:直接操作fd
這個 watcher 負責檢測文件描述符(以下簡稱fd)是否可寫入數據或者是讀出數據。最好是將fd設置為非阻塞的。
注意有時候在調用read
時是沒有數據的(返回0),此時一個一個非阻塞的read
會得到EAGAIN
錯誤。
(以下兩個特殊問題,是 libev 文檔中特別提到的,但是我看不太懂……)
失蹤的 fd 的特殊問題
部分系統需要顯式地調用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
,請謹慎使用,確保這兩者沒有被重定向至文件。
關於 fork 的特殊問題
記得使用ev_loop_fork
,並且使用EVFLAG_FORKCHECK
。不過對於epoll
和kqueue
之外的無需擔心。
關於SIGPIPE的問題
只是提醒一下:記得處理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);
...
}
ev_timer:相對超時機制
Libev 提供了一個相對超時機制的定時器。所謂的“相對”,就是說這個定時器的參數是:指定以當前時間為基准,延遲多久出發事件。這個定時器與基於萬年歷的日期/時間是無關的,只基於系統單調時間。
循環定時器設計
下面列出一個以60秒為單位的循環定時器作為例子,來說明使用ev_timer的不同策略
1. 使用標准的初始化和停止 API 來重設
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,並且重啟它。第一個參數是首次超時,第二個參數是第二次開始的固定超時時間。
但是這樣的方法雖然比較簡易,但是時間不穩定,而且開銷較大
2. 使用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
3. 讓 timer 超時,但視情況重新配置
這個方式的基本思路是因為許多 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)
4. 為 timer 使用雙向鏈表
使用場景:有成千上萬個請求,並且都需要 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 *)
ev_periodic:基於日歷的定時器
相關函數
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)
以下是幾種不同應用場景的設置方法:
- 絕對計時器:offset 等於絕對時間,interval 為0,reschedule_cb 為 NULL。在這種設置下,時鍾只執行一次,不重復
- 重復內部時鍾:offset 小於等於 interval 值,interval 大於0,reschedule_cb 為 NULL。這種設置下,watcher 永遠在每一個(offset + N * interval)超時。
- 手動排程模式:offset 忽略,reschedule_cb 設置。使用 callback 來返回下次的 trigger 時間。callback 原型為:
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 *)
返回下一次觸發的絕對時間。
ev_signal:捕獲 signal 事件
在哦你跟一個 loop 可以多次觀測同一個 signal,但是無法在多個 loop 中觀測同一個 signal。此外,SIGCHILD
只能在 default loop 中監聽。
注意點
關於繼承 fork / execve / ptherad_create 的問題
在子進程調用 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)
ev_child:子進程退出事件
當接收到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 時,監控目標位置上無需存在文件,因為文件從“不存在”變為存在也是一種狀態變化。
文件路徑必須是絕對路徑,不能存在“./
”或“../
”。
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
都是只讀,字面意義上的意思。
ev_idle:無事可做時的事件
void ev_idle_init (ev_idle *, callback)
這個功能沒有研究過,暫記着把。
其他事件(僅記錄)
ev_prepare 和 ev_check
ev_embed
ev_fork
ev_cleanup
ev_asunc
其他函數
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
。
Reference
Create tcp echo server using libev
基本流程
- 創建 socket,綁定 socket 地址
Listen
socket- 創建一個 watcher,用來承載
accept
事件 - 寫一個 callback 用來做實際的
accept
調用 - 創建並初始化一個 watcher 用來從 client 中讀取請求
- 寫一個 callback 用來
read
- 啟動 event loop
創建 socket 並綁定 address
注意:原文例子中未顯示的是,應當將 fd 設置為非阻塞的。帶非阻塞設置的代碼如下:
some_init_func()
{
...
sd = socket (PF_INET, SOCK_STREAM, 0);
flags = fcntl (sd, F_GETEL, 0);
fcntl (sd, F_SETEL, flags | O_NONBLOCK);
bzero (&addr, sizeof(addr));
... // 設置 Address 和 port
bind (sd, (struct sockaddr *)(&addr), sizeof(addr));
...
}
監聽端口
some_init_func()
{
...
listen (sd, 2);
...
}
准備用來accept()
的 watcher
some_init_func()
{
...
ev_io_init (&w_accept, accept_cb, sd, EV_READ);
ev_io_start (loop, &w_accept);
...
}
回調函數如下:
static void accept_cb (struct ev_loop *loop,
struct ev_io *watcher,
int revents)
{
...
client_sd = accept (watcher->fd, // accept() 調用,接受傳入連接
(struct sockaddr *)(&client_addr),
&client_len);
...
w_client = (struct ev_io *)malloc(sizeof(struct ev_io)); // 為 read watcher 准備內存
...
ev_io_init (w_client, read_cb, client_sd, EV_READ); // 這里就只示例 read 事件了。write 事件同理
ev_io_start (loop, w_client);
}
准備用來read()
的 callback
static void read_cb (struct ev_loop *loop,
struct ev_io *watcher,
int revents)
{
...
readCount = recv (watcher->fd, buffer, BUFFER_SIZE, 0); // 讀取的方法就視乎程序員的實現啦
send (watcher->fd, buffer, readCount, 0); // 把數據 echo 回去
...
}
原文例子使用的就是recv
/send
,實際上我個人偏愛的是read
/write
啟動 event loop
ev_loop (loop, 0); // 這里可以直接使用 default loop
============== End