UDPでマルチキャストを使う

ここでは、UDPでマルチキャストパケットを送る方法を説明したいと思います。

マルチキャスト送信サンプル

まずは、マルチキャストを送信するサンプルを以下に示します。 このサンプルではwinsock2.hだけでなくws2tcpip.hというファイルをincludeしているので注意してください。 ws2tcpip.hでIP_MULTICAST_IFやstruct ip_mreqなどが宣言されています。


#include <stdio.h>
#include <winsock2.h>
#include <ws2tcpip.h>

int
main()
{
 WSAData wsaData;

 SOCKET sock;
 struct sockaddr_in addr;
 DWORD ipaddr;

 WSAStartup(MAKEWORD(2,0), &wsaData);

 sock = socket(AF_INET, SOCK_DGRAM, 0);

 addr.sin_family = AF_INET;
 addr.sin_port = htons(12345);
 addr.sin_addr.S_un.S_addr = inet_addr("239.192.1.2");

 ipaddr = inet_addr("127.0.0.1");
 if (setsockopt(sock,
		IPPROTO_IP,
		IP_MULTICAST_IF,
		(char *)&ipaddr, sizeof(ipaddr)) != 0) {
	printf("setsockopt : %d\n", WSAGetLastError());
	return 1;
 }

 sendto(sock, "HELLO", 5, 0, (struct sockaddr *)&addr, sizeof(addr));

 closesocket(sock);

 WSACleanup();

 return 0;
}

上記例のようにsetsockoptを行わなくても、struct sockaddr_inに設定する宛先をマルチキャストアドレスにするだけでマルチキャストパケットの送信は行えます。 ただし、複数インターフェースがある機器では出力インターフェースを指定しないと意図しないインターフェースからマルチキャストパケットが出てしまいます。 そのため、面倒でもIP_MULTICAST_IFは設定しておくことをお勧めいたします。 IP_MULTICAST_IFで特定のネットワークインターフェースを指定するには、そのネットワークインターフェースに設定されたIPアドレスを利用します。 上記例では、127.0.0.1(ローカルホスト)というネットワークインターフェースを指定しています。

マルチキャスト受信サンプル

次に、マルチキャストを受信するサンプルを以下に示します。 以下のサンプルは、前述したマルチキャスト送信サンプルからのパケットを受け取ります。


#include <stdio.h>
#include <winsock2.h>
#include <ws2tcpip.h>

int
main()
{
 WSAData wsaData;

 SOCKET sock;
 struct sockaddr_in addr;
 struct ip_mreq mreq;

 char buf[2048];

 WSAStartup(MAKEWORD(2,0), &wsaData);

 sock = socket(AF_INET, SOCK_DGRAM, 0);

 addr.sin_family = AF_INET;
 addr.sin_port = htons(12345);
 addr.sin_addr.S_un.S_addr = INADDR_ANY;

 bind(sock, (struct sockaddr *)&addr, sizeof(addr));

 // setsockoptは、bind以降で行う必要あり
 memset(&mreq, 0, sizeof(mreq));
 mreq.imr_interface.S_un.S_addr = INADDR_ANY;
 mreq.imr_multiaddr.S_un.S_addr = inet_addr("239.192.1.2");

 if (setsockopt(sock,
	IPPROTO_IP,
	IP_ADD_MEMBERSHIP,
	(char *)&mreq, sizeof(mreq)) != 0) {
	printf("setsockopt : %d\n", WSAGetLastError());
	return 1;
 }

 memset(buf, 0, sizeof(buf));
 recv(sock, buf, sizeof(buf), 0);

 printf("%s\n", buf);

 closesocket(sock);

 WSACleanup();

 return 0;
}

マルチキャストパケットを受け取るには、マルチキャストグループに「参加(JOIN)」しなくてはなりません。 マルチキャストグループへの参加は、setsockoptを利用して行えます。 setsockoptを利用するポイントとしては、bindを行った後に行う必要があることが挙げられます。 マルチキャストグループへの参加を行うためには、参加するグループを表すマルチキャストアドレスと利用するネットワークインターフェースを指定しなくてはなりません。 ネットワークインターフェースにINADDR_ANYを指定することにより、明示的に利用するネットワークインターフェースを指定しないことも出来ます。 ただし、bindを行ったネットワークインターフェースとIP_ADD_MEMBERSHIPをINADDR_ANYで指定したときのネットワークインターフェースが一致する保障はありません。

一度JOINしたマルチキャストグループから「脱退(LEAVE)」するには、setsockoptをIP_DROP_MEMBERSHIPで行います。

ローカルコンピュータのIPアドレス取得

マルチキャスト送信インターフェースやマルチキャスト受信インターフェースを設定するためには、ローカルコンピュータに存在するIPアドレスを取得する必要があります。 winsockには、そのような機能はありません。 そのため、winsock以外の方法を使ってIPアドレスやインターフェースの情報を取得しなくてはなりません。 Windowsでは、そのような情報を取得する方法としてIP Helper APIを提供しています。 IP Helper APIの使い方に関してはこちらをご覧ください。

TTLの設定

Windowsの初期設定では、マルチキャストパケットはTTL 1で送信されます。 ローカルセグメントだけでマルチキャスト利用し、ルータを越える必要がない場合には問題はありませんが、ルータを越えるマルチキャストを行いたい場合には問題が発生します。 以下に、TTLの設定例を示します。


#include <stdio.h>
#include <winsock2.h>
#include <ws2tcpip.h>

int
main()
{
 WSAData wsaData;

 SOCKET sock;
 struct sockaddr_in addr;
 DWORD ipaddr;
 int ttl = 10;

 WSAStartup(MAKEWORD(2,0), &wsaData);

 sock = socket(AF_INET, SOCK_DGRAM, 0);

 addr.sin_family = AF_INET;
 addr.sin_port = htons(12345);
 addr.sin_addr.S_un.S_addr = inet_addr("239.192.1.2");

 ipaddr = inet_addr("127.0.0.1");
 if (setsockopt(sock,
	IPPROTO_IP,
	IP_MULTICAST_IF,
	(char *)&ipaddr, sizeof(ipaddr)) != 0) {
	printf("setsockopt : %d\n", WSAGetLastError());
	return 1;
 }

 if (setsockopt(sock,
	IPPROTO_IP,
	IP_MULTICAST_TTL,
	(char *)&ttl,
	sizeof(ttl)) != 0) {
	printf("setsockopt : %d\n", WSAGetLastError());
	return 1;
 }

 sendto(sock, "HELLO", 5, 0, (struct sockaddr *)&addr, sizeof(addr));

 closesocket(sock);

 WSACleanup();

 return 0;
}

プロフェッショナルIPv6解説動画シリーズ再生リスト

動画で学ぶ「プロフェッショナルIPv6」を作っています。 もしよろしければご覧ください。お楽しみいただければ幸いです!