tjun月1日記

なんでもいいので毎月書きたい

recvmmsgについて

(過去の記事の転載)

recvmmsg というのは、recvmsg(2)を拡張して複数のメッセージをsocketから受け取れるシステムコールで、Linux の 2.6.33 から利用できます。 glibcの version 2.12 からサポートされてます。 これを使うと、要するに1回のシステムコールで複数回recvmsgをすることができるので、messageをどかどか受けるようなアプリケーションでパフォーマンスが上がることがあります。

実際に使ってみて効果が見られたのと、man読んだだけじゃ使い方分かりにくかったので、これを書いておく。

manをみれば使い方はなんとなく分かると思う。manにあるサンプルコードを一番下に貼りました。 複数個受けるだけではなくtimeoutを取れるのがrecvmsgと違うところで、manを読むとまあ普通のタイムアウトのように見えるんだけど、カーネルのソースを読むと厳密なタイムアウトにはなってないようです。(ということを教えてもらいました)

また、すぐに抜けてきて欲しいときに timeoutに0 (tvsec=0, tvnsec=0) をセットすると、1つrecvしただけで返ってきてしまうのも注意。 すぐに抜けて来て欲しいけど、そのときに取れるmessageは全て取りたい、という時には、flagに MSG_DONTWAIT をセットして、timeoutはNULLにして呼び出すと期待している動作になります。

ハマりやすいポイントみたいで、manが分かりにくいという意見もあるようです。

また、sendでも似たようなことをやるためのsendmmsgというのもあります。

以下に、manにあったサンプルコードを載せておく。

#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, i;
  struct sockaddr_in sa;
  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);
  }

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

  memset(msgs, 0, sizeof(msgs));
  for (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 (i = 0; i < retval; i++) {
    bufs[i][msgs[i].msg_len] = 0;
    printf("%d %s", i+1, bufs[i]);
  }
  exit(EXIT_SUCCESS);
}