20.11.2014 Aufrufe

UDP通信の基礎

UDP通信の基礎

UDP通信の基礎

MEHR ANZEIGEN
WENIGER ANZEIGEN

Sie wollen auch ein ePaper? Erhöhen Sie die Reichweite Ihrer Titel.

YUMPU macht aus Druck-PDFs automatisch weboptimierte ePaper, die Google liebt.

1<br />

Chapter 3<br />

2<br />

3<br />

UDP 通 信 の 基 礎<br />

4<br />

UDPの 特 徴 は 信 頼 性 を 犠 牲 としたリアルタイム 性 です。サーバに<br />

負 荷 をかけずに 多 くの 受 信 者 にパケットを 送 信 できるブロードキャ<br />

ストやマルチキャストが 利 用 できるため、 音 声 や 映 像 を 転 送 するア<br />

プリケーションなどに 使 われます。また、DNSに 対 するqueryにも 使<br />

われています。<br />

Chapter 3では、まず 最 初 にUDPによる 単 純 な 受 信 サンプルと 送<br />

信 サンプルを 示 し、 次 にブロードキャストとマルチキャストによる<br />

サンプルプログラムを 示 します。<br />

5<br />

6<br />

7<br />

Linux_069_100_03.indd 69<br />

10.2.26 1:35:11 PM


Chapter3<br />

UDP 通 信 の 基 礎<br />

3-2 UDPのプログラミング<br />

3-1<br />

UDP<br />

ネットワークプログラミングでTCPの 次 に 多 い 通 信 方 法 がUDPだと 思 われます。<br />

UDPはデータが 宛 先 に 届 いたかどうかを 関 知 しないため、データの 到 着 を 保 障 しない 点 が<br />

TCPと 異 なります。そのため、UDPを 使 った 通 信 を 行 うプログラムを 書 く 場 合 には、パケット<br />

がネットワークの 途 中 で 消 えてしまうことも 想 定 しなくてはなりません。 確 実 にデータを 届 け<br />

たいアプリケーションではTCPを 使 うのが 一 般 的 です。<br />

このように 紹 介 するとUDPは 使 いにくいだけと 思 うかもしれませんが、もちろん 利 点 もあり<br />

ます。<br />

■<br />

■<br />

■<br />

■<br />

複 数 の 相 手 に 同 時 にデータを 送 信 できる(ブロードキャスト/マルチキャスト)<br />

TCPよりもリアルタイム 性 が 高 い<br />

メッセージの 境 界 が 明 確<br />

TCPよりもNATを 超 えやすい(P2Pの 場 合 )<br />

このように、UDPにはTCPにない 利 点 があり、これらを 生 かしたアプリケーションに 使 われ<br />

るのが 一 般 的 です。UDPを 使 ったアプリケーションの 例 として、 映 像 や 音 声 のストリーミング<br />

やIP 電 話 などのVoIP(Voice over IP)などが 挙 げられます。 音 声 などを 使 って 通 話 をするアプ<br />

リケーションは、すべての 音 が 正 しく(オリジナルデータに 忠 実 に) 届 くことよりもリアルタイ<br />

ム 性 が 重 視 されます。 相 手 の 声 がわかっても、 会 話 にならないのは 困 るというわけです。<br />

メッセージの 境 界 が 明 確 である 点 もUDPの 特 徴 のひとつです。TCPの 場 合 は、 連 続 的 なスト<br />

リームとしてデータが 扱 われるため、アプリケーションが 途 中 で 数 バイト 間 違 えてデータを 読<br />

み 込 んでしまうと、 二 度 とデータの 整 合 性 が 取 れなくなる 場 合 があります( 読 み 込 みだけでは<br />

なく 書 き 込 み 側 が 数 バイトデータを 間 違 える 場 合 も 同 様 です)。 一 方 、UDPの 場 合 は 各 メッセー<br />

ジが 独 立 したパケットとして 扱 われるため、 個 別 のデータ 境 界 が 明 確 となり、 途 中 でデータ 読<br />

み 込 みに 失 敗 しても 次 から 復 帰 することが 容 易 になります。<br />

最 後 に、UDPはNAPT(NAT)に 強 いという 特 徴 もあります。 通 信 を 行 う 両 端 がNATの 裏 側<br />

に 存 在 した 場 合 、TCPでは 接 続 を 確 立 するのが 困 難 ですが、UDPでSTUN(Simple Traversal<br />

of UDP through NATs)などを 活 用 することで 通 信 できる 場 合 があります。その 仕 組 みはプロ<br />

グラミングとは 関 係 が 薄 いのでここでは 割 愛 します。 興 味 のある 方 はネットワーク 系 技 術 書 籍<br />

などをご 覧 ください。<br />

1<br />

2<br />

3<br />

まず、 最 初 の 利 点 として「 複 数 の 相 手 に 同 時 にデータ 送 信 ができる」ことが 挙 げられます。<br />

Chapter 1で 解 説 したように、IPの 通 信 形 態 には、「ユニキャスト」「ブロードキャスト」「マル<br />

チキャスト」の3 種 類 があります ( 注 3-1) 。<br />

TCPでは1 対 1の 通 信 しかできないため、このうちユニキャストしかサポートしていません。<br />

それに 対 してUDPでは、ひとつデータパケットを 送 ればネットワークで 必 要 に 応 じて 増 やして<br />

送 ってくるブロードキャストやマルチキャストが 利 用 できます。 送 信 側 は 受 信 者 数 に 関 係 なく<br />

必 要 最 小 限 のパケットだけ 送 っていれば、あとはネットワークが 適 切 に 処 理 をしてくれるため、<br />

送 信 側 のアプリケーションの 負 荷 を 大 きく 軽 減 できます。<br />

第 二 の 利 点 としては「リアルタイム 性 」が 挙 げられます。TCPはデータの 到 着 を 保 障 するため、<br />

ネットワーク 内 でパケットが 消 えると 再 送 します。この 再 送 によって、リアルタイム 性 が 損 な<br />

われることがあります。また、TCPはネットワークが 混 雑 しているとデータ 送 信 量 を 減 らす「 輻<br />

輳 (ふくそう) 制 御 」を 行 います。この 輻 輳 制 御 によっても、TCPのリアルタイム 性 が 損 なわれ<br />

ています。<br />

一 方 、UDPにはリアルタイム 性 を 損 なう 再 送 や 輻 輳 制 御 がないため、UDPはTCPよりもリ<br />

アルタイム 性 が 高 くなります。ただし、UDPには 輻 輳 制 御 が 必 要 ないわけではないのでご 注 意<br />

ください。TCPは 輻 輳 制 御 機 構 がユーザアプリケーションでは 関 知 できない 部 分 ( 下 層 レイヤ)<br />

で 行 われるため、アプリケーションに 工 夫 の 余 地 はありません。 他 方 で、UDPではアプリケー<br />

ションごとに 最 適 な 輻 輳 制 御 機 構 を 設 計 し、 構 築 できるという 特 徴 があります。<br />

3-2<br />

UDP<br />

UDPでは、TCPのように 接 続 を 確 立 してから 通 信 を 始 めるようなことは 行 いません。そのた<br />

め、どちらがサーバでどちらがクライアントか 直 感 的 にわからない 場 合 があります。 本 書 では<br />

UDPに 関 してはサーバ/クライアントという 表 現 をせずに、「UDP 送 信 プログラム」「UDP 受 信<br />

プログラム」という 表 現 をしていきます( 図 3-1)。<br />

4<br />

5<br />

6<br />

7<br />

注 3-1:ただし、IPv6にはブロードキャストはありません。 代 わりに、IPv6には「エニーキャスト」があります(エニー<br />

キャストについては 本 書 では 割 愛 します)。<br />

70 71<br />

Linux_069_100_03.indd 70-71<br />

10.2.26 1:35:12 PM


Chapter3<br />

UDP 通 信 の 基 礎<br />

3-2 UDPのプログラミング<br />

3-1<br />

UDP の 通 信<br />

List 3-1<br />

recvfrom()システムコール<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

#include <br />

#include <br />

ssize_t recvfrom(<br />

int s,<br />

void *buf,<br />

size_t len,<br />

int flags,<br />

struct sockaddr *from,<br />

socklen_t *fromlen<br />

);<br />

/* ソケットのファイルディスクリプタ*/<br />

/* 受 信 データを 取 得 するためのバッファ*/<br />

/* bufのサイズ */<br />

/* 受 信 するときの 挙 動 を 指 定 するためのフラグ */<br />

/* 宛 先 に 関 する 情 報 を 取 得 するためのsockaddr 構 造 体 */<br />

/* fromの 大 きさ */<br />

1<br />

2<br />

<br />

List 3-2<br />

recv()システムコール<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

<br />

単 純 な UDP 受 信 プログラム<br />

UDP 通 信 を 行 うプログラムは、 受 信 と 送 信 で 異 なります。 最 初 にUDP 受 信 プログラムを 説<br />

明 します。<br />

UDP 受 信 プログラムは、 特 定 のIPアドレス+UDPポート 番 号 でパケットを 待 ち 続 けます。 手<br />

順 は 以 下 のとおりです。<br />

■<br />

■<br />

■<br />

■<br />

ソケットを 作 る<br />

IPアドレスとポートを 設 定 する<br />

ソケットに 名 前 を 付 ける(bind()する)<br />

データを 受 け 取 る<br />

UDPを 利 用 したソケットプログラミングでは、データを 受 信 する 方 法 としてrecvfrom()や<br />

recv()システムコールを 利 用 することが 一 般 的 です。 両 者 の 主 な 違 いは、 送 信 側 の 情 報 を 毎<br />

回 取 得 できるかどうかです。<br />

recvfrom()とrecv()の 各 システムコールは、 以 下 のように 宣 言 されています。<br />

#include <br />

#include <br />

ssize_t recv(<br />

int s,<br />

void *buf,<br />

size_t len,<br />

int flags<br />

);<br />

flagsのパラメータとしては、<br />

■<br />

■<br />

■<br />

■<br />

■<br />

■<br />

■<br />

MSG_CMSG_CLOEXEC<br />

MSG_DONTWAIT<br />

MSG_ERRQUEUE<br />

MSG_OOB<br />

MSG_PEER<br />

MSG_TRUNC<br />

MSG_WAITALL<br />

/* ソケットのファイルディスクリプタ */<br />

/* 受 信 データを 取 得 するためのバッファ*/<br />

/* bufのサイズ */<br />

/* 受 信 するときの 挙 動 を 指 定 するためのフラグ */<br />

などが 指 定 可 能 です。これらはビット 表 現 されているので、 複 数 の 論 理 和 をとったものを 指 定<br />

できます。 詳 細 はman 2 recvfromをご 覧 ください。<br />

fromlenはINとOUTの 双 方 で 利 用 されるため、ポインタで 渡 します。recv()あるいはrecvfrom<br />

()を 行 う 前 にfromlenのための 変 数 にfromのサイズを 代 入 することでカーネルにfromのサイズ<br />

を 知 らせ、カーネルは 取 得 されたfromのサイズをfromlenの 実 体 に 代 入 します。<br />

recv()とrecvfrom()は、 成 功 すると 受 信 したバイト 数 を 返 します。エラーの 場 合 には-1を<br />

返 し、errnoが 設 定 されます。<br />

さっそく 簡 単 なUDP 受 信 プログラムのサンプルを 以 下 に 示 します。ここでは、recvfrom()<br />

システムコールを 利 用 しています。 繰 り 返 しますが、コードを 簡 単 にするためエラー 処 理 は 省<br />

3<br />

4<br />

5<br />

6<br />

7<br />

72 73<br />

Linux_069_100_03.indd 72-73<br />

10.2.26 1:35:14 PM


Chapter3<br />

UDP 通 信 の 基 礎<br />

3-2 UDPのプログラミング<br />

いてあります。 実 際 にコードを 書 く 場 合 にはエラー 処 理 も 行 ってください。<br />

List 3-3<br />

単 純 な UDP 受 信 プログラム<br />

#include <br />

#include <br />

#include <br />

#include <br />

#include <br />

int<br />

main()<br />

{<br />

int sock;<br />

struct sockaddr_in addr;<br />

struct sockaddr_in senderinfo;<br />

socklen_t addrlen;<br />

char buf[2048];<br />

int n;<br />

単 純 な UDP 送 信 プログラム<br />

UDP 送 信 プログラムは、 特 定 のIPアドレス+UDPポート 番 号 で 待 っているアプリケーショ<br />

ンに 対 して、パケットを 送 信 します。<br />

■<br />

■<br />

ソケットを 作 る<br />

宛 先 を 指 定 して 送 信 する<br />

UDPを 利 用 したソケットプログラミングでは、データを 送 信 する 方 法 としてsendto()や<br />

send()システムコールを 利 用 することが 一 般 的 です。<br />

sendto()とsend()の 各 システムコールは、 以 下 のように 宣 言 されています。<br />

List 3-4<br />

sendto()システムコール<br />

1<br />

2<br />

3<br />

sock = socket(AF_INET, SOCK_DGRAM, 0);<br />

addr.sin_family = AF_INET;<br />

addr.sin_port = htons(12345);<br />

addr.sin_addr.s_addr = INADDR_ANY;<br />

bind(sock, (struct sockaddr *)&addr, sizeof(addr));<br />

addrlen = sizeof(senderinfo);<br />

n = recvfrom(sock, buf, sizeof(buf) - 1, 0,<br />

(struct sockaddr *)&senderinfo, &addrlen);<br />

write(fileno(stdout), buf, n);<br />

1<br />

2<br />

3<br />

#include <br />

#include <br />

ssize_t sendto(<br />

int s,<br />

/* ソケットのファイルディスクリプタ */<br />

const void *buf,<br />

/* 送 信 するデータを 含 むバッファ */<br />

size_t len,<br />

/* bufのサイズ */<br />

int flags,<br />

/* 送 信 するときの 挙 動 を 指 定 するためのフラグ */<br />

const struct sockaddr *to, /* 宛 先 に 関 する 情 報 を 示 すsockaddr 構 造 体 */<br />

socklen_t tolen<br />

/* toの 大 きさ*/<br />

);<br />

4<br />

}<br />

close(sock);<br />

return 0;<br />

4<br />

List 3-5<br />

send()システムコール<br />

#include <br />

#include <br />

5<br />

上 記 UDP 受 信 プログラムは、UDP 送 信 プログラムから 送 られてきたデータを 表 示 して 終 了<br />

します。Chapter 2で 解 説 したTCPによる 通 信 プログラムよりもシンプルであることがわかる<br />

でしょう。<br />

まず 最 初 に、IPv4のUDPソケットを 作 成 しています(1)。AF_INET+SOCK_DGRAMの 組 み 合<br />

わ せ がIPv4のUDPで あ る こ と を 示 し て い ま す。 次 にUDPソ ケ ッ ト に 対 し て の 設 定 値 を<br />

sockaddr_inに 対 して 代 入 し(2)、ソケットをbind()しています。<br />

データを 受 信 し、 受 信 したデータを 表 示 しています(3)。recvfrom()のサイズを「sizeof(buf)<br />

−1」に 指 定 しているのは、 最 後 の1バイトを 必 ず「\0」にするためです。これを 行 わないと、 次<br />

にくるprintfと% sの 組 み 合 わせでバッファオーバフローを 発 生 させる 可 能 性 があります。<br />

最 後 に、ソケットを 閉 じています(4)。<br />

ssize_t send(<br />

int s,<br />

const void *buf,<br />

size_t len,<br />

int flags<br />

);<br />

/* ソケットのファイルディスクリプタ */<br />

/* 送 信 するデータを 含 むバッファ */<br />

/* bufのサイズ */<br />

/* 送 信 するときの 挙 動 を 指 定 するためのフラグ */<br />

両 者 の 違 いは、send()システムコールがソケットが 接 続 状 態 (すでにconnect()した 状 態 )<br />

であることを 要 求 する 点 です。 接 続 状 態 にあるソケットであればwrite()システムコールによ<br />

るデータ 送 信 も 可 能 ですが、send()とwrite()の 違 いはflagsを 指 定 できるか 否 かになります。<br />

そのため、flagsに0を 指 定 したsend()システムコールは、write()と 同 じ 挙 動 になります。<br />

flagsのパラメータとしては、<br />

6<br />

7<br />

74 75<br />

Linux_069_100_03.indd 74-75<br />

10.2.26 1:35:14 PM


Chapter3<br />

UDP 通 信 の 基 礎<br />

3-2 UDPのプログラミング<br />

■<br />

■<br />

■<br />

■<br />

■<br />

■<br />

■<br />

MSG_CONFIRM<br />

MSG_DONTROUTE<br />

MSG_DONTWAIT<br />

MSG_EOR<br />

MSG_MORE<br />

MSG_NOSIGNAL<br />

MSG_OOB<br />

などを 指 定 可 能 です。これらはビット 表 現 されているので、flagsパラメータには 複 数 の 論 理 和<br />

をとったものを 指 定 できます。<br />

sendto()とsend()は、 成 功 すると 送 信 されたバイト 数 を 返 します。エラーの 場 合 には-1を<br />

返 し、errnoが 設 定 されます。errnoの 値 としては 以 下 のものがあり 得 ます。<br />

3-2<br />

EACCES<br />

errno 値<br />

EAGAINまたはEWOULDBLOCK<br />

EBADF<br />

ECONNRESET<br />

EDESTADDRREQ<br />

EFAULT<br />

EINTR<br />

EINVAL<br />

内 容<br />

ソケット・ファイルへの 書 き 込 み 許 可 がなかったか、パス 名 へ 到 達 するまでの<br />

ディレクトリのいずれかに 対 する 検 索 許 可 がなかった<br />

ソケットが 非 停 止 に 設 定 されており、 要 求 された 操 作 が 停 止 した<br />

無 効 なディスクリプタが 指 定 された<br />

接 続 が 接 続 相 手 によりリセットされた<br />

ソケットが 接 続 型 (connection-mode)ではなく、かつ 送 信 先 のアドレスが 設 定<br />

されていない<br />

ユーザ 空 間 として 不 正 なアドレスがパラメータとして 指 定 された<br />

データが 送 信 される 前 に、シグナルが 発 生 した<br />

不 正 な 引 数 が 渡 された<br />

EISCONN 接 続 型 ソケットの 接 続 がすでに 確 立 していたが、 受 信 者 が 指 定 されていた ( 注 3-2)<br />

EMSGSIZE<br />

そのソケット 種 別 ではソケットに 渡 されたままの 形 でメッセージを 送 信 する 必<br />

要 があるが、メッセージが 大 きすぎるため 送 信 できない<br />

ENOBUFS ネットワークインターフェースの 出 力 キューがいっぱいである ( 注 3-3)<br />

ENOMEM<br />

ENOTCONN<br />

ENOTSOCK<br />

EOPNOTSUPP<br />

EPIPE<br />

sendto()と send()の errno 値 (man 2 sendto より)<br />

メモリが 足 りない<br />

ソケットが 接 続 されておらず、 接 続 先 も 指 定 されていない<br />

引 数 s がソケットでない<br />

引 数 flagsのいくつかのビットが、そのソケット 種 別 では 不 適 切 なものである<br />

接 続 指 向 のソケットでローカル 側 が 閉 じられている。この 場 合 、MSG_NOSIG<br />

NALが 設 定 されていなければ、プロセスにはSIGPIPE も 同 時 に 送 られる<br />

単 純 なUDP 送 信 プログラムのプログラムを 以 下 に 示 します。ここではUDPソケットに 対 す<br />

るconnect()を 行 わないので、sendto()システムコールを 利 用 しています。 実 行 ファイル 名 を<br />

注 3-2: 現 在 のところ、この 状 況 では、このエラーが 返 されるか 受 信 者 の 指 定 が 無 視 されるかのいずれかとなります。<br />

注 3-3: 一 般 的 には、 一 時 的 な 輻 輳 (congestion) のためにインターフェースが 送 信 を 止 めていることを 意 味 します。 通<br />

常 、Linuxではこのようなことは 起 こりません。デバイスのキューがオーバーフローした 場 合 にはパケットは<br />

黙 って 捨 てられます。<br />

「a.out」としてコンパイルした 場 合 、「./a.out 127.0.0.1」のように 宛 先 IPv4アドレスを 指<br />

定 して 実 行 してください。<br />

List 3-6<br />

単 純 な UDP 送 信 プログラム<br />

#include <br />

#include <br />

#include <br />

#include <br />

#include <br />

int<br />

main(int argc, char *argv[])<br />

{<br />

int sock;<br />

struct sockaddr_in addr;<br />

int n;<br />

if (argc != 2) {<br />

fprintf(stderr, "Usage : %s dstipaddr\n", argv[0]);<br />

return 1;<br />

}<br />

sock = socket(AF_INET, SOCK_DGRAM, 0);<br />

addr.sin_family = AF_INET;<br />

addr.sin_port = htons(12345);<br />

inet_pton(AF_INET, argv[1], &addr.sin_addr.s_addr);<br />

n = sendto(sock, "HELLO", 5, 0, (struct sockaddr *)&addr, sizeof(addr));<br />

if (n < 1) {<br />

perror("sendto");<br />

return 1;<br />

}<br />

close(sock);<br />

return 0;<br />

}<br />

上 記 UDP 送 信 プログラムは、UDP 受 信 プログラムに 対 してデータを 送 信 して 終 了 します。こ<br />

の 動 作 を 確 認 するためには、あらかじめUDP 受 信 プログラムを 起 動 しておく 必 要 があります。<br />

まず、IPv4のUDPソケットを 作 成 しています(1)。AF_INET+SOCK_DGRAMの 組 み 合 わせが<br />

IPv4のUDPであることを 示 しています。<br />

2では、「./a.out 127.0.0.1」として 実 行 した 場 合 、127.0.0.1を 宛 先 に 設 定 しています。<br />

127.0.0.1とは、localhostという「 自 分 自 身 」を 示 す 特 殊 アドレスです。 前 述 したUDP 受 信 プ<br />

ログラムが 別 ホストで 動 作 している 場 合 には、このIPアドレスを 変 更 してください。<br />

続 いて、「HELLO」という5 文 字 を 含 むパケットを 送 信 しています(3)。 送 信 先 は、2で 設 定<br />

したsockaddr_inに 入 っている 宛 先 です。そして 最 後 にソケットを 閉 じています(4)。<br />

1<br />

2<br />

3<br />

4<br />

1<br />

2<br />

3<br />

4<br />

5<br />

6<br />

7<br />

76 77<br />

Linux_069_100_03.indd 76-77<br />

10.2.26 1:35:15 PM


Chapter3<br />

UDP 通 信 の 基 礎<br />

3-2 UDPのプログラミング<br />

UDP 送 信 プログラムは、UDPの 受 信 プログラムよりもさらにシンプルです。 特 別 な 準 備 は<br />

行 わずに、いきなりパケットを 投 げられるUDPのプロトコルとしての 特 徴 がそのままコードに<br />

出 ているといえます。ただし、UDPには 信 頼 性 がないため、パケットが 相 手 に 届 かないことが<br />

ある 点 には 注 意 しましょう。<br />

getaddrinfo()を 利 用 した UDP プログラム<br />

inet_pton()はIPv4/IPv6 両 方 に 対 応 していますが、アドレスファミリを 指 定 しなければなら<br />

ない 点 や、 結 果 出 力 先 をアドレスファミリに 応 じて 変 更 しなければならない 点 で、 両 方 に 対 応<br />

したコードが 多 少 書 きにくい 点 があります。<br />

IPv4/IPv6 両 用 のコードを 書 くには、getaddrinfo()を 活 用 するのが 便 利 です。<br />

UDPパケット 送 信 側 サンプルプログラム<br />

以 下 に、UDPパケット 送 信 側 サンプルプログラムを 示 します。<br />

List 3-7<br />

getaddrinfo()を 利 用 した UDP 送 信 プログラム<br />

#include <br />

#include <br />

#include <br />

#include <br />

#include <br />

#include <br />

#include <br />

int<br />

main(int argc, char *argv[])<br />

{<br />

int sock;<br />

struct addrinfo hints, *res;<br />

int n;<br />

int err;<br />

if (argc != 2) {<br />

fprintf(stderr, "Usage : %s dst\n", argv[0]);<br />

return 1;<br />

}<br />

/* IPアドレス 表 記 +ホスト 名 両 方 に 対 応 */<br />

memset(&hints, 0, sizeof(hints));<br />

hints.ai_family = AF_UNSPEC; /* IPv4/IPv6 両 方 に 対 応 */<br />

hints.ai_socktype = SOCK_DGRAM;<br />

err = getaddrinfo(argv[1], "12345", &hints, &res);<br />

if (err != 0) {<br />

1<br />

printf("getaddrinfo : %s\n", gai_strerror(err));<br />

return 1;<br />

}<br />

sock = socket(res->ai_family, res->ai_socktype, 0);<br />

if (sock < 0) {<br />

perror("socket");<br />

return 1;<br />

}<br />

{<br />

const char *ipverstr;<br />

switch (res->ai_family) {<br />

case AF_INET:<br />

ipverstr = "IPv4";<br />

break;<br />

case AF_INET6:<br />

ipverstr = "IPv6";<br />

break;<br />

default:<br />

ipverstr = "unknown";<br />

break;<br />

}<br />

printf("%s\n", ipverstr);<br />

}<br />

n = sendto(sock, "HELLO", 5, 0, res->ai_addr, res->ai_addrlen);<br />

if (n < 1) {<br />

perror("sendto");<br />

return 1;<br />

}<br />

close(sock);<br />

freeaddrinfo(res);<br />

return 0;<br />

}<br />

まず、AF_UNSPEC+SOCK_DGRAMでgetaddrinfo()を 実 行 しています(1)。AF_UNSPECを 指<br />

定 することで、IPv4/IPv6 両 用 になっています。このAF_UNSPECをAF_INETにするとIPv4 固 定<br />

に、AF_INET6にするとIPv6 固 定 にできます。getaddrinfo()の 第 二 引 数 は 宛 先 ポート 番 号 を 文<br />

字 列 表 現 したものです。<br />

2では、getaddrinfo()の 結 果 をそのまま 利 用 してソケットを 作 成 しています。こうするこ<br />

とで、getaddrinfo()の 結 果 がIPv4/IPv6どちらであっても、 適 切 なソケットを 作 成 できます。<br />

ここで、 解 決 した 宛 先 アドレスがIPv4かIPv6かをprintf()で 表 示 するコードを 追 加 してみま<br />

した(3)。この 部 分 は 必 ずしも 必 要 ではないのでご 注 意 ください。<br />

4ではgetaddrinfo()の 結 果 をそのまま 利 用 してsendto()を 実 行 しています。<br />

最 後 に、ソケットを 閉 じて、getaddrinfo()によって 確 保 されたaddrinfo 構 造 体 のメモリを 解<br />

放 しています(5)。freeaddrinfo()を 行 うタイミングはclose()より 前 でも 大 丈 夫 です。<br />

78 79<br />

2<br />

3<br />

4<br />

5<br />

1<br />

2<br />

3<br />

4<br />

5<br />

6<br />

7<br />

Linux_069_100_03.indd 78-79<br />

10.2.26 1:35:16 PM


Chapter3<br />

UDP 通 信 の 基 礎<br />

3-2 UDPのプログラミング<br />

UDPパケット 受 信 側 サンプルプログラム<br />

次 は、 受 信 側 のプログラムです。<br />

List 3-8<br />

getaddrinfo()を 利 用 した UDP 受 信 プログラム<br />

まず、AI_PASSIVEを 利 用 してgetaddrinfo()を 実 行 しています(1)。このgetaddrinfo()の<br />

結 果 を、socket()とbind()の 引 数 としてそのまま 利 用 しています(2)。<br />

続 いてgetaddrinfo()によって 確 保 されたメモリを 解 放 しています(3)。recv()を 利 用 して<br />

データを 受 信 し、それを 表 示 したあと、ソケットを 閉 じています(4)。<br />

1<br />

#include <br />

#include <br />

#include <br />

#include <br />

#include <br />

#include <br />

#include <br />

int<br />

main()<br />

{<br />

int sock;<br />

struct addrinfo hints, *res;<br />

int err, n;<br />

char buf[2048];<br />

memset(&hints, 0, sizeof(hints));<br />

hints.ai_family = AF_INET; /* このサンプルはIPv4で 作 成 */<br />

hints.ai_socktype = SOCK_DGRAM;<br />

hints.ai_flags = AI_PASSIVE;<br />

err = getaddrinfo(NULL, "12345", &hints, &res);<br />

if (err != 0) {<br />

printf("getaddrinfo : %s\n", gai_strerror(err));<br />

return 1;<br />

}<br />

1<br />

IPv6 が 未 設 定 の 環 境 を 想 定 する<br />

List 3-7では、getaddrinfo()が 返 す 最 初 の 結 果 を、そのまま 利 用 してソケットを 作 成 し、<br />

sendto()によるパケット 送 信 を 行 っています。そのため、たとえば、IPv6 設 定 を 行 っていな<br />

い 環 境 でgetaddrinfo()が 最 初 に 結 果 として 返 したものがIPv6だった 場 合 、sendto()は 失 敗 し<br />

てしまいます。このような 状 況 は、IPv4を 利 用 してIPv6の 名 前 解 決 が 可 能 な 環 境 で 発 生 します。<br />

この 問 題 を 回 避 するために、sendto()を 一 度 実 行 してみて、その 結 果 を 確 認 したうえで 利 用<br />

するgetaddrinfo()の 結 果 を 決 定 するという 方 法 があります。Chapter 2では、TCPとgetad<br />

drinfo()を 解 説 する 際 に、getaddrinfo()の 各 結 果 に 対 してconnect()を 行 い、 成 功 したものを<br />

最 終 的 に 利 用 していました。UDPソケットにおいても、TCPのサンプルプログラム 同 様 にcon<br />

nect()を 使 う 方 法 もありますが、ここではconnect()の 代 わりにsendto()の 結 果 を 確 認 してい<br />

ます。<br />

なお、 次 のサンプルプログラムはsendto()を 一 度 行 った 直 後 にclose()によってソケットを<br />

閉 じてしまいますが、 必 要 に 応 じてソケットを 閉 じずに 使 うプログラムに 変 更 してください。<br />

List 3-9<br />

IPv6 が 未 設 定 の 環 境 を 想 定 する<br />

2<br />

3<br />

4<br />

sock = socket(res->ai_family, res->ai_socktype, 0);<br />

if (sock < 0) {<br />

perror("socket");<br />

return 1;<br />

}<br />

if (bind(sock, res->ai_addr, res->ai_addrlen) != 0) {<br />

perror("bind");<br />

return 1;<br />

}<br />

freeaddrinfo(res);<br />

memset(buf, 0, sizeof(buf));<br />

n = recv(sock, buf, sizeof(buf), 0);<br />

printf("%s\n", buf);<br />

close(sock);<br />

2<br />

3<br />

4<br />

#include <br />

#include <br />

#include <br />

#include <br />

#include <br />

#include <br />

#include <br />

int<br />

main(int argc, char *argv[])<br />

{<br />

int sock;<br />

struct addrinfo hints, *res0, *res;<br />

int n;<br />

int err;<br />

if (argc != 2) {<br />

fprintf(stderr, "Usage : %s dst\n", argv[0]);<br />

return 1;<br />

}<br />

5<br />

6<br />

7<br />

return 0;<br />

}<br />

/* IPアドレス 表 記 +ホスト 名 両 方 に 対 応 */<br />

80 81<br />

Linux_069_100_03.indd 80-81<br />

10.2.26 1:35:17 PM


Chapter3<br />

UDP 通 信 の 基 礎<br />

3-3 ブロードキャストプログラミング<br />

memset(&hints, 0, sizeof(hints));<br />

hints.ai_family = AF_UNSPEC; /* IPv4/IPv6 両 方 に 対 応 */<br />

hints.ai_socktype = SOCK_DGRAM;<br />

err = getaddrinfo(argv[1], "12345", &hints, &res0);<br />

if (err != 0) {<br />

printf("getaddrinfo : %s\n", gai_strerror(err));<br />

return 1;<br />

}<br />

for (res = res0; res != NULL; res = res->ai_next) {<br />

sock = socket(res->ai_family, res->ai_socktype, 0);<br />

if (sock < 0) {<br />

perror("socket");<br />

return 1;<br />

}<br />

n = sendto(sock, "HELLO", 5, 0, res->ai_addr, res->ai_addrlen);<br />

if (n < 1) {<br />

perror("sendto");<br />

}<br />

1<br />

2<br />

3<br />

4<br />

3-3<br />

<br />

次 に、ひとつのパケットをある 特 定 のネットワークに 対 する「すべてのホスト」 宛 に 送 信 する、<br />

「ブロードキャストアドレス」に 対 して 送 信 する 方 法 を 説 明 します。<br />

ブロードキャスト 送 信 プログラム<br />

UDPでブロードキャストを 利 用 したパケット 送 信 をするときに 必 要 な 操 作 は2つあります。<br />

■<br />

■<br />

1) 宛 先 IPアドレスをブロードキャストIPアドレスにする<br />

2)setsockopt()を 利 用 してソケットにSO_BROADCASTを 設 定 する(setsockopt()につい<br />

1<br />

2<br />

3<br />

close(sock);<br />

5<br />

てはChapter 6を 参 照 )<br />

if (n > 0) {<br />

break;<br />

}<br />

}<br />

freeaddrinfo(res0);<br />

return 0;<br />

}<br />

このサンプルプログラムでは、まずgetaddrinfo()に 渡 すaddrinfo 構 造 体 のパラメータを 設 定<br />

しています(1)。ここでは、AF_UNSPECを 指 定 することによってIPv4とIPv6 両 方 に 対 応 し、<br />

SOCK_DGRAMにすることによってUDPを 指 定 しています。<br />

2の 部 分 は、getaddrinfo()の 結 果 を 最 初 から 順 番 に 試 すためのforループです。このforルー<br />

プは、sendto()が 成 功 するまで 繰 り 返 されます。<br />

forループのなかでは、まず 最 初 にgetaddrinfo()の 各 結 果 が 示 すアドレスファミリによるソ<br />

ケットを 作 成 しています(3)。さらに、そのソケットを 利 用 してsendto()を 行 い(4)、その<br />

ままソケットをclose()しています(5)。<br />

sendto()が 成 功 した 場 合 、そのままforループを 抜 けています(6)。<br />

6<br />

何 もしない 状 態 のUDPソケットでは、ブロードキャストパケットを 送 信 できません。setsock<br />

opt()を 利 用 してソケットに 対 してSO_BROADCASTを 設 定 することで、ブロードキャストパケッ<br />

トを 送 信 できるようになります。この 設 定 を 行 わずにブロードキャストパケットを 送 ろうとす<br />

ると、sendto()が 失 敗 してしまいます。<br />

以 下 に、ブロードキャストパケットを 送 信 するプログラムを 示 します。<br />

List 3-10<br />

ブロードキャスト 送 信 プログラム<br />

#include <br />

#include <br />

#include <br />

#include <br />

#include <br />

#include <br />

int<br />

main()<br />

{<br />

int sock;<br />

struct sockaddr_in addr;<br />

int yes = 1;<br />

int n;<br />

sock = socket(AF_INET, SOCK_DGRAM, 0);<br />

addr.sin_family = AF_INET;<br />

addr.sin_port = htons(12345);<br />

4<br />

5<br />

6<br />

7<br />

82 83<br />

Linux_069_100_03.indd 82-83<br />

10.2.26 1:35:18 PM


Chapter3<br />

UDP 通 信 の 基 礎<br />

3-3 ブロードキャストプログラミング<br />

inet_pton(AF_INET, "255.255.255.255", &addr.sin_addr.s_addr);<br />

3-3<br />

サブネットブロードキャスト<br />

if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST,<br />

(char *)&yes, sizeof(yes)) != 0) {<br />

perror("setsockopt");<br />

return 1;<br />

}<br />

n = sendto(sock, "HELLO", 5, 0, (struct sockaddr *)&addr, sizeof(addr));<br />

if (n < 1) {<br />

perror("sendto");<br />

return 1;<br />

}<br />

1<br />

2<br />

}<br />

close(sock);<br />

return 0;<br />

3<br />

このサンプルによって 送 信 されるパケットの 受 信 は、 本 Chapterの 最 初 に 登 場 した 単 純 な<br />

UDP 受 信 プログラム(List 3-3)で 行 えます。 通 常 の 設 定 では、ブロードキャストであるかない<br />

かに 関 わらず、 指 定 されたUDPポートに 届 くパケットはそのまま 受 信 されます。<br />

このプログラムで 利 用 している「255.255.255.255」という 宛 先 (IPアドレス)は、サブネッ<br />

ト 内 のすべてのノードを 表 します。255.255.255.255が 宛 先 のパケットは、ルータを 越 えま<br />

せん。<br />

昔 は 普 通 にサブネットブロードキャストを 使 えていましたが、DoS(Denial of Service) 攻 撃<br />

などに 多 用 されるようになったため、 現 在 はルータでのブロードキャストパケット 転 送 を 許 可<br />

しないのが 一 般 的 です。 送 信 側 ホストからブロードキャストパケットが 出 ているにも 関 わらず、<br />

受 信 側 ホストにパケットが 届 かない 場 合 には、 受 信 側 のひとつ 手 前 のルータが 転 送 しているか<br />

どうかも 疑 ってみてください。<br />

4<br />

3-2<br />

255.255.255.255 宛 のブロードキャスト<br />

COLUMN<br />

ブロードキャストで 注 意 が 必 要 なのは、ルータを 越 えるブロードキャストです。ルータを 越<br />

えるブロードキャストである「サブネットブロードキャスト」は、IPアドレスのホスト 部 をす<br />

べて1にしたアドレスです。たとえば、「192.168.0.0/24」というサブネットに 対 するサブネッ<br />

トブロードキャストは「192.168.0.255」となります。<br />

サブネットブロードキャストを 利 用 したDoS<br />

昔 、 送 信 元 IPアドレスを 攻 撃 対 象 となる 標 的 のものに 偽 装 して、まったく 関 係 がない 第 三 者 のサブ<br />

ネットブロードキャスト 宛 にICMP Echo 要 求 を 送 信 するというDoS 攻 撃 が 多 く 発 生 しました。その<br />

仕 組 みは 以 下 のようなものです。<br />

まず、 最 初 に 攻 撃 者 は 攻 撃 を 行 いたい 相 手 のIPアドレスを 偽 装 してICMP Echo 要 求 パケットをサ<br />

ブネットブロードキャストで 送 信 します。サブネットブロードキャストでICMP Echo 要 求 を 受 け<br />

取 った 各 ホストは、ICMP Echo 要 求 に 対 して 応 えます。すると、 攻 撃 者 から 出 されたひとつのパケッ<br />

トが 数 倍 に 増 えて 標 的 に 送 信 されてしまいます。<br />

このような 作 業 を 攻 撃 者 が 繰 り 返 すと、 標 的 のネットワーク 資 源 が 浪 費 されてしまい、 標 的 が 通 信<br />

を 行 えない 状 態 が 続 いてしまいます。サブネットブロードキャストによって、まったく 関 係 のない 第<br />

三 者 が 加 害 者 になってしまうため、 現 在 のほとんどのルータではサブネットブロードキャストは 無 効<br />

に 設 定 されています。<br />

5<br />

6<br />

7<br />

84 85<br />

Linux_069_100_03.indd 84-85<br />

10.2.26 1:35:25 PM


Chapter3<br />

UDP 通 信 の 基 礎<br />

3-4 マルチキャストプログラミング<br />

3-4<br />

<br />

return 1;<br />

}<br />

close(sock);<br />

1<br />

マルチキャストも、ブロードキャスト 同 様 にひとつのパケットが 複 数 人 に 届 くことがありま<br />

す。ブロードキャストが「 送 信 者 が 宛 先 を 指 定 する」のに 対 して、マルチキャストは「 欲 しい 人<br />

に 届 く」という 違 いがあります。<br />

ここではマルチキャストプログラミングを 知 るための 基 本 を 解 説 します。マルチキャストそ<br />

のものに 関 する 概 要 は1-4 節 を、さらに 詳 細 なマルチキャストプログラムについてはChapter 13<br />

をそれぞれご 覧 ください。<br />

マルチキャスト 送 信 プログラム<br />

マルチキャストは 非 常 に 複 雑 で 難 しい 技 術 です。しかし、マルチキャストの 難 しさの 多 くは<br />

ネットワーク 側 にあるため、マルチキャストを 利 用 するアプリケーション 作 成 は 非 常 にシンプ<br />

ルです。<br />

以 下 に、マルチキャスト 送 信 プログラムのサンプルプログラムを 示 します。<br />

List 3-11<br />

マルチキャスト 送 信 プログラム<br />

#include <br />

#include <br />

#include <br />

#include <br />

#include <br />

int<br />

main()<br />

{<br />

int sock;<br />

struct sockaddr_in addr;<br />

int n;<br />

sock = socket(AF_INET, SOCK_DGRAM, 0);<br />

addr.sin_family = AF_INET;<br />

addr.sin_port = htons(12345);<br />

inet_pton(AF_INET, "239.192.1.2", &addr.sin_addr.s_addr);<br />

n = sendto(sock, "HELLO", 5, 0, (struct sockaddr *)&addr, sizeof(addr));<br />

if (n < 1) {<br />

perror("sendto");<br />

return 0;<br />

}<br />

ブロードキャストのときとは 違 い、setsockopt()などによる 特 殊 な 処 理 は 必 要 とせず、 送 信<br />

先 のIPアドレスをマルチキャストアドレスにするだけで 送 信 可 能 です。<br />

ただし、sendto()が 正 しく 動 作 するためには 経 路 表 に 適 切 な 経 路 が 記 載 されている 必 要 があ<br />

ります。 経 路 表 に 経 路 がなくてもマルチキャストパケットを 送 信 したい 場 合 には、IP_MULTI<br />

CAST_IFをsetsockopt()で 利 用 します。IP_MULTICAST_IFに 関 してはChapter 13で 解 説 します。<br />

IPv6によるマルチキャストパケット 送 信 を 行 う 場 合 、socket()システムコールの 第 一 引 数 を<br />

AF_INETからAF_INET6に 変 更 し、 宛 先 を 示 すsockaddr_inをsockaddr_in6に 変 更 したうえで、<br />

マルチキャストアドレスもIPv6のマルチキャストアドレスに 変 更 してください。 作 成 したプロ<br />

グラムを 実 行 する 際 には、IPv6に 関 する 適 切 な 経 路 が 経 路 表 に 存 在 しているかどうかの 確 認 も<br />

忘 れずに 行 いましょう。<br />

マルチキャスト 受 信 プログラム<br />

次 に、マルチキャストを 受 信 するプログラムです。 以 下 のサンプルプログラムは、 前 述 した<br />

マルチキャスト 送 信 プログラムからのパケットを 受 け 取 ります。<br />

このサンプルではgetaddrinfo()を 利 用 しています。getaddrinfo()を 利 用 せずに、inet_pton<br />

()などを 利 用 すればもう 少 し 簡 潔 なコードを 書 くことが 可 能 ですが、IPv6 対 応 を 行 いやすいよ<br />

うに、あえてgetaddrinfo()を 利 用 してみました。<br />

List 3-12<br />

マルチキャスト 受 信 プログラム<br />

#include <br />

#include <br />

#include <br />

#include <br />

#include <br />

#include <br />

#include <br />

int<br />

main()<br />

{<br />

int sock;<br />

struct addrinfo hints, *res;<br />

2<br />

3<br />

4<br />

5<br />

6<br />

7<br />

86 87<br />

Linux_069_100_03.indd 86-87<br />

10.2.26 1:35:26 PM


Chapter3<br />

UDP 通 信 の 基 礎<br />

3-4 マルチキャストプログラミング<br />

int err, n;<br />

struct group_req greq;<br />

char buf[2048];<br />

memset(&hints, 0, sizeof(hints));<br />

hints.ai_family = AF_INET;<br />

hints.ai_socktype = SOCK_DGRAM;<br />

hints.ai_flags = AI_PASSIVE;<br />

err = getaddrinfo(NULL, "12345", &hints, &res);<br />

if (err != 0) {<br />

printf("getaddrinfo : %s\n", gai_strerror(err));<br />

return 1;<br />

}<br />

sock = socket(res->ai_family, res->ai_socktype, 0);<br />

if (sock < 0) {<br />

perror("socket");<br />

return 1;<br />

}<br />

if (bind(sock, res->ai_addr, res->ai_addrlen) != 0) {<br />

perror("bind");<br />

return 1;<br />

}<br />

freeaddrinfo(res);<br />

memset(buf, 0, sizeof(buf));<br />

n = recv(sock, buf, sizeof(buf), 0);<br />

printf("%s\n", buf);<br />

close(sock);<br />

return 0;<br />

}<br />

先 ほど 触 れたように、マルチキャストパケットを 受 け 取 るには、マルチキャストグループに<br />

「 参 加 (JOIN)」しなくてはなりません。マルチキャストグループへの 参 加 には、IPPROTO_IP<br />

+MCAST_JOIN_GROUPでsetsockopt()を 利 用 します。<br />

MCAST_JOIN_GROUPで 利 用 するgroup_req 構 造 体 は、/usr/include/netinet/in.h 内 で 以 下 の<br />

ように 宣 言 されています。<br />

List 3-13<br />

group_req 構 造 体<br />

/* Multicast group request. */<br />

struct group_req<br />

{<br />

uint32_t gr_interface;<br />

struct sockaddr_storage gr_group; /* グループアドレス */<br />

};<br />

/* インターフェース 番 号 */<br />

1<br />

2<br />

3<br />

4<br />

memset(&hints, 0, sizeof(hints));<br />

hints.ai_family = AF_INET;<br />

hints.ai_socktype = SOCK_DGRAM;<br />

err = getaddrinfo("239.192.1.2", NULL, &hints, &res);<br />

if (err != 0) {<br />

printf("getaddrinfo : %s\n", gai_strerror(err));<br />

return 1;<br />

}<br />

memset(&greq, 0, sizeof(greq));<br />

greq.gr_interface = 0; /* 任 意 のネットワークインターフェースを 利 用 */<br />

/* getaddrinfo()の 結 果 をgroup_req 構 造 体 へコピー */<br />

memcpy(&greq.gr_group, res->ai_addr, res->ai_addrlen);<br />

freeaddrinfo(res);<br />

/* MCAST_JOIN_GROUPを 利 用 してマルチキャストグループへJOIN */<br />

if (setsockopt(sock, IPPROTO_IP, MCAST_JOIN_GROUP, (char<br />

sizeof(greq)) != 0) {<br />

perror("setsockopt");<br />

return 1;<br />

}<br />

*)&greq,<br />

setsockopt()は、bind()を 行 ったあとで 利 用 する 必 要 があります。そしてマルチキャストグ<br />

ループへの 参 加 を 行 うためには、 参 加 するグループを 表 わすマルチキャストアドレスと 利 用 す<br />

るインターフェースを 指 定 しなくてはなりません。<br />

インターフェースに0を 指 定 することにより、 明 示 的 に 利 用 するインターフェースを 指 定 し<br />

ないこともできます。ただし、bind()を 行 ったインターフェースと、MCAST_JOIN_GROUPを<br />

gr_interface=0で 指 定 したときのインターフェースが 一 致 する 保 障 はありません。<br />

一 度 JOINしたマルチキャストグループから「 脱 退 (LEAVE)」するには、setsockopt()に<br />

MCAST_LEAVE_GROUPを 指 定 しますが、ソケットをclose()することで 同 様 の 効 果 を 得 られます。<br />

MCAST_JOIN_GROUPは、IPv4/IPv6 両 方 で 利 用 可 能 です。group_req 構 造 体 のメンバがイン<br />

ターフェース 番 号 やsockaddr_storage 構 造 体 である 点 からも、それがうかがえます(sockaddr_<br />

storageに 関 してはChapter 11を 参 照 )。<br />

これまで、 旧 APIであるIP_ADD_MEMBERSHIPはip_mreq 構 造 体 を 利 用 し、IPV6_ADD_MEM<br />

BERSHIPはipv6_mreqを 利 用 するなど、プロトコル 依 存 の 構 造 体 を 利 用 していました。<br />

5<br />

6<br />

7<br />

88 89<br />

Linux_069_100_03.indd 88-89<br />

10.2.26 1:35:27 PM


Chapter3<br />

UDP 通 信 の 基 礎<br />

3-5 UDPソケットの「 名 前 」とbind()<br />

List 3-14<br />

プロトコルに 依 存 した 構 造 体<br />

/* IPv4 multicast request. */<br />

struct ip_mreq<br />

{<br />

struct in_addr imr_multiaddr; /* マルチキャストグループのIPアドレス */<br />

struct in_addr imr_interface; /* インターフェースのローカルIPアドレス */<br />

};<br />

/* Likewise, for IPv6. */<br />

struct ipv6_mreq<br />

{<br />

struct in6_addr ipv6mr_multiaddr; /* マルチキャストグループのIPv6 IPアドレス */<br />

unsigned int ipv6mr_interface; /* ローカルインターフェース */<br />

};<br />

上 記 のようにsetsockopt()に 渡 す 引 数 が 異 なっていたので、IP_ADD_MEMBERSHIPとIPV6_<br />

ADD_MEMBERSHIPを 両 方 扱 うのは 面 倒 だったのですが、MCAST_JOIN_GROUPを 使 うことによっ<br />

てIPv4/IPv6 両 用 のマルチキャストコードが 書 きやすくなっています。IP_ADD_MEMBERSHIP<br />

とIPV6_ADD_MEMBERSHIPに 関 するサンプルプログラムは、Chapter 13をご 覧 ください。<br />

また、setsockopt()は、bind()を 行 ったあとで 利 用 する 必 要 があります。そしてマルチキャ<br />

ストグループへの 参 加 を 行 うためには、 参 加 するグループを 表 すマルチキャストアドレスと 利<br />

用 するインターフェースを 指 定 しなくてはなりません。<br />

インターフェースに0を 指 定 することにより、 明 示 的 に 利 用 するインターフェースを 指 定 し<br />

ないこともできます。ただし、bind()を 行 ったインターフェースとMCAST_JOIN_GROUPをgr_<br />

interface=0で 指 定 したときのインターフェースが 一 致 する 保 証 はありません。<br />

一 度 JOINしたマルチキャストグループから「 脱 退 (LEAVE)」するには、setsockoptにIP_<br />

DROP_MEMBERSHIPを 指 定 します。ソケットをclose()することでも 同 様 の 効 果 を 得 られます。<br />

3-5<br />

UDPbind<br />

ここまで 紹 介 したUDPサンプルプログラムでは、データ 送 信 側 はbind()を 行 わず、データ<br />

受 信 側 だけがbind()を 行 っています。しかし、bind()の 役 目 は「 名 前 を 付 ける」ことであって、<br />

bind()そのものが 送 受 信 の 可 否 を 決 定 付 けるものではありません。bind()を 行 わなくても<br />

UDPでデータを 受 信 できますし、 送 信 側 で 意 図 的 にbind()することもあります。<br />

では、ソケットに 対 して「 名 前 を 付 ける」とはどのようなことなのでしょうか?<br />

を 説 明 するために、 以 下 のようなサンプルプログラムを 作 成 しました。<br />

そのこと<br />

List 3-15<br />

ソケットの 名 前 を 取 得 する<br />

#include <br />

#include <br />

#include <br />

#include <br />

#include <br />

#include <br />

void<br />

print_my_port_num(int sock)<br />

{<br />

struct sockaddr_in s;<br />

socklen_t sz = sizeof(s);<br />

getsockname(sock, (struct sockaddr *)&s, &sz);<br />

printf("%d\n", ntohs(s.sin_port));<br />

}<br />

int<br />

main()<br />

{<br />

int sock;<br />

struct sockaddr_in addr;<br />

int n;<br />

sock = socket(AF_INET, SOCK_DGRAM, 0);<br />

print_my_port_num(sock);<br />

addr.sin_family = AF_INET;<br />

addr.sin_port = htons(12345);<br />

inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr.s_addr);<br />

n = sendto(sock, "HELLO", 5, 0, (struct sockaddr *)&addr, sizeof(addr));<br />

if (n < 1) {<br />

perror("sendto");<br />

return 1;<br />

}<br />

print_my_port_num(sock);<br />

close(sock);<br />

return 0;<br />

}<br />

このサンプルプログラムは、getsockname()システムコール(Chapter 6を 参 照 )を 利 用 して<br />

ソケットに 付 いている「 名 前 」を 取 得 し、そのうちのポート 番 号 部 分 をprintf()で 表 示 していま<br />

す。この「 名 前 の 確 認 」は、 以 下 の2カ 所 で 行 われています。<br />

1<br />

2<br />

3<br />

4<br />

5<br />

6<br />

7<br />

■ socket()システムコールによってソケットを 作 成 した 直 後<br />

90 91<br />

Linux_069_100_03.indd 90-91<br />

10.2.26 1:35:28 PM


Chapter3<br />

UDP 通 信 の 基 礎<br />

3-5 UDPソケットの「 名 前 」とbind()<br />

■<br />

sendto()の 直 後<br />

このサンプルプログラムを 実 行 すると、 最 初 の 部 分 では「0」というポート 番 号 が 表 示 され、<br />

次 の 部 分 では 何 らかのポート 番 号 が 表 示 されます。これにより、 作 成 されたソケットから 送 信<br />

されるUDPパケットの 送 信 元 ポート 番 号 は、sendto()システムコールを 利 用 したあとに 決 定<br />

されているのがわかります。<br />

このようなタイミングで 送 信 元 ポート 番 号 が 付 くのは、ポート 番 号 をbind()で 指 定 していな<br />

いソケットを 使 おうとすると、カーネル 内 部 で 自 動 的 にポート 番 号 が 割 り 当 てられるためです。<br />

このサンプルではbind()を 利 用 していませんが、「 名 前 が 付 く」タイミングをこれで 理 解 して<br />

もらえるかと 思 います。<br />

では、 次 はbind()を 行 った 場 合 にどうなるかを 見 てみましょう。<br />

List 3-16<br />

bind()を 使 った 名 前 確 認<br />

#include <br />

#include <br />

#include <br />

#include <br />

#include <br />

#include <br />

void<br />

print_my_port_num(int sock)<br />

{<br />

struct sockaddr_in s;<br />

socklen_t sz = sizeof(s);<br />

getsockname(sock, (struct sockaddr *)&s, &sz);<br />

printf("%d\n", ntohs(s.sin_port));<br />

}<br />

int<br />

main()<br />

{<br />

int sock;<br />

struct sockaddr_in addr;<br />

struct sockaddr_in myname;<br />

int n;<br />

sock = socket(AF_INET, SOCK_DGRAM, 0);<br />

print_my_port_num(sock);<br />

addr.sin_family = AF_INET;<br />

addr.sin_port = htons(12345);<br />

inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr.s_addr);<br />

myname.sin_family = AF_INET;<br />

myname.sin_addr.s_addr = INADDR_ANY;<br />

myname.sin_port = htons(54321);<br />

bind(sock, (struct sockaddr *)&myname, sizeof(myname));<br />

print_my_port_num(sock);<br />

n = sendto(sock, "HELLO", 5, 0, (struct sockaddr *)&addr, sizeof(addr));<br />

if (n < 1) {<br />

perror("sendto");<br />

return 1;<br />

}<br />

print_my_port_num(sock);<br />

close(sock);<br />

return 0;<br />

}<br />

このサンプルプログラムでは、 以 下 の3カ 所 でソケットの「 名 前 」を 確 認 しています。<br />

■<br />

■<br />

■<br />

1)socket()システムコールによってソケットを 作 成 した 直 後<br />

2)bind()の 直 後<br />

3)sendto()を 行 ったあと<br />

実 際 には、1)のタイミングでは「0」と 出 力 されますが、2)と3)のタイミングでは「12345」<br />

と 出 力 されます。ここから、 最 初 のソケット 作 成 直 後 以 外 は、bind()によって 割 り 当 てたポー<br />

ト 番 号 が 自 分 の「 名 前 」の 一 部 として 登 録 されていることがわかると 思 います。このように、<br />

bind()は 送 信 元 のポート 番 号 をソケットに 対 して 関 連 付 けます。<br />

何 げなく 使 いがちなbind()システムコールですが、 意 図 的 にbind()することで 指 定 どおり<br />

の 送 信 元 ポート 番 号 でのUDP 通 信 が 可 能 になります。<br />

送 信 元 ポート 番 号 を 指 定 するということは、 逆 に 考 えるとデータを 受 信 する 待 ち 受 けポート<br />

番 号 の 指 定 でもあるということです。そう 考 えると、なぜ 受 信 側 サンプルプログラムで 毎 回<br />

bind()を 行 っているのかが 見 えてきます。 送 信 側 プログラムと 受 信 側 プログラムは、 何 らかの<br />

方 法 で 利 用 するUDPポート 番 号 に 関 しての 合 意 形 成 があらかじめ 確 立 されており、 受 信 側 はそ<br />

れに 従 ったポート 番 号 でbind()を 行 っているというわけです。この「 合 意 形 成 」としては、た<br />

とえば、 静 的 に「このポート 番 号 を 使 う」とプログラム 内 に 埋 め 込 んだり、プロトコルの 中 に<br />

規 定 としてポート 番 号 が 記 述 されていたり、 動 的 にポート 番 号 などを 割 り 当 てるなどの 方 法 が<br />

あります。<br />

送 信 用 UDPソケットに 対 するbind()は、ポート 番 号 を 指 定 するだけではなく、 送 信 元 IPア<br />

ドレスを 指 定 するためにも 利 用 可 能 です。たとえば、インターフェースが2つ 以 上 存 在 してい<br />

て、 送 信 元 のIPアドレスを 特 定 のIPアドレスにしたい 場 合 にも、bind()を 行 ってからsendto()<br />

を 行 うという 方 法 が 使 えます。<br />

1<br />

2<br />

3<br />

4<br />

5<br />

6<br />

7<br />

92 93<br />

Linux_069_100_03.indd 92-93<br />

10.2.26 1:35:29 PM


Chapter3<br />

UDP 通 信 の 基 礎<br />

3-5 UDPソケットの「 名 前 」とbind()<br />

UDP での「 返 信 」<br />

UDPソケットに 対 するbind()の 意 味 がわかったところで、 次 はやり 取 りを 行 うUDPソケッ<br />

トのサンプルプログラムを 示 したいと 思 います。<br />

以 下 のサンプルは、UDPのポート12345で 待 ち 続 けます。 待 ち 続 けているUDPポート12345<br />

にデータが 到 着 し、recvfrom()によってデータを 受 け 取 ると、 受 け 取 ったUDPパケットの 送<br />

信 IPアドレス+ 送 信 元 ポートが 示 す 送 信 者 に 対 してsendto()が 行 われます。このサンプルプロ<br />

グラムは、このように 受 け 取 った 相 手 に 対 してUDPでそのまま 返 信 するという 動 作 をwhile(1)<br />

によって 繰 り 返 し 続 けます。<br />

List 3-17<br />

UDP パケットのやりとりを 行 うサンプルプログラム<br />

#include <br />

#include <br />

#include <br />

#include <br />

#include <br />

#include <br />

#include <br />

int<br />

main()<br />

{<br />

int sock;<br />

struct sockaddr_in addr;<br />

struct sockaddr_in senderinfo;<br />

socklen_t addrlen;<br />

char buf[2048];<br />

char senderstr[16];<br />

int n;<br />

/* AF_INET+SOCK_DGRAMなので、IPv4のUDPソケット */<br />

sock = socket(AF_INET, SOCK_DGRAM, 0);<br />

/* 待 ち 受 けポート 番 号 を12345にするためにbind()を 行 う */<br />

addr.sin_family = AF_INET;<br />

addr.sin_port = htons(12345);<br />

addr.sin_addr.s_addr = INADDR_ANY;<br />

bind(sock, (struct sockaddr *)&addr, sizeof(addr));<br />

while (1) {<br />

memset(buf, 0, sizeof(buf));<br />

/* recvfrom()を 利 用 してUDPソケットからデータを 受 信 */<br />

addrlen = sizeof(senderinfo);<br />

n = recvfrom(sock, buf, sizeof(buf) - 1, 0,<br />

(struct sockaddr *)&senderinfo, &addrlen);<br />

/* 送 信 元 に 関 する 情 報 を 表 示 */<br />

inet_ntop(AF_INET, &senderinfo.sin_addr, senderstr, sizeof(senderstr));<br />

printf("recvfrom : %s, port=%d\n", senderstr, ntohs(senderinfo.sin_port));<br />

/* UDPで 返 信 */<br />

sendto(sock, buf, n, 0, (struct sockaddr *)&senderinfo, addrlen);<br />

/* 送 信 元 に 関 する 情 報 をもう 一 度 表 示 */<br />

printf("sent data to : %s, port=%d\n", senderstr,<br />

ntohs(senderinfo.sin_port));<br />

}<br />

/* このサンプルコードはここへは 到 達 しません */<br />

close(sock);<br />

return 0;<br />

}<br />

次 は、UDPの 返 信 アプリケーションに 対 してUDPパケットを 送 信 して、 返 信 を 受 け 取 る 側<br />

のプログラムです。このサンプルを 実 行 するには、たとえば「./a.out 127.0.0.1」のよう<br />

に 実 行 時 にUDP 返 信 アプリケーションのIPv4アドレスを 指 定 してください。<br />

List 3-18<br />

UDP パケットの 送 信 & 受 信 プログラム<br />

#include <br />

#include <br />

#include <br />

#include <br />

#include <br />

#include <br />

#include <br />

void<br />

print_my_port_num(int sock)<br />

{<br />

struct sockaddr_in s;<br />

socklen_t sz = sizeof(s);<br />

getsockname(sock, (struct sockaddr *)&s, &sz);<br />

printf("%d\n", ntohs(s.sin_port));<br />

}<br />

int<br />

main(int argc, char *argv[])<br />

{<br />

int sock;<br />

struct sockaddr_in addr;<br />

struct sockaddr_in senderinfo;<br />

socklen_t senderinfolen;<br />

int n;<br />

char buf[2048];<br />

1<br />

2<br />

3<br />

4<br />

5<br />

6<br />

7<br />

94 95<br />

Linux_069_100_03.indd 94-95<br />

10.2.26 1:35:30 PM


Chapter3<br />

UDP 通 信 の 基 礎<br />

3-6 ソケットバッファ<br />

if (argc != 2) {<br />

fprintf(stderr, "Usage : %s dstipaddr\n", argv[0]);<br />

return 1;<br />

}<br />

sock = socket(AF_INET, SOCK_DGRAM, 0);<br />

3-4<br />

UDP 返 信 サンプルの 動 作<br />

<br />

<br />

<br />

<br />

<br />

<br />

1<br />

addr.sin_family = AF_INET;<br />

addr.sin_port = htons(12345);<br />

inet_pton(AF_INET, argv[1], &addr.sin_addr.s_addr);<br />

n = sendto(sock, "HELLO", 5, 0, (struct sockaddr *)&addr, sizeof(addr));<br />

if (n < 1) {<br />

perror("sendto");<br />

return 1;<br />

}<br />

<br />

<br />

<br />

<br />

<br />

2<br />

print_my_port_num(sock);<br />

memset(buf, 0, sizeof(buf));<br />

senderinfolen = sizeof(senderinfo);<br />

recvfrom(sock, buf, sizeof(buf), 0,<br />

(struct sockaddr *)&senderinfo, &senderinfolen);<br />

printf("%s\n", buf);<br />

close(sock);<br />

return 0;<br />

}<br />

このサンプルでは、sendto()に 利 用 したソケットをそのまま 使 ってrecvfrom()を 行 ってい<br />

ます。これにより、UDP 返 信 アプリケーションからの「 返 信 」が、そのままこのソケットに 到<br />

着 します。<br />

また、UDP 返 信 アプリケーションがsendto()していると 主 張 するprintf()の 出 力 結 果 と 比 べ<br />

られるように、getsockname()によって 取 得 した 自 分 のソケットに 割 り 当 てられた 送 信 元 ポー<br />

ト 番 号 がprintf()で 表 示 されるようにしてあります。<br />

これら2つのサンプルコードの 動 作 は 図 3-4のようになります。<br />

待 ち 受 け 側 であるList 3-17のプログラムがUDPポート 番 号 12345で 待 ち 受 けています。List<br />

3-18のプログラムが 待 ち 受 けを 行 っているホストのUDPポート 番 号 12345に 対 してsendto()を<br />

行 います。このとき、3-18 側 の 送 信 元 UDPポート 番 号 は43657 ( 注 3-4) であるとします。<br />

3-18 側 からのUDPパケットを、recvfrom()によって 受 け 取 った3-17 側 は、3-18のホスト 宛 の<br />

UDPパケットをポート 番 号 43657で 送 信 します。3-18 側 は、recvfrom()によって3-17 側 からの<br />

UDPパケットを 受 け 取 ります。<br />

さて、このようなUDPによる「 返 信 」ですが、さまざまなところで 活 用 されています。たと<br />

えば、ユーザがホストの 名 前 解 決 を 行 うときのDNSへのQueryはUDPで 行 われますが、 上 記 サ<br />

ンプルに 近 い 形 で「DNSサーバはQueryを 受 け 取 った 送 信 元 に 対 してUDPで 返 信 」が 行 われます。<br />

また、たとえばUPnP(Universal Plug and Play)のSSDP(Simple Service Discovery Proto<br />

col)のようにマルチキャストで 機 器 発 見 用 のQueryがサブネット 上 に 送 信 され、ネットワーク<br />

上 の 機 器 側 はマルチキャストで 送 信 されたUDPパケットの 送 信 元 に 対 して「UDPで 返 信 を 行<br />

う」という 用 途 もあります。<br />

3-6<br />

<br />

3<br />

4<br />

5<br />

6<br />

Linuxのカーネルは、sendto()が 利 用 された 瞬 間 に 直 接 ネットワークにパケットを 送 信 して<br />

いるわけではありません。また、ネットワークからのパケット 受 信 が、そのままrecvfrom()<br />

と 直 結 しているわけでもありません。<br />

ユーザからのデータはLinuxカーネルに 渡 されるとソケットバッファという 内 部 バッファに<br />

注 3-4:43657は 仮 の 値 です。プログラム 実 行 ごとに3-17 側 の 送 信 元 ポート 番 号 は 変 化 します。<br />

7<br />

96 97<br />

Linux_069_100_03.indd 96-97<br />

10.2.26 1:35:31 PM


Chapter3<br />

UDP 通 信 の 基 礎<br />

3-6 ソケットバッファ<br />

格 納 され、 準 備 が 整 った 時 点 でネットワークに 送 信 されます。また、Linuxカーネルはパケッ<br />

トを 受 信 するとソケットバッファに 格 納 します。<br />

3-5 送 信 用 ソケットバッファと sendto()システムコール<br />

ソケットに 設 定 されているソケットバッファの 大 きさを 知 るには、getsockopt()システム<br />

コールで SO_SNDBUFやSO_RCVBUFを 指 定 して 値 を 取 得 します。setsockopt()でSO_SNDBUFや<br />

SO_RCVBUFを 指 定 しない 状 態 でのデフォルト 値 はシステム 内 で 一 定 です。Linuxカーネルに 設<br />

定 されているデフォルト 値 を 知 りたい 場 合 には、 以 下 のコマンドで 知 ることも 可 能 です。<br />

1<br />

<br />

<br />

<br />

書 き 込 みバッファ<br />

sysctl net.core.wmem_default<br />

<br />

読 み 込 みバッファ<br />

2<br />

<br />

sysctl net.core.rmem_default<br />

<br />

<br />

<br />

<br />

なお、SO_SNDBUFやSO_RCVBUFに 大 きめの 値 を 設 定 したい 場 合 には、ソケットバッファの<br />

最 大 値 にもご 注 意 ください。Linuxカーネルの/net/core/sock.cでは、SO_RCVBUFの 設 定 時 に<br />

以 下 のように 実 装 されています。<br />

3<br />

List 3-20<br />

ソケットバッファの 最 大 値<br />

ソケットバッファは、ネットワークカードのキューに 対 してパケットの 追 加 を 行 ったり、 逆<br />

にキューからデータを 取 得 します。パケットの 送 受 信 などのネットワークが 関 係 する 処 理 のタ<br />

イミングは、ネットワークやネットワークカードの 状 態 に 応 じて 変 化 します。<br />

このタイミングは、アプリケーションが 動 作 しているプロセスがLinuxカーネルのスケジュー<br />

ラによってCPUを 利 用 できるタイミングとは 独 立 したタイミングで 動 作 しています。そのため、<br />

アプリケーションの 扱 いたいデータをネットワーク 経 由 で 送 受 信 するには、Linuxカーネル 内<br />

にソケットバッファのように 一 時 的 に 格 納 できる 場 所 が 求 められます。<br />

音 声 や 動 画 などのマルチメディアデータをやり 取 りするUDPアプリケーションを 書 いている<br />

とき、ソケットバッファの 存 在 がパケット 喪 失 数 に 反 映 される 場 合 があります。これは、アプ<br />

リケーションがrecv()などでカーネル 内 のソケットバッファからデータを 取 り 出 したり、<br />

send()などでデータをカーネルに 渡 すタイミングと 実 際 のパケット 処 理 タイミングがずれた<br />

り、ネットワーク 上 でのデータ 到 着 がバースト 状 に 発 生 した 場 合 に 発 生 しがちです。<br />

ソケットバッファを 設 定 するには、setsockopt()をSOL_SOCKET+SO_RCVBUFもしくは<br />

SO_SNDBUFで 利 用 します。SO_RCVBUFが 受 信 用 のバッファで、SO_SNDBUFが 送 信 用 のバッファ<br />

です。<br />

List 3-19<br />

ソケットバッファの 設 定<br />

int bufsz = 100000;<br />

if (setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char *)&bufsz, sizeof(bufsz)) !=<br />

0) {<br />

perror("setsockopt");<br />

....エラー 処 理<br />

}<br />

case SO_RCVBUF:<br />

/* Don’t error on this BSD doesn’t and if you think<br />

about it this is right. Otherwise apps have to<br />

play ‘guess the biggest size’ games. RCVBUF/SNDBUF<br />

are treated in BSD as hints */<br />

if (val > sysctl_rmem_max)<br />

val = sysctl_rmem_max;<br />

このように、SO_SNDBUFやSO_RCVBUFに 与 えられた 設 定 値 が 大 きいと、エラーにならずに<br />

Linuxカーネルに 設 定 されているソケットバッファ 最 大 値 に 自 動 的 に 変 更 されます。<br />

ソケットバッファの 最 大 値 は 以 下 のコマンドで 調 べられます。<br />

書 き 込 みバッファ<br />

sysctl net.core.wmem_max<br />

読 み 込 みバッファ<br />

sysctl net.core.rmem_max<br />

ソケットバッファに 設 定 可 能 な 最 小 値 にも 注 意 が 必 要 です。/net/core/sock.cでは 下 限 値 に<br />

関 して 以 下 のように 処 理 しています。<br />

4<br />

5<br />

6<br />

7<br />

98 99<br />

Linux_069_100_03.indd 98-99<br />

10.2.26 1:35:32 PM


Chapter3<br />

UDP 通 信 の 基 礎<br />

List 3-21<br />

ソケットバッファの 最 小 値<br />

/*<br />

* We double it on the way in to account for<br />

* "struct sk_buff" etc. overhead. Applications<br />

* assume that the SO_RCVBUF setting they make will<br />

* allow that much actual data to be received on that<br />

* socket.<br />

*<br />

* Applications are unaware that "struct sk_buff" and<br />

* other overheads allocate from the receive buffer<br />

* during socket buffer allocation.<br />

*<br />

* And after considering the possible alternatives,<br />

* returning the value we actually used in getsockopt<br />

* is the most desirable behavior.<br />

*/<br />

if ((val * 2) < SOCK_MIN_RCVBUF)<br />

sk->sk_rcvbuf = SOCK_MIN_RCVBUF;<br />

else<br />

sk->sk_rcvbuf = val * 2;<br />

break;<br />

SOCK_MIN_SNDBUFとSOCK_MIN_RCVBUFは、Linuxカーネルソースの/include/net/sock.hに<br />

て 以 下 のように 設 定 されています。<br />

List 3-22<br />

SOCK_MIN_SNDBUF と SOCK_MIN_RCVBUF の 既 定 値<br />

#define SOCK_MIN_SNDBUF 2048<br />

#define SOCK_MIN_RCVBUF 256<br />

このように、setsockopt()が 成 功 したように 見 えても、アプリケーションが 設 定 したいと 考<br />

えているソケットバッファの 大 きさが 必 ずしも 設 定 されているわけではないのでご 注 意 くださ<br />

い。 正 確 にソケットバッファの 設 定 値 を 知 る 場 合 には、setsockopt()のあとにgetsockopt()で<br />

実 際 の 設 定 値 を 確 認 する 必 要 があります。<br />

3-7<br />

Chapter 3<br />

100<br />

Chapter 3では、UDPによる 通 信 の 基 礎 として、ユニキャスト、ブロードキャスト、マルチキャ<br />

ストについて 簡 単 なサンプルプログラムを 示 しました。インターネットでは、TCP 通 信 だけでな<br />

く、このUDP 通 信 が 活 用 される 場 面 も 多 々あります。マルチキャストプログラミングについて<br />

は、Chapter 13で 詳 しく 紹 介 します。<br />

Linux_069_100_03.indd 100<br />

10.2.26 1:35:33 PM

Hurra! Ihre Datei wurde hochgeladen und ist bereit für die Veröffentlichung.

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!