分散型アプリケーションを構築する場合、通常は、これまでに説明したメカニズムで十分対応できます。この節では、拡張機能について説明します。
ストリームソケットの抽象化には、帯域外データが含まれます。帯域外データは、接続されたストリームソケットペア間の論理的に独立した伝送チャネルです。帯域外データは通常データとは無関係に配信されます。帯域外データ機能が使用される場合、一度に 1 つ以上の帯域外メッセージが確実に配信されなければなりません。このメッセージには 1 バイト以上のデータを含むことができます。また、いつでも 1 つ以上のメッセージの配信を保留できます。
帯域内シグナリングでは、緊急データは通常データと一緒に順番どおりに配信され、メッセージは通常データストリームから抽出されます。抽出されたメッセージは個別に格納されます。したがって、ユーザーは中間のデータをバッファリングせずに、緊急データを順番どおりに受信するか、順不同で受信するかを選択できます。
MSG_PEEK を使用すると、帯域外データを先読みできます。ソケットにプロセスグループがある場合は、その存在がプロトコルに通知される時に SIGURG シグナルが生成されます。プロセスは適切な fcntl(2) 呼び出しを使用して、プロセスグループまたはプロセス ID が SIGURG を配信するように設定できます (SIGIO については、割り込み方式のソケット入出力を参照)。複数のソケットに配信待ちの帯域外データがある場合は、例外状況用に select(3C) 呼び出し、どのソケットがこのようなデータを保留しているかを判断してください。
帯域外データが送信された位置のデータストリームには、論理マークが置かれます。リモートログインアプリケーションとリモートシェルアプリケーションは、この機能を使用してクライアントプロセスとサーバープロセス間にシグナルを伝達します。シグナルが受信された時点で、データストリームの論理マークまでのデータはすべて破棄されます。
帯域外メッセージデータを送信するには、MSG_OOB フラグを send(3SOCKET) または sendto(3SOCKET) に指定します。ただし、帯域外データを受信するには、MSG_OOB フラグを recvfrom(3SOCKET) または recv(3SOCKET) に指定します。ただし、帯域外データを順番どおりに取得する場合、MSG_OOB フラグは必要ありません。SIOCATMARK ioctl(2) は、読み取りポインタが現在、データストリーム内のマークを指しているかどうかを示します。
int yes; ioctl(s, SIOCATMARK, &yes);
yes が 1 で返される場合、次の読み取りはマークのあとのデータを返します。yes が 1 でない場合は、帯域外データが到着したと想定して、次の読み取りは帯域外シグナルを送信する前にクライアントによって送信されたデータを提供します。割り込みシグナルまたは終了シグナルを受信したときに出力をフラッシュするリモートログインプロセス内のルーチンを以下に示します。このコードは通常データを破棄を示すマークまで読み取った後、帯域外バイトを読み取ります。
プロセスは、初めにマークまでを読み取らずに、帯域外データの読み取りまたは先読みを行うこともできます。基底のプロトコルが通常データと一緒に帯域内にある緊急データを配信するときに、その存在だけを前もって通知する場合、このようなデータにアクセスすることはより困難になります。このようなタイプのプロトコルの例としては、TCP (インターネットファミリにソケットストリームを提供するときに使用されるプロトコル) があります。このようなプロトコルでは、MSG_OOB フラグを指定して recv(3SOCKET) を呼び出したときに、帯域外バイトが到着していないことがあります。このような場合、呼び出しはエラー EWOULDBLOCK を返します。また、入力バッファー内の帯域内データの量によっては、ピアはバッファーが空になるまで (通常のフロー制御によって) 緊急データを送信できなくなる場合があります。この場合、プロセスが待ち行列に入ったデータを十分に読み取って入力バッファーをクリアしてからでないと、ピアは緊急データを送信できません。
#include <sys/ioctl.h> #include <sys/file.h> ... oob() { int out = FWRITE; char waste[BUFSIZ]; int mark = 0; /* ローカル端末出力をフラッシュする */ ioctl(1, TIOCFLUSH, (char *) &out); while(1) { if (ioctl(rem, SIOCATMARK, &mark) == -1) { perror("ioctl"); break; } if (mark) break; (void) read(rem, waste, sizeof waste); } if (recv(rem, &mark, 1, MSG_OOB) == -1) { perror("recv"); ... } ... }
ソケットストリームのインライン (帯域内) にある緊急データの位置を保持する機能もあります。この機能は、ソケットレベルのオプションである SO_OOBINLINE として提供されます。使用法については、getsockopt(3SOCKET) のマニュアルページを参照してください。このソケットレベルのオプションを使用すると、緊急データの位置を保持できます。ただし、MSG_OOB フラグを指定しない場合、通常データストリームにおいてマークの直後にある緊急データが返されます。複数の緊急指示を受信するとマークは移動しますが、帯域外データが消失することはありません。
一部のアプリケーションは、ブロックしないソケットを必要とします。たとえば、要求がすぐに完了できない場合、サーバーはエラーコードを返して、その要求を実行しないことがあります。このようなエラーが発生した場合、プロセスは要求が完了するまで待ち、結果として中断されます。このようなアプリケーションではソケットを作成および接続したあと、次の例に示すように、fcntl(2) 呼び出しを発行してソケットを非ブロックに設定します。
#include <fcntl.h> #include <sys/file.h> ... int fileflags; int s; ... s = socket(AF_INET6, SOCK_STREAM, 0); ... if (fileflags = fcntl(s, F_GETFL, 0) == -1) perror("fcntl F_GETFL"); exit(1); } if (fcntl(s, F_SETFL, fileflags | FNDELAY) == -1) perror("fcntl F_SETFL, FNDELAY"); exit(1); } ...
非ブロックソケットで入出力を行う場合は、操作が正常にブロックする場合に発生する、errno.h 内のエラー EWOULDBLOCK を確認してください。accept(3SOCKET)、connect(3SOCKET)、send(3SOCKET)、recv(3SOCKET)、read(2)、および write(2) はすべて EWOULDBLOCK を返すことができます。send(3SOCKET) などの操作を完全には実行できないが、部分的な書き込みは可能である場合 (ストリームソケットを使用する場合など)、送信できるデータはすべて処理されます。そして、戻り値は実際に送信された量になります。
複数の要求を同時に処理するアプリケーションでは、プロセス間の非同期通信が必要です。非同期ソケットは SOCK_STREAM タイプである必要があります。ソケットを非同期にするには、次に示すように、 fcntl(2) 呼び出しを実行します。
#include <fcntl.h> #include <sys/file.h> ... int fileflags; int s; ... s = socket(AF_INET6, SOCK_STREAM, 0); ... if (fileflags = fcntl(s, F_GETFL ) == -1) perror("fcntl F_GETFL"); exit(1); } if (fcntl(s, F_SETFL, fileflags | FNDELAY | FASYNC) == -1) perror("fcntl F_SETFL, FNDELAY | FASYNC"); exit(1); } ...
ソケットを初期化および接続して、非ブロックと非同期に設定したあと、通信はファイルを非同期で読み書きする場合のように行われます。データ転送を開始するには、send(3SOCKET)、write(2)、recv(3SOCKET)、または read(2) を使用します。データ転送を完了するには、シグナル (割り込み) 方式の入出力ルーチンを使用します (次の節を参照)。
SIGIO シグナルは、ソケット (任意のファイル記述子) がデータ転送を終了した時点をプロセスに通知します。 SIGIO を使用する手順は次のとおりです。
signal(3C) 呼び出しまたは sigvec(3UCB) 呼び出しを使用して、SIGIO シグナルハンドラを設定する。
fcntl(2) を使用してプロセス ID またはプロセスグループ ID を設定し、シグナルの経路をそれ自体のプロセス ID またはプロセスグループ ID に指定する。ソケットのデフォルトのプロセスグループはグループ 0。
ソケットを非同期に変換する (非同期ソケット入出力を参照)。
次のコードに、特定のプロセスがあるソケットに対して要求を行うときに、保留中の要求の情報を受信できるようにする例を示します。SIGURG のハンドラを追加すると、このコードは SIGURG シグナルを受信する目的でも使用できます。
#include <fcntl.h> #include <sys/file.h> ... signal(SIGIO, io_handler); /* SIGIO または SIGURG シグナルを受信するプロセスを s に設定する。*/ if (fcntl(s, F_SETOWN, getpid()) < 0) { perror("fcntl F_SETOWN"); exit(1); }
SIGURG と SIGIO の場合、各ソケットにはプロセス番号とプロセスグループ ID があります。前述の例のとおり、これらの値は 0 に初期化されますが、F_SETOWN fcntl(2) コマンドを使用すると、その後でも定義し直すことができます。fcntl(2) の 3 番目の引数が正の場合、ソケットのプロセス ID を設定します。fcntl(2) の 3 番目の引数が負の場合、ソケットのプロセスグループ ID を設定します。SIGURG シグナルと SIGIO シグナルの受信側として許可されるのは、呼び出し側のプロセスだけです。同様に、fcntl(2)、F_GETOWN は、ソケットのプロセス番号を返します。
また、ioctl(2) を使用してソケットをユーザーのプロセスグループに割り当てても、SIGURG と SIGIO を受信できるように設定できます。
/* oobdata はルーチンを処理する帯域外データ */ sigset(SIGURG, oobdata); int pid = -getpid(); if (ioctl(client, SIOCSPGRP, (char *) &pid) < 0) { perror("ioctl: SIOCSPGRP"); }
サーバープロセスで便利なシグナルとして、ほかに SIGCHLD が挙げられます。このシグナルは、任意の子プロセスがその状態を変更した場合にプロセスに配信されます。通常、サーバーはこのシグナルを使用して、明示的に終了を待機せずに、あるいは終了状態を周期的にポーリングせずに、終了した子プロセスの「リープ (取得)」を行います。たとえば、前述の例のリモートログインサーバーループは次のように拡張できます。
int reaper(); ... sigset(SIGCHLD, reaper); listen(f, 5); while (1) { int g, len = sizeof from; g = accept(f, (struct sockaddr *) &from, &len); if (g < 0) { if (errno != EINTR) syslog(LOG_ERR, "rlogind: accept: %m"); continue; } ... } #include <wait.h> reaper() { int options; int error; siginfo_t info; options = WNOHANG | WEXITED; bzero((char *) &info, sizeof(info)); error = waitid(P_ALL, 0, &info, options); }
親サーバープロセスがその子プロセスのリープに失敗する場合、ゾンビプロセスが生じます。
socket(3SOCKET) 呼び出しの 3 番目の引数が 0 の場合、socket(3SOCKET) は要求されたタイプである返されたソケットにデフォルトのプロトコルを使用するように選択します。通常はデフォルトプロトコルで十分であり、ほかの選択肢はありません。raw ソケットを使用して低レベルのプロトコルやハードウェアインタフェースと直接通信を行う場合は、プロトコルの引数で非多重化を設定してください。
raw ソケットをインターネットファミリで使用して新しいプロトコルを IP 上に実装すると、このソケットは必ず、指定されたプロトコルのパケットだけを受信します。特定のプロトコルを取得するには、プロトコルファミリで定義されているようにプロトコル番号を決定します。インターネットファミリの場合、標準ルーチンで説明しているライブラリルーチンの 1 つ (getprotobyname(3SOCKET) など) を使用してください。
#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> ... pp = getprotobyname("newtcp"); s = socket(AF_INET6, SOCK_STREAM, pp->p_proto);
getprotobyname を使用すると、ソケット s はストリームベースのコネクションを使用しますが、デフォルトの tcp ではなく、newtcp というプロトコルタイプを使用します。
アドレスを指定するとき、TCP と UDP は次の 4 つの要素を使用します。
ローカル IP アドレス
ローカルポート番号
外部 IP アドレス
外部ポート番号
ローカルアドレスまたはローカルポート (あるいはこの両方)
外部アドレスと外部ポート
accept(3SOCKET) 呼び出しは外部クライアントからコネクション情報を取得します。したがって、accept(3SOCKET) の呼び出し元が何も指定していなくても、ローカルアドレスとローカルポートをシステムに指定できます。外部アドレスと外部ポートが返されます。
listen(3SOCKET) を呼び出すと、ローカルポートが選択されます。ローカル情報を割り当てる bind(3SOCKET) を明示的に指定していない場合、listen(3SOCKET) は一時的なポート番号を割り当てます。
あるポート上に常駐するサービスがローカルアドレス情報を必要としない場合、そのサービスはそのポートに bind(3SOCKET) できます。このとき、ローカルアドレスは指定しないままにしておいてもかまいません。ローカルアドレスは、<netinet/in.h> に定数値を持つ変数 in6addr_any に設定されます。ローカルポートを固定する必要がない場合、listen(3SOCKET) を呼び出すと、ポートが選択されます。アドレス in6addr_any またはポート番号 0 を指定することを「ワイルドカード (を使用する)」と呼びます。 AF_INET の場合は、in6addr_any の代わりに INADDR_ANY を使用します。
ワイルドカードアドレスは、インターネットファミリにおけるローカルアドレスのバインドを簡易化します。次のコードは、getaddrinfo(3SOCKET) の呼び出しで返された特定のポート番号をソケットにバインドし、ローカルアドレスを指定しないままにしておく例です。
#include <sys/types.h> #include <netinet/in.h> ... struct addrinfo *aip; ... if (bind(sock, aip->ai_addr, aip->ai_addrlen) == -1) { perror("bind"); (void) close(sock); return (-1); }
ホスト上の各ネットワークインタフェースは、通常、一意の IP アドレスを持ちます。ワイルドカードローカルアドレスを持つソケットは、指定されたポート番号に宛てたメッセージを受信できます。ワイルドカードローカルアドレスを持つソケットはまた、ホストに割り当てられている可能性のあるアドレスに送信されたメッセージを受信できます。特定のネットワーク上のホストだけにサーバーとの接続を許可するために、サーバーは適切なネットワーク上のインタフェースのアドレスをバインドします。
同様に、ローカルポート番号を指定しないままにしておくと、システムがポート番号を選択します。たとえば、特定のローカルアドレスをソケットにバインドするが、ローカルポート番号は指定しないままにしておくには、次のように bind を使用します。
bzero (&sin, sizeof (sin)); (void) inet_pton (AF_INET6, ":ffff:127.0.0.1", sin.sin6_addr.s6_addr); sin.sin6_family = AF_INET6; sin.sin6_port = htons(0); bind(s, (struct sockaddr *) &sin, sizeof sin);
システムは、次の 2 つの基準でローカルポート番号を選択します。
1024 未満のインターネットポート番号 (IPPORT_RESERVED ) は、特権ユーザー用に予約される。非特権ユーザーは、1024 を超える任意のインターネットポート番号を使用されるインターネットポート番号の最大値は 65535。
現在、ほかのソケットにバインドされていないポート番号
クライアントのポート番号と IP アドレスは accept(3SOCKET) または getpeername(3SOCKET) で確認します。
関連付けが 2 段階のプロセスで作成されるため、システムがポート番号を選択するために使用するアルゴリズムがアプリケーションに適さない場合もあります。たとえば、インターネットファイル転送プロトコルでは、データコネクションは常に同じローカルポートから実行する必要があると定めています。しかし、異なる外部ポートに接続することによって、関連付けの重複を避けることができます。この場合、前のデータコネクションのソケットが存在しているとき、システムは同じローカルアドレスとローカルポート番号をソケットにバインドすることを許可しません。
デフォルトのポート選択アルゴリズムを無効にするには、次に示すようにオプション呼び出しを行なってからアドレスをバインドする必要があります。
... int on = 1; ... setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof on); bind(s, (struct sockaddr *) &sin, sizeof sin);
この呼び出しを行うと、すでに使用されているローカルアドレスをバインドできます。この呼び出しは一意性という条件に違反しません。なぜなら、同じローカルアドレスとローカルポートを持つ別のソケットが同じ外部アドレスと外部ポートを持たないことをシステムがコネクション時に検証するためです。関連付けがすでに存在する場合、エラー EADDRINUSE が返されます。
SunOS 5.6 およびその互換バージョンでは、TCP/IP プロトコルスタックは、ゼロコピーと TCP チェックサム負荷解除という 2 つの新しい機能をサポートするように拡張されました。
ゼロコピーは、仮想メモリー MMU の再マッピングと、書き込み時にコピーを行う手法を使用して、アプリケーションとカーネル空間の間でデータを移動します。
チェックサム負荷解除は、特殊なハードウェアロジックにより TCP チェックサム計算の負荷を解除します。
ゼロコピーとチェックサム負荷解除は互いに機能的には依存していませんが、最高の性能を得るには連携して動作する必要があります。チェックサム負荷解除には、ネットワークインタフェースのハードウェアサポートが必要です。このハードウェアサポートがない場合、ゼロコピーは有効になりません。
ゼロコピーには、仮想メモリーページの再マッピングを適用する前に、アプリケーションがページ型のバッファーを供給することが必要です。負荷が高い書き込み時コピーの失敗を避けるには、アプリケーションは伝送側に大きな循環バッファーを使用する必要があります。一般的なバッファー割り当ては 16 の 8K バッファーです。
setsockopt(3SOCKET) と getsockopt(3SOCKET) を使用すると、ソケットのオプションを設定および取得できます。たとえば、送信バッファー空間または受信バッファー空間を変更できます。次に、呼び出しの一般的な書式を示します。
setsockopt(s, level, optname, optval, optlen);
および
getsockopt(s, level, optname, optval, optlen);
オペレーティングシステムはいつでもこれらの値を適切に調整できます。
次に、setsockopt(3SOCKET) 呼び出しと getsockopt(3SOCKET) 呼び出しの引数を示します。
オプションの適用先であるソケット
sys/socket.h 内の記号定数 SOL_SOCKET が示すプロトコルレベル (ソケットレベルなど) を指定する
オプションを指定する、sys/socket.h で定義されている記号定数
オプションの値を示す
オプションの値の長さを示す
getsockopt(3SOCKET) の場合、optlen は値結果の引数です。初期状態時、optlen 引数は optval が示す記憶領域のサイズに設定されます。復帰時、optlen 引数は使用された記憶領域の長さに設定されます。
既存のソケットのタイプを判断する必要があるとき、プログラムは SO_TYPE ソケットオプションと getsockopt(3SOCKET) 呼び出しを使用して、inetd(1M) を起動する必要があります。
#include <sys/types.h> #include <sys/socket.h> int type, size; size = sizeof (int); if (getsockopt(s, SOL_SOCKET, SO_TYPE, (char *) &type, &size) <0) { ... }
getsockopt(3SOCKET) のあと、type はソケットタイプの値 (sys/socket.h で定義) に設定されます。データグラムソケットの場合、type は SOCK_DGRAM です。
inetd(1M) デーモンは起動時に呼び出され、待機するサービスを /etc/inet/inetd.conf ファイルから取得します。デーモンは /etc/inet/inetd.conf ファイルに記述されている各サービスごとに 1 つのソケットを作成し、各ソケットに適切なポート番号の割り当てを行います。inetd(1M) についての詳細は、マニュアルページを参照してください。
inetd(1M) デーモンは各ソケットをポーリングして、そのソケットに対応するサービスへのコネクション要求を待機します。SOCK_STREAM タイプのソケットの場合、inetd(1M) は待機ソケット上で受け入れ (accept(3SOCKET))、フォークし (fork(2))、新しいソケットをファイル記述子 0 および 1 (stdin および stdout) に複製し (dup(2)) 、ほかの開いているファイル記述子を閉じて、適切なサーバーを実行します (exec(2))。
inetd(1M) を使用する主な利点は、使用していないサービスがシステムのリソースを消費しない点にあります。また、コネクションの確立に関する処理の大部分を inetd(1M) が行う点も大きな利点の 1 つです。inetd(1M) によって起動されたサーバーのソケットはファイル記述子 0 と 1 上のクライアントに接続されます。したがって、サーバーはすぐに、読み取り、書き込み、送信、または受信を行うことができます。fflush(3C) を適宜使用する限り、サーバーはバッファリングされた入出力を stdio の規約に従って使用できます。
getpeername(3SOCKET) ルーチンはソケットに接続されたピア (プロセス) のアドレスを返します。このルーチンは、inetd(1M) によって起動されたサーバーで使用すると便利です。たとえば、このルーチンを使用すると、クライアントの IPv6 アドレスを表現するときに使用される fec0::56:a00:20ff:fe7d:3dd2 のようなインターネットアドレスを記録できます。次に、inetd(1M) サーバーが使用するコードの例を示します。
struct sockaddr_storage name; int namelen = sizeof (name); char abuf[INET6_ADDRSTRLEN]; struct in6_addr addr6; struct in_addr addr; if (getpeername(fd, (struct sockaddr *)&name, &namelen) == -1) { perror("getpeername"); exit(1); } else { addr = ((struct sockaddr_in *)&name)->sin_addr; addr6 = ((struct sockaddr_in6 *)&name)->sin6_addr; if (name.ss_family == AF_INET) { (void) inet_ntop(AF_INET, &addr, abuf, sizeof (abuf)); } else if (name.ss_family == AF_INET6 && IN6_IS_ADDR_V4MAPPED(&addr6)) { /* これは IPv4 でマップされた IPv6 アドレス */ IN6_MAPPED_TO_IN(&addr6, &addr); (void) inet_ntop(AF_INET, &addr, abuf, sizeof (abuf)); } else if (name.ss_family == AF_INET6) { (void) inet_ntop(AF_INET6, &addr6, abuf, sizeof (abuf)); } syslog("Connection from %s\n", abuf); }
ブロードキャストは IPv6 ではサポートされません。ブロードキャストがサポートされるのは IPv4 のみです。
データグラムソケットにより送信されたメッセージは、接続されているネットワークのすべてのホストに届くようにブロードキャストを行うことができます。システムはブロードキャストのシミュレーションをソフトウェアで行わないため、ネットワークがブロードキャストをサポートする必要があります。ブロードキャストメッセージを使用すると、ネットワーク上のすべてのホストがブロードキャストメッセージをサービスする必要があるので、ブロードキャストメッセージはネットワークに大きな負荷をかける可能性があります。ブロードキャストは主に次の 2 つの目的に使用されます。
アドレスが不明なローカルネットワーク上の資源の検索
アクセス可能なすべての隣接ホストに情報を送信する必要がある機能
ブロードキャストメッセージを送信するには、次のようにインターネットデータグラムソケットを作成します。
s = socket(AF_INET, SOCK_DGRAM, 0);
次に、ポート番号をソケットにバインドします。
sin.sin_family = AF_INET; sin.sin_addr.s_addr = htonl(INADDR_ANY); sin.sin_port = htons(MYPORT); bind(s, (struct sockaddr *) &sin, sizeof sin);
ネットワークのブロードキャストアドレスに送信することにより、データグラムは 1 つのネットワーク上のみでブロードキャストを行うことができます。また、netinet/in.h 内で定義されている特別なアドレス INADDR_BROADCAST に送信することにより、接続されているすべてのネットワークに対して、データグラムのブロードキャストを行うことができます。
システムは、システム上のネットワークインタフェースについての情報の数を判断するメカニズムを提供します。この情報には、IP アドレスおよびブロードキャストアドレスが含まれます。SIOCGIFCONF ioctl(2) 呼び出しはホストのインタフェース構成を単一の ifconf 構造体で返します。この構造体には ifreq 構造体の配列が含まれます。ifreq 構造体は、ホストに接続されているすべてのネットワークインタフェースがサポートするアドレスファミリごとに 1 つずつ存在します。
次の例では、net/if.h で定義されている ifreq 構造体を示します。
struct ifreq { #define IFNAMSIZ 16 char ifr_name[IFNAMSIZ]; /* たとえば名前が 「en0」 */ union { struct sockaddr ifru_addr; struct sockaddr ifru_dstaddr; char ifru_oname[IFNAMSIZ]; /* 名前の場合、その他 */ struct sockaddr ifru_broadaddr; short ifru_flags; int ifru_metric; char ifru_data[1]; /* インタフェース依存データ */ char ifru_enaddr[6]; } ifr_ifru; #define ifr_addr ifr_ifru.ifru_addr #define ifr_dstaddr ifr_ifru.ifru_dstaddr #define ifr_oname ifr_ifru.ifru_oname #define ifr_broadaddr ifr_ifru.ifru_broadaddr #define ifr_flags ifr_ifru.ifru_flags #define ifr_metric ifr_ifru.ifru_metric #define ifr_data ifr_ifru.ifru_data #define ifr_enaddr ifr_ifru.ifru_enaddr };
インタフェース構成を取得する呼び出しは以下の通りです。
/* * インタフェースの数を検索するため SIOCGIFNUM ioctl を実行。 * * 検索されたインタフェースの数に相当する空間を割り当て。 * * 割り当てられたバッファーに対し SIOCGIFCONF を実行。 * */ if (ioctl(s, SIOCGIFNUM, (char *)&numifs) == -1) { numifs = MAXIFS; } bufsize = numifs * sizeof(struct ifreq); reqbuf = (struct ifreq *)malloc(bufsize); if (reqbuf == NULL) { fprintf(stderr, "out of memory\n"); exit(1); } ifc.ifc_buf = (caddr_t)&reqbuf[0]; ifc.ifc_len = bufsize; if (ioctl(s, SIOCGIFCONF, (char *)&ifc) == -1) { perror("ioctl(SIOCGIFCONF)"); exit(1); } ... }
この呼び出しの後、buf には ifreq 構造体の配列が含まれます。ifreq 構造体は、ホストに接続されているすべてのネットワークごとに 1 つずつ存在します。これらの構造体のソート順は次のとおりです。
インタフェース名のアルファベット順
サポートされるアドレスファミリの番号順
ifc.ifc_len の値は ifreq 構造体が使用したバイト数に設定されます。
各構造体は、対応するネットワークが稼働または停止しているか、ポイントツーポイントまたはブロードキャストのどちらであるか、などを示すインタフェースフラグセットを持ちます。次の例では、ifreq 構造体が指定するインタフェース用の SIOCGIFFLAGS フラグを返す ioctl(2) を示します。
struct ifreq *ifr; ifr = ifc.ifc_req; for (n = ifc.ifc_len/sizeof (struct ifreq); --n>= 0; ifr++) { /* * 別の目的でアドレスファミリ用に使用されているインタフェースを * 使用しないよう注意する */ if (ifr->ifr_addr.sa_family != AF_INET) continue; if (ioctl(s, SIOCGIFFLAGS, (char *) ifr) < 0) { ... } if ((ifr->ifr_flags & IFF_UP) == 0 || (ifr->ifr_flags & IFF_LOOPBACK) || (ifr->ifr_flags & (IFF_BROADCAST | IFF_POINTOPOINT)) == 0) continue; }
次の例では、インタフェースのブロードキャストアドレスを取得するための SIOGGIFBRDADDR ioctl(2) コマンドを示します。
if (ioctl(s, SIOCGIFBRDADDR, (char *) ifr) < 0) { ... } memcpy((char *) &dst, (char *) &ifr->ifr_broadaddr, sizeof ifr->ifr_broadaddr);
また、SIOGGIFBRDADDR ioctl(2) を使用すると、ポイントツーポイントインタフェースの宛先アドレスを取得できます。
インタフェースのブロードキャストアドレスを取得したあと、sendto(3SOCKET) を使用してブロードキャストデータグラムを送信します。
sendto(s, buf, buflen, 0, (struct sockaddr *)&dst, sizeof dst);
ホストのインタフェースがブロードキャストまたはポイントツーポイントアドレスをサポートする場合、そのホストが接続されているインタフェースごとに 1 つの sendto(3SOCKET) を使用します。