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