RECVMMSG(2)

名前
recvmmsg - 複数のメッセージをソケットから受信する

書式
#define _GNU_SOURCE         /* feature_test_macros(7) 参照 */
#include <sys/socket.h>

int recvmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen,
             int flags, struct timespec *timeout);

説明
recvmmsg() システムコールは recvmsg(2) の拡張で、 このシステムコールを使うと一度の呼び出しでソケットから複数のメッセージを受信することができる (アプリケーションによっては性能上のメリットがある)。 他に recvmsg(2) から拡張されている点としては、受信操作におけるタイムアウトのサポートがある。

sockfd 引数は、データを受信するソケットのファイルディスクリプターである。

msgvec 引数は mmsghdr 構造体の配列である。 この配列の大きさは vlen で指定する。

mmsghdr 構造体は <sys/socket.h> で次のように定義されている。

    struct mmsghdr {
        struct msghdr msg_hdr;  /* メッセージヘッダー */
        unsigned int  msg_len;  /* このヘッダーで受信されたバイト数 */
    };

msg_hdr フィールドは、 recvmsg(2) で説明されている msghdr 構造体である。 msg_len フィールドは、 このエントリーで返されるメッセージのバイト数で、 このヘッダーに対して recvmsg(2) を呼び出した場合の返り値と同じ値が入る。

flags 引数には複数のフラグを論理和 (OR) で指定できる。 フラグは、 recvmsg(2) で説明されているものに加えて、以下が使用できる。

MSG_WAITFORONE (Linux 2.6.34 以降)
    最初のメッセージを受信後に MSG_DONTWAIT を有効にする。

timeout 引数は struct timespec (clock_gettime(2) 参照) へのポインターで、 この構造体で受信操作のタイムアウト (秒とナノ秒) を指定する (ただし、バグを参照のこと) (待ち時間はシステムクロックの粒度に切り上げられ、カーネルのスケジューリング遅延により少しだけ長くなる可能性がある)。 timeoutが NULL の場合、 受信操作は無期限に停止 (block) する。

停止 (blocking) モードの recvmmsg() の呼び出しは、 vlen 個のメッセージを受信するか、タイムアウトが満了するまで停止する。 非停止 (nonblocking) モードの呼び出しでは、 読み出し可能なメッセージ (最大で vlen 個) を読み出し、 すぐに返る。

recvmmsg() が返った際には、 msgvec のうちデータが受信された要素には、受信したそれぞれのメッセージの情報が格納されている。 また、 msg_len には受信したメッセージの大きさが入り、 msg_hdr の各フィールドは recvmsg(2) に書かれている通りに更新される。 呼び出しの返り値は、更新された msgvec の要素数である。

返り値
成功すると、 recvmmsg() は msgvec に受信されたメッセージ数を返す。 エラーの場合、 -1 を返し、 errno にエラーを示す値を設定する。

エラー
エラーは recvmsg(2) と同じである。 これに加えて、以下のエラーが起こる場合がある。

EINVAL
    timeout が無効である。

See also BUGS.

バージョン
recvmmsg() システムコールは Linux 2.6.33 で追加された。 glibc でのサポートはバージョン 2.12 以降で利用可能である。

準拠
recvmmsg() は Linux 固有である。

バグ
timeout 引数は意図した通りには動作しない。 タイムアウトは各データグラムの受信後にのみチェックされる。 そのため、 タイムアウトが満了する前に vlen-1 個のデータグラムを受信し、 その後全くデータグラムを受信しなかった場合、 呼び出しはずっと停止し続けてしまう。

If an error occurs after at least one message has been received, the call succeeds, and returns the number of messages received. The error code is expected to be returned on a subsequent call to recvmmsg(). In the current implementation, however, the error code can be overwritten in the meantime by an unrelated network event on a socket, for example an incoming ICMP packet.

例
以下のプログラムは、 recvmmsg() を使って複数のメッセージをソケットから受信し、それらを複数のバッファーに格納する。 呼び出しは、すべてのバッファーにメッセージが格納されるか、 指定したタイムアウト時間が経過すると返る。

以下のコマンドは、 ランダムな数字が入った UDP データグラムを定期的に生成する。

    $ while true; do echo $RANDOM > /dev/udp/127.0.0.1/1234;
    sleep 0.25; done

生成されたデータグラムをサンプルアプリケーションが読み出し、以下のような出力が得られる。

    $ ./a.out
    5 messages received
    1 11782
    2 11345
    3 304
    4 13514
    5 28421

  プログラムのソース
    #define _GNU_SOURCE
    #include <netinet/ip.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/socket.h>

    int
    main(void)
    {
    #define VLEN 10
    #define BUFSIZE 200
    #define TIMEOUT 1
        int sockfd, retval;
        struct sockaddr_in addr;
        struct mmsghdr msgs[VLEN];
        struct iovec iovecs[VLEN];
        char bufs[VLEN][BUFSIZE+1];
        struct timespec timeout;

        sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (sockfd == -1) {
            perror("socket()");
            exit(EXIT_FAILURE);
        }

        addr.sin_family = AF_INET;
        addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
        addr.sin_port = htons(1234);
        if (bind(sockfd, (struct sockaddr *) &addr, sizeof(addr)) == -1) {
            perror("bind()");
            exit(EXIT_FAILURE);
        }

        memset(msgs, 0, sizeof(msgs));
        for (int i = 0; i < VLEN; i++) {
            iovecs[i].iov_base         = bufs[i];
            iovecs[i].iov_len          = BUFSIZE;
            msgs[i].msg_hdr.msg_iov    = &iovecs[i];
            msgs[i].msg_hdr.msg_iovlen = 1;
        }

        timeout.tv_sec = TIMEOUT;
        timeout.tv_nsec = 0;

        retval = recvmmsg(sockfd, msgs, VLEN, 0, &timeout);
        if (retval == -1) {
            perror("recvmmsg()");
            exit(EXIT_FAILURE);
        }

        printf("%d messages received\n", retval);
        for (int i = 0; i < retval; i++) {
            bufs[i][msgs[i].msg_len] = 0;
            printf("%d %s", i+1, bufs[i]);
        }
        exit(EXIT_SUCCESS);
    }

関連項目
clock_gettime(2), recvmsg(2), sendmmsg(2), sendmsg(2), socket(2), socket(7)

この文書について
この man ページは Linux man-pages プロジェクトのリリース 5.10 の一部である。プロジェクトの説明とバグ報告に関する情報は https://www.kernel.org/doc/man-pages/ に書かれている。