イベント関連のライブラリを作るにあたり、select, epollどちらを使おうかなと探していると、libeventというライブラリを発見。サクッと使えて多機能だったので紹介します。
ざっくり概要
epollやkqueue, select等沢山あるイベント監視系のAPIをラップし、シンプルに使えるようにしたイベント監視ライブラリ。マルチプラットフォーム対応になっています。
また、派生機能なのかはわからないですが、http通信用API等も備わっていて、大分多機能なライブラリとなっています。
こちらの記事によると、Chromeやntpdなんかでも利用されているそうです!
今回はイベント監視ライブラリ部分を紹介します。
動作環境
Ubuntu 18.04 Desktop
インストール
通常のaptコマンドでインストールできます。
sudo apt install libevent-dev
/usr配下に色々インストールされます。
公式のサンプルコードは以下やgithub公式に置いてあります。
/usr/share/doc/libevent-dev/examples/
or
https://github.com/libevent/libevent
サンプルコード
サンプルコードのevent-read-fifo.cを参考に、こんな流れのサンプルにしました。
- main内、
event_base_new
,event_new
,event_add
でbaseに対してイベント登録。 - main_thread内で
event_base_dispatch
のmainループを起動(スレッドはブロックされます。) - mainから登録済みのFDへメッセージを送ると、
event_base_dispatch
内のイベント検知が発生し、登録してあったsock_read
が呼ばれます。 - ループを終わらせたい時は
event_base_loopbreak
を実行。
event_new/event_add
もしくはevent_init/event_set/event_add
でさらにFDの追加も可能。
また、event_base_dispatchでは、最後の引数で呼び出される関数sock_readの引数argを好きに指定できます。
event_dispatch
というAPIを利用すると監視もバックグラウンドでやってくれるようですが、今回の用途としては監視の開始/終了をこちらでコントロールしたいのでevent_base_dispatch
を採用予定。
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <event2/event-config.h>
#include <event2/event.h>
static void sock_read(evutil_socket_t fd, short event, void *arg)
{
char buf[255];
memset(buf, 0, sizeof(buf));
read(fd, buf, sizeof(buf) - 1);
printf("%s\n", buf);
if(strcmp(buf, "exit") == 0) {
event_base_loopbreak((struct event_base*)arg);
}
}
void * main_thread(void *arg) {
struct event_base* base = (struct event_base*)arg;
event_base_dispatch(base);
return NULL;
}
int main() {
pthread_t tid;
int sockpair[2];
socketpair(AF_UNIX, SOCK_DGRAM, 0, sockpair);
struct event_base* base = event_base_new();
struct event *evfifo = event_new(base, sockpair[1], EV_READ|EV_PERSIST, sock_read, base);
event_add(evfifo, NULL);
printf("test before dispatch\n");
pthread_create(&tid, NULL, main_thread, base);
write(sockpair[0], "test", strlen("test"));
write(sockpair[0], "file", strlen("file"));
write(sockpair[0], "finished", strlen("finished"));
write(sockpair[0], "exit", strlen("exit"));
///...
pthread_join(tid, NULL);
return 0;
}
実行するとこんな感じ。このサンプルでは"exit"を受け取った時点でループ終了となります。
$ ./a.out
test before dispatch
test
file
finished
exit
ライセンス
ファイルによって微妙に違うようですが、BSD, OpenBSD, MITの3種類が記載されていました(公式githubより)。
どれも縛りの緩いものなので安心して利用出来そう。
利用の際に発生した問題
-
event_base_dispatch(base);
利用中に削除・追加したイベントの反映はすぐにされないことがあります。その際はevent_base_loopcontinue
を利用するとイベントをリロードしてくれる。(意外と反映が遅い?) -
event_assign
という関数がイベントのdelete && addをしてくれそうな説明文があったが、実際は出来ませんでした。update時は素直にdel⇒new⇒addしましょう。 - 何かlibevent側でエラーが発生している場合は、
event_enable_debug_mode()
を使うとデバッグログが出てエラー箇所で止まります。(ただしevent_base作成前に実行すること)
-
event_enable_debug_mode()
を利用して確認したエラーの紹介。
event_enable_debug_mode()
を2回呼んだ際に発生。
[err] event_enable_debug_mode was called twice!
event_assign
時に発生。add設定されたイベントの差し替えには使えません。残念。
[err] event_assign called on an already added event 0x7f2680000c30 (events: 0x12, fd: 64, flags: 0x82)
event_base_free
時に発生。根本原因までは追えていませんが、eventに絡んだデータのfree漏れがあると発生するようです。
valgrind --leak-check=full実行時に指摘されたリーク部分を修正することで発生しなくなりました。
2018/06/16 追記
⇒こちらイベント待ち受け終了とイベントハンドルが被った場合に発生していました。
終了の仕方については別途纏めます。
[err] event_del_nolock_: noting a del on a non-setup event 0x55fcb1ff4e30 (events: 0x12, fd: 44, flags: 0x80)
終了の仕方について
ヘッダーの英語をざっくり読んだ感じと動作を見比べた結果、以下のような仕様となりそうです。
API | 意味合い | 備考 |
---|---|---|
event_base_loopbreak | 発行されたイベントの処理終了を一通り待ってからloopを終了する。 | 入れ違いでのイベント発生することもある。 |
event_base_loopexit | 発行されたイベントの処理終了を待たずに指定時間後に強制終了する。 | イベントはおそらく発生(未確認) |
event_base_got_break | event_base_loopbreakで終了したかを確認する。まだ処理終了していないイベントがある場合はtrueが返る。 | |
event_base_got_exit | event_base_loopexitによる強制終了が発生したかを確認する。 |
それぞれの仕様を照らし合わせると、event_base_dispatch
を利用したループの場合は以下のようにすると安全に終了できそうです。
- event_base_loopbreakを利用してloopを終了
- event_base_dispatch後処理で、event_base_loopbreakとの入れ違いイベントがあるかもしれないので、
event_base_got_break
がtrueか確認 - event_base_got_breakがtrueならもう一度
event_base_loopbreak
を実行してイベントの処理終了を再度待つ
コードとしてはこんな感じになるかと
event_base_dispatch(event_base);
if(event_base_got_break(event_base)) {
/*remain event callback, wait to call it*/
event_base_loopbreak(event_base);
}
参考
概要として一番参考になったサイト
libeventとは
主な参考
libeventの使い方
github (sampleフォルダのサンプル、testフォルダのコードおよびinclude内のAPI説明コメント)
libevent + 使い方