Libev庫學習

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庫的安裝與使用html

https://segmentfault.com/a/1190000006173864 01:概述和 ev_loop
https://segmentfault.com/a/1190000006200077 02:watcher 基礎
https://segmentfault.com/a/1190000006679929 03:經常使用 watcher 接口java

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中的信號監視器nginx

http://blog.chinaunix.net/uid-25203957-id-1753940.html Libev 初步
http://blog.chinaunix.net/uid-25203957-id-1760908.html 互斥鎖和條件變量程序員

 

概述

Features

  • ev_io:支持 Linux 的selectpollepoll;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類型。segmentfault

錯誤事件

Libev 使用三種層級的錯誤:後端

  1. 操做系統錯誤:調用ev_set_syserr_cb所設置的回調。默認行爲是調用abort()
  2. 參數錯誤:調用assert
  3. 內部錯誤(bug):內部調用assert

全局(配置)函數

如下函數能夠在任意時間調用,用於配置 libev 庫:安全

ev_tstamp ev_time ();

返回當前的時間。併發

void ev_sleep (ev_tstamp interval);

休眠一段指定的時間。若是interval小於等於0,則馬上返回。最大支持一天,也就是86400秒app

int ev_version_major ();
int ev_version_minor ();

能夠調用這兩個函數,而且與系統與定義的EV_VERSION_MAJOREV_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 API
  • EVFLAG_SIGNALFD:在ev_signal監聽中使用signalfd API
  • EVFLAG_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_prepareev_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_XXXev_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 中指定的 signal
  • EV_CHILD:ev_child 中指定的 pid 得到了一個狀態變化
  • EV_STAT:ev_stat 中指定的 path 的屬性修改了
  • EV_IDLE:ev_idle watcher 發現無事可作
  • EV_PREPAREEV_CHECK:全部 ev_prepare watchers 在 loop 開始收集事件前調用;全部ev_check watchers 則在之後調用。回調可在這兩個 watchers 中開始/中止相應的 watchers。
  • EV_EMBED:ev_embed watcher
  • EV_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_eventev_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(如kqueueepoll),不然當一個 fd 消失、而新的 fd 進入,佔用同一個 fd 號時,libev不知道這是一個新的fd。
  libev 一側解決的辦法是每次調用ev_io_set時,都假定這是一個新的 fd。

使用dup操做 fd 的特殊問題

一些後端(backend)不能註冊普通的 fd 事件,只能註冊underlying file descriptions,這意味着使用dup()或其餘奇怪操做的fd,只能由其中一個被接收到。
  這沒有有效的解決辦法,除非將後端設置爲BACKEND_SELECTEVBACKEND_POLL

關於文件的特殊問題

ev_io對於文件淚說沒有什麼用,只要文件存在,就當即會有時間。對於stdinstdout,請謹慎使用,確保這二者沒有被重定向至文件。

關於 fork 的特殊問題

記得使用ev_loop_fork,而且使用EVFLAG_FORKCHECK。不過對於epollkqueue以外的無需擔憂。

關於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_WRITEEV_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_suspendev_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)

如下是幾種不一樣應用場景的設置方法:

  1. 絕對計時器:offset 等於絕對時間,interval 爲0,reschedule_cb 爲 NULL。在這種設置下,時鐘只執行一次,不重複
  2. 重複內部時鐘:offset 小於等於 interval 值,interval 大於0,reschedule_cb 爲 NULL。這種設置下,watcher 永遠在每個(offset + N * interval)超時。
  3. 手動排程模式: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_statdatastruct 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

基本流程

  1. 建立 socket,綁定 socket 地址
  2. Listen socket
  3. 建立一個 watcher,用來承載accept事件
  4. 寫一個 callback 用來作實際的accept調用
  5. 建立並初始化一個 watcher 用來從 client 中讀取請求
  6. 寫一個 callback 用來read
  7. 啓動 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 (loop0); // 這裏能夠直接使用 default loop

 

============== End

相關文章
相關標籤/搜索