0Pricing
C Academy · レッスン

イベントループ

読み取り可能なソケットに反応します。

「イベントループ」はCoddyKit上の無料C Academyレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはC Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 C Academyコースには全4レッスンが含まれています。

サーバーの心臓部

イベントループは、epollサーバーを動かすエンジンです。準備完了したディスクリプタを繰り返し待ち受け、ハンドラーに処理を振り分けます。

基本的な形は常に同じです。epoll_wait()を呼び出し、返されたイベントを反復処理し、それぞれに対応してから、再びループします。それ以外の処理はすべてこの骨格につながります。

for (;;) {
    int n = epoll_wait(epfd, events, MAX_EVENTS, -1);
    for (int i = 0; i < n; i++) handle(&events[i]);
}

epoll_waitの呼び出し

epoll_wait()には、epoll fd、epoll_eventの出力配列、その容量、ミリ秒単位のタイムアウトを渡します。

少なくとも1つのディスクリプタが準備完了になるまで(またはタイムアウトするまで)ブロックし、配列に格納されたイベント数を返します。有効なのは、その数のエントリだけです。

struct epoll_event events[MAX_EVENTS];
int n = epoll_wait(epfd, events, MAX_EVENTS, -1);
if (n == -1) { perror("epoll_wait"); }

タイムアウト引数

タイムアウトに-1を指定すると、イベントが到着するまで無期限にブロックします。0を指定すると待機せず、すぐに返ります。

正の値を指定すると待機時間の上限になります。ログのフラッシュやアイドル接続の期限切れなど、定期的なタスクも実行する必要がある場合に便利です。

int n = epoll_wait(epfd, events, MAX_EVENTS, 1000); /* up to 1s */
if (n == 0) run_periodic_tasks();

イベント配列のサイズ設定

配列の容量によって、1回のepoll_wait()で通知できる準備完了イベント数の上限が決まります。準備完了したイベントがそれより多い場合、残りは次の呼び出しで返されます。

64や1024のような適度なサイズで問題ありません。epollは呼び出しごとに保留中のイベントを公平に順番処理するため、どれかのイベントだけが放置されることはありません。

#define MAX_EVENTS 64
struct epoll_event events[MAX_EVENTS];

接続待ちソケットでの振り分け

ループ内ではまず、準備完了したディスクリプタが接続待ちソケットかどうかを確認します。そうであれば、新しい接続が保留中であることを意味します。

接続を受け付け、ノンブロッキングに設定してから、epollに登録します。それ以外のディスクリプタは、読み取るデータまたは書き込み可能な空きがある既存のクライアントです。

if (events[i].data.fd == listen_fd) {
    accept_new_connections(epfd, listen_fd);
} else {
    handle_client(epfd, &events[i]);
}

ループでの接続受付

接続待ちソケットで1回の準備完了イベントが発生しても、複数の接続が保留されていることがあります。accept()がEAGAINを返すまでループします。

これはエッジトリガーモードでは必須であり、どのモードでも推奨されます。そうしないと、次に起床するまでクライアントを待たせることになります。

for (;;) {
    int c = accept(listen_fd, NULL, NULL);
    if (c == -1) { if (errno == EAGAIN) break; else break; }
    set_nonblocking(c);
    add_to_epoll(epfd, c);
}

読み取り可能イベントの処理

クライアントでEPOLLINが発生したら、そこから読み取ります。レベルトリガーモードでは、起床するたびに1回だけrecv()を呼び出しても問題ありません。

戻り値が0の場合、クライアントが接続を閉じたことを意味するため、後処理を行います。負の戻り値がEAGAINの場合は、現時点で利用可能なデータをすべて読み出したことを意味します。

ssize_t r = recv(fd, buf, sizeof buf, 0);
if (r == 0) { close_conn(epfd, fd); }
else if (r > 0) { process(buf, r); }

エッジトリガーでの読み出し完了

EPOLLETでは、recv()がEAGAINを返すまで読み取らなければなりません。カーネルは読み取り可能になった瞬間にだけ通知するため、残ったバイトは次に状態が変化するまで通知されません。

読み取りをループで囲み、EAGAINまたはストリームの終端になった場合だけループを抜けます。

for (;;) {
    ssize_t r = recv(fd, buf, sizeof buf, 0);
    if (r > 0) process(buf, r);
    else if (r == 0) { close_conn(epfd, fd); break; }
    else { if (errno == EAGAIN) break; else { close_conn(epfd, fd); break; } }
}

書き込み可能イベントの処理

EPOLLOUTは、ソケットがさらに送信データを受け付けられるときに発生します。以前に送信しきれず、バッファリングされたデータがある場合だけ、これを監視します。

バッファをフラッシュし、空になったらEPOLL_CTL_MODで監視対象をEPOLLINだけに戻します。これにより、書き込み可能通知が絶え間なく発生する状態を防げます。

if (events[i].events & EPOLLOUT) {
    flush_pending(fd);
    if (buffer_empty(fd)) watch_read_only(epfd, fd);
}

割り込みが発生した待機の処理

シグナルによって割り込みが発生すると、epoll_wait()はerrno == EINTRの状態で-1を返すことがあります。これは実際のエラーではないため、再試行してください。

堅牢なループではEINTRの場合は処理を続け、それ以外のエラーコードだけを致命的なエラーとして扱います。これを忘れると、初めてシグナルを受信したときにサーバーがクラッシュする可能性があります。

int n = epoll_wait(epfd, events, MAX_EVENTS, -1);
if (n == -1) {
    if (errno == EINTR) continue;
    perror("epoll_wait"); break;
}

1つのループで多数のクライアントを処理

ここまでの内容を組み合わせると、1つのスレッドが準備完了したディスクリプタを永続的に処理します。接続を受け付け、読み取り、書き込み、切断するという処理を繰り返します。

アイドル時にはスレッドがepoll_wait()でスリープし、処理中は準備完了した処理だけを実行するため、この設計で数万の同時接続にも十分対応できます。

確認テスト

イベントループを理解できているか確認しましょう。

まとめ

イベントループはepoll_wait()を呼び出し、準備完了したイベントを反復処理して振り分けます。接続待ちソケットでは接続を受け付け、EPOLLINでは読み取り、EPOLLOUTではフラッシュを行い、切断時には後処理を行います。

接続受付と読み取りはループで行い、EINTRでは再試行し、EPOLLETでは最後まで読み出します。次は、完全なエコーサーバーを組み立てます。

よくある質問

「イベントループ」レッスンは無料ですか?

はい。「イベントループ」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、C Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 C Academyコースには全4レッスンが含まれています。

「イベントループ」で何を学びますか?

読み取り可能なソケットに反応します。 ブラウザで直接実行するハンズオンコードでC Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

C Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのC Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。

「イベントループ」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このC Academyレッスンでコードを書いて実行できますか?

はい。すべてのC Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. ブロッキングI/OとノンブロッキングI/O
  2. epollをセットアップする
  3. イベントループ
  4. シンプルなエコーサーバー
← C Academyに戻る