I/Oの多重化(epoll API)を使いやすくしたい

はじめに epoll APIは有能だなあと思う今日この頃ですが、epoll_wait()してイベントを取り出す処理をべた書きするとコードが汚くなりがちです。少し前の記事ですが、epoll APIを使ったチャットサーバでは、epoll_wait()後のfor(){}が長くなり、読みにくいものになっていました。そこでイベント監視を少し一般化して裏でepollを呼ぶような仕組みを作りたくなりました。話がややこしいので結論だけ書いておくと、 epoll_data_t dataのユーザ定義のポインタに関数ポインタが混じった構造体を代入すると簡潔にアプリケーション側が書けそう インタレストリストへ追加/削除するような基本的な関数を作ってアプリケーション側から呼ぶと良さそう だねっていう話です。 後述するこれらの良くないであろう点でも書いていますが、あまりいいものではないです。 この記事内で使うコードの一式をGitHubに置いておきます。ヘッダファイルはこの記事内ですべてを載せてるわけではなので使ってみたい場合は以下のリポジトリからどうぞ。 いい感じにするための方針 方向性 いい感じにするには、struct epoll_eventのメンバであるepoll_data_t dataカギになりそうです。epoll_data_tは共用体なので実際に(同時に)使えるメンバは1つだけです。前回のチャットサーバと同様に、void *ptrを使うことを考えていきます。void *ですので任意の型で代入することができます。 typedef union epoll_data { void *ptr; // ユーザ定義のデータへのポインタ int fd; // ファイルディスクリプタ uint32_t u32; // 32ビット整数 uint64_t u64; // 64ビット整数 } epoll_data_t; 前回のチャットサーバで、for(){}が長くなっている原因はIO可能になって処理する内容を関数として作られていないからというのがありますが、何よりもそれらの処理が連結リストのポインタの操作が混じっていることが原因だと思います。そのため、ポインタ操作など面倒くさいものはできるだけ裏でやるようにすると良さそうです。 void*ptrに入れる構造体 void *ptrに代入するものを以下のstruct io_eventのようにしました。...

July 3, 2021 · Eisuke Okazaki

I/O多重化でチャットサーバを作る

チャットサーバの簡単な説明 1プロセス1スレッドで動作するチャットサーバを考えていきます。 echoサーバとほぼ同じです。 複数のクライアントが接続できるサーバを考えます。 クライアントからの1回目のwrite()でユーザ名の登録をします。 ユーザ名の登録以外(2回目以降)のwrite()はサーバはそれを受け取った後に、メッセージを他の全クライアントに対して送信します。 サーバはメッセージを受け取った後に以下のフォーマットに従って編集します。 [メッセージの通し番号 発言元のユーザ名 日時] メッセージ プログラムの実行例(というか最終的に出来上がったものの実行例) ※ 今回はサーバがメインです。クライアント側はtelnetをいくつか開いて、それぞれをクライアントと想定しています。 発言元はログイン時、ログアウト時は"server"とし、それ以外の発言はユーザ名を表示しています。 epoll APIについて I/Oの多重化を扱うにはいくつかの方法があるようですが、select()やpoll()よりも扱いやすそうだったため、今回はepoll()を使用しました。 epoll APIはLinux固有の機能であるためLinux上での実行を想定しています。 int epoll_create(int size); epollインスタンスの作成 インタレストリストを空にする 引数 説明 備考 int size 監視対象のファイルディスクリプタ数 sizeは初期化処理のヒントのためであって、sizeを超えた数のファイルディスクリプタの処理も可能(だから適当な値でいいっぽい?) 戻り値の型 説明 int 成功時にファイルディスクリプタ、エラー時に-1 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *ev); インタレストリストの変更(追加、編集、削除)...

June 4, 2021 · Eisuke Okazaki