プログラミングインタフェース

第 6 章 ソケットインタフェース

この章では、ソケットインタフェースについて説明します。また、プログラム例を使用して重要なポイントを示します。この章の内容は次のとおりです。


注 –

この章で説明するインタフェースは、マルチスレッドに対して安全です。ソケットインタフェースの呼び出しを含むアプリケーションは、マルチスレッド対応のアプリケーションで自由に使用できます。ただし、アプリケーションに有効な多重度は指定されていません。


SunOS 4 のバイナリ互換性

SunOS 4 以降で行われた 2 つの主な変更は SunOS 5.9 リリースでも継承されています。パッケージにバイナリ互換性があるため、動的にリンクされた SunOS 4 ベースのソケットアプリケーションは SunOS 5.9 でも実行できます。

ソケットの概要

ソケットは、1981 年以来 SunOS リリースに不可欠な部分となっています。ソケットは、名前を結合できる通信の終端です。ソケットにはタイプがあり、関連プロセスが 1 つ存在します。ソケットは、次のようなプロセス間通信のためのクライアントサーバーモデルを実装するために設計されました。

ソケットは、UNIX ファイルのように動作し、ネットワークプロトコルが使用できるようにします。アプリケーションは、必要に応じてソケットを作成します。ソケットは、close(2)、read(2)、write(2)、<ioctl(2)、および fcntl(2) インタフェースと連携して動作します。オペレーティングシステムは、ファイルの ファイル記述子とソケットのファイル記述子を区別します。

ソケットライブラリ

ソケットインタフェースルーチンは、アプリケーションとリンクが必要なライブラリ内に存在します。ライブラリ libsocket.so は、他のシステムサービスライブラリとともに /usr/lib にあります。libsocket.so は動的リンクに使用されます。

ソケットタイプ

ソケットタイプには、ユーザーが認識できる通信プロパティを定義します。インターネットファミリソケットは、TCP/IP トランスポートプロトコルへのアクセスを提供します。インターネットファミリは、IPv6 と IPv4 の両方で通信できるソケットの場合、AF_INET6 という値で識別されます。また、以前のアプリケーションとのソース互換、および IPv4 に対する raw アクセスを目的とした値 AF_INET もサポートされています。

次に、SunOS 環境がサポートする 3 つのタイプのソケットを示します。

詳細については、特定のプロトコルの選択を参照してください。

インタフェースセット

SunOS 5.9 プラットフォームは 2 つのソケットインタフェースセットを提供します。BSD ソケットインタフェース (SunOS バージョン 5.7 およびそれ以降のリリースで提供される) と XNS 5 (Unix98) ソケットインタフェースです。XNS 5 インタフェースは、BSD インタフェースとわずかに異なります。

XNS 5 ソケットインタフェースについては、次のマニュアルページを参照してください。

従来の BSD ソケットの動作については、対応する 3N のマニュアルページを参照してください。さらに、マニュアルページのセクション 3N には、次のような新しいインタフェースが追加されました。

XNS 5 (Unix98) ソケットインタフェースを使用するアプリケーションを構築する方法については、standards(5) のマニュアルページを参照してください。

ソケットの基本的な使用

この節では、基本的なソケットインタフェースの使用について説明します。

ソケットの作成

socket(3SOCKET) 呼び出しは、指定されたファミリに指定されたタイプのソケットを作成します。

s = socket(family, type, protocol);

プロトコルが指定されない場合、システムは要求されたソケットタイプをサポートするプロトコルを選択します。ソケットハンドルが返されます。ソケットハンドルはファイル記述子です。

ファミリは、sys/socket.h に定義されている定数の 1 つで指定します。AF_ suite という名前の定数は、名前を解釈するときに使用されるアドレス形式を指定します。

AF_APPLETALK

Apple Computer, Inc. の Appletalk ネットワーク

AF_INET6

IPv6 と IPv4 用のインターネットファミリ

AF_INET

IPv4 専用のインターネットファミリ

AF_PUP

Xerox Corporation の PUP インターネット

AF_UNIX

UNIX ファイルシステム

ソケットタイプは、sys/socket.h で定義されています。ソケットタイプの SOCK_STREAMSOCK_DGRAM、または SOCK_RAW は、AF_INET6AF_INET、および AF_UNIX でサポートされます。インターネットファミリでストリームソケットを作成する例です。

s = socket(AF_INET6, SOCK_STREAM, 0);

この呼び出しの結果、ストリームソケットが作成されます。(このストリームソケットでは) TCP プロトコルが基本的な通信を提供します。ほとんどの場合、protocol 引数はデフォルトの 0 に設定します。ソケットの拡張機能で説明しているように、デフォルト以外のプロトコルも指定できます。

ローカル名のバインド

ソケットは、その作成時には名前がありません。アドレスがソケットにバインドされるまで、リモートプロセスはソケットを参照できません。通信プロセスは、アドレスを介して接続されます。インターネットファミリでは、コネクションはローカルアドレス、リモートアドレス、ローカルポート、およびリモートポートから構成されます。順番が重複しているセット、たとえば protocollocal addresslocal portforeign addressforeign port は指定できません。ほとんどのファミリでは、コネクションは一意である必要があります。

bind(3SOCKET) インタフェースを使用すると、プロセスはソケットのローカルアドレスを指定できます。このインタフェースは local addresslocal port というセットになります。connect(3SOCKET) と accept(3SOCKET) は、アドレス組のリモート側を固定することにより、ソケットの関連付けを完了します。bind(3SOCKET) 呼び出しは次のように使用します。

bind (s, name, namelen);

s は、ソケットハンドルです。バインド名は、バイト文字列で、サポートするプロトコル (複数も可) がこれを解釈します。インターネットファミリ名には、インターネットアドレスとポート番号が含まれます。

次の例では、インターネットアドレスのバインドを示します。

#include <sys/types.h>
#include <netinet/in.h>
...
		struct sockaddr_in6 sin6;
...
		s = socket(AF_INET6, SOCK_STREAM, 0);
		bzero (&sin6, sizeof (sin6));
		sin6.sin6_family = AF_INET6;
		sin6.sin6_addr.s6_addr = in6addr_arg;
		sin6.sin6_port = htons(MYPORT);
		bind(s, (struct sockaddr *) &sin6, sizeof sin6);

アドレス sin6 の内容は、インターネットアドレスのバインドについて説明した アドレスのバインドで説明しています。

コネクションの確立

コネクションの確立は、通常、クライアントの役割を果たすプロセスと、サーバーの役割を果たすプロセスによって非同期で行われます。サーバーは、サービスに関連付けられた既知のアドレスにソケットをバインドし、コネクション要求のためにソケットをブロックします。これで、無関係のプロセスがサーバーに接続できます。クライアントは、サーバーのソケットへのコネクションを起動することでサーバーにサービスを要求します。クライアント側では、connect(3SOCKET) 呼び出しでコネクションを起動します。インターネットファミリの場合、このコネクションは次のようになります。

struct sockaddr_in6 server;
 ...
connect(s, (struct sockaddr *)&server, sizeof server);

接続呼び出しの時点でクライアントのソケットがバインドされていない場合、システムは自動的に名前を選択し、ソケットにバインドします。詳細は、アドレスのバインドを参照してください。これは、クライアントのソケットにローカルアドレスを バインドする一般的な方法です。

クライアントのコネクションを受信するには、サーバーはそのソケットをバインドした後に 2 つの処理を行う必要があります。まず、待ち行列に入れることができるコネクション要求の数を示し、続いてコネクションを受け入れます。

struct sockaddr_in6 from;
 ...
listen(s, 5);                /* 5 つのコネクション待ち行列を許可する */
fromlen = sizeof(from);
newsock = accept(s, (struct sockaddr *) &from, &fromlen);

ソケットハンドル s は、コネクション要求の送信先であるアドレスにバインドされるソケットです。listen(3SOCKET) の 2 つ目のパラメータは、待ち行列に入れることができる未処理のコネクションの最大数を指定します。from は、クライアントのアドレスを指定する構造体です。場合によって NULL ポインタが渡されます。fromlen は構造体の長さです。UNIX ファミリでは、 fromstruct sockaddr_un として宣言されます。

accept(3SOCKET) ルーチンは通常、プロセスをブロックします。accept(3SOCKET) は、要求しているクライアントに接続される新しいソケット記述子を返します。fromlen の値は、アドレスの実際のサイズに変更されます。

サーバーは、特定のアドレスからのみコネクションを受け入れますが、これを表示することはできません。サーバーは accept(3SOCKET が返した from アドレスを確認し、受け入れ不可能なクライアントとのコネクションを閉じることができます。サーバーは、複数のソケット上のコネクションを受け入れることも、あるいは accept(3SOCKET) 呼び出しのブロックを避けることもできます。これらの手法については、ソケットの拡張機能で説明しています。

コネクションエラー

コネクションが失敗した場合、エラーが返されますが、システムがバインドしたアドレスは残ります。コネクションが成功した場合、ソケットがサーバーに関連付けられ、データ転送を開始できます。

次の表に、コネクションが失敗したときに返される一般的なエラーの一覧を示します。

表 6–1 ソケットコネクションエラー

ソケットエラー 

エラーの説明 

ENOBUFS

呼び出しをサポートするためのメモリーが足りない 

EPROTONOSUPPORT

不明なプロトコルの要求 

EPROTOTYPE

サポートされないソケットタイプの要求 

ETIMEDOUT

指定された時刻にコネクションが確立されていない。このエラーは、宛先ホストがダウンしているか、あるいはネットワーク内の障害で伝送が中断した場合に発生する 

ECONNREFUSED

ホストがサービスを拒否した。このエラーは、要求されたアドレスにサーバープロセスが存在しない場合に発生する 

ENETDOWN または EHOSTDOWN

これらのエラーは、基本通信インタフェースが配信する状態情報によって発生する 

ENETUNREACH または EHOSTUNREACH

この操作エラーは、ネットワークまたはホストへの経路がないために発生する。この操作エラーはまた、中間ゲートウェイまたは切り替えノードが返す状態情報によっても発生する。返される状態情報が十分でないために、ダウンしているネットワークとダウンしているホストが区別できない場合もある 

データ転送

この節では、データを送受信するためのインタフェースについて説明します。メッセージの送受信は、次のように通常の read(2) インタフェースと write(2) インタフェースを使用できます。

write(s, buf, sizeof buf);
read(s,  buf, sizeof buf);

また、次のように send(3SOCKET) と recv(3SOCKET) も使用できます。

send(s, buf, sizeof buf, flags);
recv(s, buf, sizeof buf, flags);

send(3SOCKET) と recv(3SOCKET) は read(2) と write(2) に非常によく似ていますが、flags 引数が重要です。flags 引数 (sys/socket.h で定義) は 0 以外の値として、次のうちの 1 つまたは複数を指定できます。

MSG_OOB

帯域外データを送受信する

MSG_PEEK

読み取らずにデータの確認だけを行う

MSG_DONTROUTE

パケットの経路を指定せずにデータを送信する

帯域外データは、ストリームソケット固有のものです。recv(3SOCKET) 呼び出しで MSG_PEEK を指定した場合、存在するすべてのデータがユーザーに返されますが、データは読み取られていないものとして扱われます。次に、ソケット上で read(2) または recv(3SOCKET) を呼び出すと、同じデータが返されます。発信パケットに適用されるパケット経路を指定せずにデータを送信するオプションは現在、経路制御テーブルの管理プロセスだけに使用されています。

ソケットを閉じる

SOCK_STREAM ソケットは close(2) インタフェース呼び出しで破棄できます。close(2) の後でも確実な配信が見込まれるソケットの待ち行列にデータが入っている場合、プロトコルは引き続きデータを転送しようとします。期限が来てもデータが配信されない場合、データは破棄されます。

shutdown(3SOCKET) は SOCK_STREAM ソケットを正常に閉じ、両方のプロセスで送信が行われなくなっていることを認識できます。この呼び出しの形式は次のとおりです。

shutdown(s, how);

how は次のように定義されています。

0

それ以上の受信を許可しない

1

それ以上の送信を許可しない

2

それ以上の送受信を許可しない

ストリームソケットのコネクション

次の 2 つの例に、インターネットファミリのストリームコネクションの開始と受け入れを示します。

図 6–1 ストリームソケットを使用したコネクション型の通信

この図はクライアントとサーバー間のデータフローを示します。accept と connect そして read と write の関数ペアを使用します。

次のプログラムはサーバーの例です。このサーバーは、ソケットを作成し、そのソケットに名前をバインドし、そして、ポート番号を表示します。このプログラムは listen(3SOCKET) を呼び出して、ソケットがコネクション要求を受け入れる用意ができていることをマークし、要求の待ち行列を初期化します。プログラムの残りの部分は無限ループです。ループの各パスは、新しいソケットを作成することによって新しいコネクションを受け入れ、待ち行列からそのコネクションを削除します。サーバーは、ソケットからのメッセージを読み取って表示し、ソケットを閉じます。in6addr_any の使用については、アドレスのバインドで説明しています。


例 6–1 インターネットストリームコネクションの受け入れ (サーバー)

#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include >stdio.h>
#define TRUE 1   
/*
 * このプログラムは、ソケットを作成したあと無限ループを開始する。
 * ループごとにコネクションを受け入れ、そのコネクションからのデータを出力
 * するコネクションが遮断されるか、またはクライアントが コネクションを閉じた
 * 時点でプログラムは新しいコネクションを受け入れる
*/
main() {
    int sock, length;
    struct sockaddr_in6 server;
    int msgsock;
    char buf[1024];
    int rval;
    /* ソケットを作成する */
    sock = socket(AF_INET6, SOCK_STREAM, 0);
    if (sock == -1) {
      perror("opening stream socket");
      exit(1);
   }
   /* ワイルドカードを使用してソケットをバインドする */
   bzero (&server, sizeof(server));
/* bzero (&sin6, sizeof (sin6)); */
   server.sin6_family = AF_INET6;
   server.sin6_addr = in6addr_any;
   server.sin6_port = 0;
   if (bind(sock, (struct sockaddr *) &server, sizeof server)
         == -1) {
      perror("binding stream socket");
      exit(1);
   }
   /* 割り当てられたポート番号を調べ、それを出力する */
   length = sizeof server;
   if (getsockname(sock,(struct sockaddr *) &server, &length)
         == -1) {
      perror("getting socket name");
      exit(1);
   }
   printf("Socket port #%d\n", ntohs(server.sin6_port));
   /* コネクションの受け入れを開始する */
   listen(sock, 5);
   do {
      msgsock = accept(sock,(struct sockaddr *) 0,(int *) 0);
      if (msgsock == -1)
         perror("accept");
      else do {
         memset(buf, 0, sizeof buf);
         if ((rval = read(msgsock,buf, 1024)) == -1)
            perror("reading stream message");
         if (rval == 0)
            printf("Ending connection\n");
         else
            /* データが出力可能であると想定する */
            printf("-->%s\n", buf);
      } while (rval > 0);
      close(msgsock);
   } while(TRUE);
   /*
    * このプログラムには無限ループが含まれるため、ソケットの 
    * sock は明示的に閉じられることはない。ただし、プロセスが
    * 中断されるかまたは正常に終了する場合は自動的に閉じる
    */
   exit(0);
}

例 6–2 のクライアント側プログラムは、コネクションを開始するために、ストリームソケットを作成し、コネクション用のソケットのアドレスを指定して connect(3SOCKET) を呼び出します。宛先ソケットが存在し、要求が受け入れられる場合、 コネクションは完了します。すると、プログラムはデータを送信できます。データは、メッセージ境界なしで順番に配信されます。コネクションは、一方のソケットが閉じられた時点で遮断されます。このプログラム内のデータ表現ルーチン (ntohl(3SOCKET)、ntohs(3SOCKET)、htons(3SOCKET)、および htonl(3XNET) など) の詳細については、byteorder(3SOCKET) のマニュアルページを参照してください。


例 6–2 インターネットファミリのストリームコネクション(クライアント)

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#define DATA "Half a league, half a league . . ."   
/* 
 * このプログラムはソケットを作成し、コマンド行で指定されるソケットを
 * 使用してコネクションを開始する。このコネクションでいくつかのデータが
 * 送信されたあとソケットが閉じられコネクションが終了する
 * コマンドの形式 : streamwrite hostname portnumber
 * 使用法 : pgm host part
 */ 
main(argc, argv)
    int argc;
    char *argv[]; 
{
    int sock, errnum, h_addr_index;
    struct sockaddr_in6 server;
    struct hostent *hp;
    char buf[1024];
    /* ソケットを作成する */
    sock = socket( AF_INET6, SOCK_STREAM, 0);
    if (sock == -1) {
        perror("opening stream socket");
        exit(1);
    }
    /* コマンド行で指定される名前を使用してソケットを接続する */
    bzero (&server, sizeof (server));
    server.sin6_family = AF_INET6;
    hp = getipnodebyname(argv[1], AF_INET6, AI_DEFAULT, &errnum);
/*
 * getipnodebyname が指定されたホストのネットワークアドレスを含む
 * 構造体を返す
 */
    if (hp == (struct hostent *) 0) {
        fprintf(stderr, "%s: unknown host\n", argv[1]);
        exit(2);
    }
    h_addr_index = 0;
    while (hp->h_addr_list[h_addr_index] != NULL) {
        bcopy(hp->h_addr_list[h_addr_index], &server.sin6_addr,
                    hp->h_length);
        server.sin6_port = htons(atoi(argv[2]));
        if (connect(sock, (struct sockaddr *) $server,
                    sizeof (server)) == -1) {
            if (hp->h_addr_list[++h_addr_index] != NULL) {
                /* 次のアドレスを試みる */
                continue;
            }
            perror("connecting stream socket");
            freehostent(hp);
            exit(1);
        }
        break;
    }
    freehostent(hp);
    if (write( sock, DATA, sizeof DATA) == -1)
        perror("writing on stream socket");
    close(sock);
    freehostent (hp);
    exit(0);
}

入出力の多重化

要求は、複数のソケットまたはファイルの間で多重化できます。多重化を行うには、select(3C) を使用します。

#include <sys/time.h>
#include <sys/types.h>
#include <sys/select.h>
 ...
fd_set readmask, writemask, exceptmask;
struct timeval timeout;
 ...
select(nfds, &readmask, &writemask, &exceptmask, &timeout);

select(3C) の最初の引数は、続く 3 つの引数が示すリスト内のファイル記述子の数です。

select(3C) の 2 つ目、3 つ目、4 つ目の引数は、3 つのファイル記述子セットを指します。つまり、 読み取りを行う記述子セット、書き込みを行うセット、および例外条件が認められるセットです。帯域外データは、唯一の例外条件です。これらのポインタはどれも、適切にキャストされた NULL として指定できます。各セットは、ロング整数ビットマスクの配列を含む構造体です。配列のサイズは FD_SETSIZE (select.h で定義) で設定します。配列には、各 FD_SETSIZE ファイル記述子のための 1 ビットを保持するだけの長さがあります。

マクロ FD_SET (fd, &mask) はセット mask 内のファイル記述子 fd を追加し、マクロ FD_CLR (fd, &mask) はこの記述子を削除します。セット mask は使用前に 0 にする必要があり、マクロ FD_ZERO (&mask) はセット mask をクリアします。

select(3C) に 5 つ目の引数を使用すると、タイムアウト値を指定できます。timeout ポインタが NULL の場合、ファイル記述子が選択できるようになるまで、または、シグナルが受信されるまで、select(3C) はブロックされます。timeout 内のフィールドが 0 に設定されると、select(3C) はすぐにポーリングして返されます。

select(3C) は通常、選択されたファイル記述子の数を返しますが、タイムアウト期限が過ぎていた場合は 0 を返します。エラーまたは割り込みが発生した場合、select(3C) ルーチンは、errno にエラー番号を指定し、ファイル記述子マスクを変更せずに、-1 を返します。成功した場合に返される 3 つのセットは読み取り可能なファイル記述子、書き込み可能なファイル記述子、または例外条件が保留されたファイル記述子を示します。

FD_ISSET (fd, &mask) マクロを使用して、選択マスク内のファイルの記述子の状態をテストしてください。セット mask 内に fd が存在する場合、このマクロは 0 以外の値を返します。それ以外の場合、このマクロは 0 を返します。ソケット上の待ち行列に入っているコネクション要求を確認するには、select(3C) を使用し、続いて、読み取りセット上で FD_ISSET (fd, &mask) マクロを使用します。

次の例は、読み取り用のリスニング (待機) ソケット上で select(3C) を使用することによって、accept(3SOCKET) 呼び出しでいつ新しいコネクションをピックアップできるかどうかタイミングを判定する方法を示します。このプログラムは、コネクション要求を受け入れ、データを読み取り、単一のソケットで切断します。


例 6–3 select(3C) を使用して保留状態のコネクションを確認する

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time/h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#define TRUE 1
/*
 * このプログラムは accept を呼び出す前に、select を使用して
 * 他のユーザーがコネクションを試みていないかを確認する
 */
main() {
    int sock, length;
    struct sockaddr_in6 server;
    int msgsock;
    char buf[1024];
    int rval;
    fd_set ready;
    struct timeval to;
    /* ソケットを開き、そのソケットを以前の例と同様にバインドする */
    /* コネクションの受け入れを開始する */
    listen(sock, 5); 
    do {
        FD_ZERO(&ready);
        FD_SET(sock, &ready);
        to.tv_sec = 5;
        to.tv_usec = 0;
        if (select(sock + 1, &ready, (fd_set *)0, 
                   (fd_set *)0, &to) == -1) {
            perror("select");
            continue;
        }
        if (FD_ISSET(sock, &ready)) {
            msgsock = accept(sock, (struct sockaddr *)0, (int *)0);
            if (msgsock == -1)
                perror("accept");
            else do {
                memset(buf, 0, sizeof buf);
                if ((rval = read(msgsock, buf, 1024)) == -1)
                    perror("reading stream message");
                else if (rval == 0)
                    printf("Ending connection\n");
                else
                    printf("-->%s\n", buf);
            } while (rval > 0);
            close(msgsock);
        } else
            printf("Do something else\n");
        } while (TRUE);
    exit(0);
}

以前のバージョンの select(3C) ルーチンでは、引数は fd_sets へのポインタではなく、整数へのポインタでした。ファイル記述子の数が整数内のビット数よりも小さい場合は、現在でもこのような呼び出しを使用できます。

select(3C) ルーチンは同期多重スキーマを提供します。SIGIO シグナルと SIGURG シグナル (ソケットの拡張機能を参照) によって、出力の完了、入力の有効性、および例外条件の非同期通知を指定できます。

データグラムソケット

データグラムソケットは、コネクションの確立を要求せずに、対称型データ交換インタフェースを提供します。各メッセージには宛先アドレスが含まれます。次の図では、サーバーとクライアント間の通信の流れを示します。

次の図において、サーバー側の bind(3SOCKET) 手順は省略できます。

図 6–2 データグラムソケットを使用したコネクションレス型の通信

この図はクライアントとサーバー間のデータフローを示します。sendto と recvfrom の関数を使用します。

ソケットの作成で説明しているように、データグラムソケットを作成します。特定のローカルアドレスが必要な場合、bind(3SOCKET) 操作を最初のデータ伝送よりも先に行う必要があります。それ以外の場合、データが最初に送信される際にシステムがローカルアドレスまたはポートを設定します。データを送信するには、sendto(3SOCKET) を使用します。

sendto(s, buf, buflen, flags, (struct sockaddr *) &to, tolen);

sbufbuflen、および flags パラメータは、コネクション型のソケットの場合と同じです。totolen の値は、意図するメッセージ受信者のアドレスを示します。ローカルにエラー条件 (到達できないネットワークなど) が検出されると、-1 が返され、errno にエラー番号が設定されます。

recvfrom(s, buf, buflen, flags, (struct sockaddr *) &from, &fromlen);

データグラムソケット上でメッセージを受信するには、recvfrom(3SOCKET) を使用します。呼び出す前、fromlen には from バッファーのサイズが設定されます。fromlen にはデータグラムの配信元であるアドレスのサイズが設定されて返されます。

データグラムソケットは connect(3SOCKET) 呼び出しを使用して、ソケットを特定の宛先アドレスに関連付けることもできます。これにより、ソケットは send(3SOCKET) 呼び出しを使用できます。宛先アドレスが明示的に指定されていないソケット上に送信されるデータはすべて、接続されたピアにアドレス指定されます。そして、そのピアから受信されるデータだけが配信されます。1 つのソケットに一度に接続できるのは、接続された 1 つのアドレスだけです。2 つ目の connect(3SOCKET) 呼び出しは、宛先アドレスを変更します。データグラムソケット上のコネクション要求は、すぐに返されます。システムは、ピアのアドレスを記録します。accept(3SOCKET) と listen(3SOCKET) はデータグラムソケットでは使用されません。

データグラムソケットが接続されている間、前の send(3SOCKET) 呼び出しからのエラーは非同期に返すことができます。ソケットはこれらのエラーを後続の操作で報告できます。また、getsockopt(3SOCKET) のオプションである SO_ERROR を使用して、エラー状態を問い合わすこともできます。

次のコードに、ソケットの作成、名前のバインド、ソケットへのメッセージ送信によって、インターネット呼び出しを送信する例を示します。


例 6–4 インターネットファミリデータグラムの送信

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#define DATA "The sea is calm, the tide is full . . ."   
/*
 * ここで、コマンド行引数から取得する名前を持つ受信箱にデータ
 * グラムを送信する。
 * コマンド行の形式 : dgramsend hostname portnumber
 */
main(argc, argv)
    int argc;
    char *argv[];
{
    int sock, errnum;
    struct sockaddr_in6 name;
    struct hostent *hp;
    /* 送信するソケットを作成する */
    sock = socket(AF_INET6,SOCK_DGRAM, 0);
    if (sock == -1) {
        perror("opening datagram socket");
        exit(1);
    }
    /*
     * 送信先ソケットのワイルドカードを使用しない構造名
     * getinodebyname は、指定されたホストのネットワークアドレスを
     * 含む構造体を返す。ポート番号はコマンド行から取得される
     */
    hp = getipnodebyname(argv[1], AF_INET6, AI_DEFAULT, &errnum);
    if (hp == (struct hostent *) 0) {
        fprintf(stderr, "%s: unknown host\n", argv[1]);
        exit(2);
    }
    bzero (&name, sizeof (name));
    memcpy((char *) &name.sin6_addr, (char *) hp->h_addr,
       hp->h_length);
    name.sin6_family = AF_INET6;
    name.sin6_port = htons(atoi(argv[2]));
    /* メッセージを送信する */
    if (sendto(sock,DATA, sizeof DATA ,0,
        (struct sockaddr *) &name,sizeof name) == -1)
        perror("sending datagram message");
    close(sock);
    exit(0);
}

次のコードに、ソケットの作成、名前のバインド、ソケットからのメッセージ読み取りによって、インターネット呼び出しを読み取る例を示します。


例 6–5 インターネットファミリデータグラムの読み取り

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
/*
 * このプログラムはデータグラムソケットを作成し、そのソケットに名前を
 * バインドし、続いてそのソケットから読み取ります
 */
 main()
{
    int sock, length;
    struct sockaddr_in6 name;
    char buf[1024];
    /* 読み取るソケットを作成する */
    sock = socket(AF_INET6, SOCK_DGRAM, 0);
    if (sock == -1) {
        perror("opening datagram socket");
        exit(1);
   }
   /* ワイルドカードを使用して名前を作成する */
   bzero (&name, sizeof (name));
   name.sin6_family = AF_INET6;
   name.sin6_addr = in6addr_any;
   name.sin6_port = 0;
   if (bind (sock, (struct sockaddr *)&name, sizeof (name)) == -1) {
       perror("binding datagram socket");
       exit(1);
   }
   /* 割り当てられたポート値を確認し、それを出力する */
   length = sizeof(name);
   if (getsockname(sock,(struct sockaddr *) &name, &length)
         == -1) 	{
       perror("getting socket name");
       exit(1);
   }
   printf("Socket port #%d\n", ntohs(name.sin6_port));
   /* ソケットから読み取りを行う */
   if (read(sock, buf, 1024) == -1 )
       perror("receiving datagram packet");
   /* データが出力可能であると想定する */
   printf("-->%s\n", buf);
   close(sock);
   exit(0);
}

標準ルーチン

この節では、ネットワークアドレスを検出したり、構築したりするルーチンについて説明します。特に明記しない限り、インターネットファミリだけに適用されます。

リモートホスト上のサービスを検出するには、クライアントとサーバーが通信を行う前にさまざまなレベルの割り当てを行う必要があります。サービスには、人が使用するための名前が付いています。サービス名とホスト名は、ネットワークアドレスに変換され、そのネットワークアドレスを使用してホストを検出し、ホストへの経路を指定します。割り当ての細部は、ネットワークアーキテクチャによって異なります。望ましいのは、ホストに名前が付けられることをネットワークが必要とせず、物理的なホストの位置の識別情報を保護できることです。

標準ルーチンは、ホスト名をネットワークアドレスに、ネットワーク名をネットワーク番号に、プロトコル名をプロトコル番号に、サービス名をポート番号にマッピングします。標準ルーチンはまた、サーバープロセスとの通信で使用するために適切なプロトコルも指定します。標準ルーチンを使用する場合は、ファイル netdb.h を組み込む必要があります。

ホスト名とサービス名

インタフェース getaddrinfo(3SOCKET)、getnameinfo(3SOCKET)、および freeaddrinfo(3SOCKE) を使用すると、ホスト上のサービスの名前とアドレスを簡単に変換できます。IPv6 の場合、getipnodebyname(3SOCKET) と getservbyname(3SOCKET) を呼び出す代わりに、これらのインタフェースを使用できます。同様に IPv4 でも、gethostbyname(3NSL) と getservbyname(3SOCKET) の代わりに、これらのインタフェースを使用できます。IPv6 アドレスと IPv4 アドレスは、どちらも透過的に処理されます。

getaddrinfo(3SOCKET) ルーチンは、指定されたホスト名とサービス名に結合アドレスとポート番号を返します。getaddrinfo(3SOCKET) が返す情報は動的に割り当てられるので、この情報は freeaddrinfo(3SOCKET) を使用して解放し、メモリーリークを回避する必要があります。getnameinfo(3SOCKET) は、指定されたアドレスとポート番号に関連付けられたホスト名とサービス名を返します。getaddrinfo(3SOCKET) と getnameinfo(3SOCKET) が返す EAI_xxx コードに基づくエラーメッセージを出力するには、gai_strerror(3SOCKET) を呼び出す必要があります。

次に、getaddrinfo(3SOCKET) の使用例を示します。

    struct addrinfo         *res, *aip;
    struct addrinfo         hints;
    int                     sock = -1;
    int                     error;

    /* ホストアドレスを取得する。アドレスのタイプは任意 */
    bzero(&hints, sizeof (hints));
    hints.ai_flags = AI_ALL|AI_ADDRCONFIG;
    hints.ai_socktype = SOCK_STREAM;

    error = getaddrinfo(hostname, servicename, &hints, &res);
    if (error != 0) {
      (void) fprintf(stderr, "getaddrinfo: %s for host %s service %s\n",
      gai_strerror(error), hostname, servicename);
     return (-1);
    }
 

res が指す構造体の getaddrinfo(3SOCKET) が返す情報を処理したあと、freeaddrinfo(res) を使用して記憶領域を解放する必要があります。

次の例に示すように、getnameinfo(3SOCKET) ルーチンはエラーの原因を識別するときに特に便利です。

    struct sockaddr_storage faddr;
    int                     sock, new_sock, sock_opt;
    socklen_t               faddrlen;
    int                     error;
    char                    hname[NI_MAXHOST];
    char                    sname[NI_MAXSERV];

     ...
         faddrlen = sizeof (faddr);
         new_sock = accept(sock, (struct sockaddr *)&faddr, &faddrlen);
         if (new_sock == -1) {
             if (errno != EINTR && errno != ECONNABORTED) {
                 perror("accept");
             }
             continue;
         }        
         error = getnameinfo((struct sockaddr *)&faddr, faddrlen, hname, 
                     sizeof (hname), sname, sizeof (sname), 0);
         if (error) {
           (void) fprintf(stderr, "getnameinfo: %s\n",
                       gai_strerror(error));
         } else {
             (void) printf("Connection from %s/%s\n", hname, sname);
         }

ホスト名 – hostent

gethostent(3NSL) に定義されているように、インターネットホスト名からアドレスへのマッピングは hostent 構造体で表現されます。

struct hostent {
	   char  *h_name;            /* ホストの正式名称 */
	   char  **h_aliases;        /* 別名リスト */
	   int   h_addrtype;         /* ホストアドレスのタイプ (AF_INET6 など) */
	   int   h_length;           /* アドレスの長さ */
	   char  **h_addr_list;      /* NULL で終わるアドレスのリスト */
};
/* 最初のアドレス、ネットワークバイトオーダー */
#define h_addr h_addr_list[0]
getipnodebyname(3SOCKET)

インターネットホスト名を hostent 構造体にマッピングする

getipnodebyaddr(3SOCKET)

インターネットホストアドレスを hostent 構造体にマッピングする

freehostent(3SOCKET)

hostent 構造体のメモリーを解放する

inet_ntop(3SOCKET)

インターネットホストアドレスを表示可能な文字列にマッピングする

このルーチンは、ホストの名前、その別名、アドレスタイプ、および NULL で終わる可変長アドレスのリストを含む hostent 構造体を返します。このアドレスリストが必要なのは、ホストが多くのアドレスを持つことができるためです。h_addr 定義は下位互換性のためであり、この定義は hostent 構造体のアドレスリストの最初のアドレスです。

ネットワーク名 – netent

ネットワーク名を番号に割り当て、netent 構造体を返すルーチンを示します。

/*
 * ネットワーク番号が 32 ビットに収まると想定します。
 */
struct netent {
   char     *n_name;      /* ネットの正式名称 */
   char     **n_aliases;  /* 別名リスト */
   int      n_addrtype;   /* ネットアドレスのタイプ */
   int      n_net;        /* ネット番号、ホストバイトオーダー */
};

getnetbyname(3SOCKET)、getnetbyaddr_r(3SOCKET)、および getnetent(3SOCKET) は、上記のホストルーチンに対応するネットワーク側のルーチンです。

プロトコル名 – protoent

protoent 構造体は、getprotobyname(3SOCKET)、getprotobynumber(3SOCKET)、および getprotoent(3SOCKET) で使用され、getprotoent(3SOCKET) で定義されるプロトコル名マッピングを定義します。

struct protoent {
   char     *p_name;          /* プロトコルの正式名称 */
   char     **p_aliases       /* 別名リスト */
   int      p_proto;          /* プロトコル番号 */
};

サービス名 – servent

インターネットファミリサービスは、特定の既知のポートに常駐し、特定のプロトコルを使用します。サービス名からポート番号へのマッピングは、getprotoent(3SOCKET) で定義されている servent 構造体で表現されます。

struct servent {
   char     *s_name;         /* サービスの正式名称 */
   char     **s_aliases;     /* 別名リスト */
   int      s_port;          /* パート番号、ネットワークバイトオーダー */
   char     *s_proto;        /* 使用するプロトコル */
};

getservbyname(3SOCKET) はサービス名と修飾プロトコル (省略可能) を servent 構造体にマッピングします。次の呼び出しは、

sp = getservbyname("telnet", (char *) 0);
任意のプロトコルを使用する Telnet サーバーのサービス仕様を返します。次の呼び出しは、

sp = getservbyname("telnet", "tcp");

TCP プロトコルを使用するTelnet サーバーを返します。getservbyport(3SOCKET) と getservent(3SOCKET) も提供されます。getservbyport(3SOCKET) に は、getservbyname(3SOCKET) で使用されるインタフェースに似たインタフェースがあります。つまり、オプションのプロトコル名を指定して、ルックアップを修飾できます。

その他のルーチン

その他にも、名前とアドレスの操作を簡易化するルーチンはいくつかあります。次の表に、可変長のバイト列、およびバイトスワッピングのネットワークアドレスと値を要約します。

表 6–2 実行時ライブラリルーチン

インタフェース 

機能説明 

memcmp(3C)

バイト列を比較する。 同じ場合は 0、異なる場合は 0 以外の値を返す

memcpy(3C)

s2 n バイトを s1 にコピーする

memset(3C)

base の最初の n バイトの領域に値 value を割り当てる

htonl(3SOCKET)

ホストからネットワークバイトオーダーへの 32 ビット量の変換 

htons(3SOCKET)

ホストからネットワークバイトオーダーへの 16 ビット量の変換 

ntohl(3SOCKET)

ネットワークからホストバイトオーダーへの 32 ビット量の変換 

ntohs(3SOCKET)

ネットワークからホストバイトオーダーへの 16 ビット量の変換 

バイトスワッピングルーチンを使用するのは、アドレスはネットワークオーダーで供給されるとオペレーティングシステムが考えるためです。一部のアーキテクチャでは、ホストバイトオーダーがネットワークバイトオーダーと異なるため、プログラムは必要に応じて値をバイトスワップする必要があります。そのため、ネットワークアドレスを返すルーチンは、ネットワークオーダーで返します。バイトスワッピング問題が発生するのは、ネットワークアドレスを解釈する場合だけです。たとえば、次のコードは TCP ポートまたは UDP ポートをフォーマットします。

printf("port number %d\n", ntohs(sp->s_port));

これらのルーチンを必要としないマシンでは、アドレスは NULL マクロとして定義されます。

クライアントサーバープログラム

もっとも一般的な分散型アプリケーションは、クライアントサーバーモデルです。このスキーマでは、クライアントプロセスはサーバープロセスからのサービスを要求します。

代替スキーマとして、休止しているサーバープロセスを削除できるサービスサーバーがあります。たとえば、inetd(1M) というインターネットサービスデーモンです。inetd(1M) はさまざまなポートで待機しますが、起動時に構成ファイルを読み取ることによって使用するポートを決定します。inetd(1M) のサービスを受けるポートでコネクションが要求されると、inetd(1M) はクライアントにサービスを行うために適切なサーバーを生成します。クライアントは、そのコネクションで中間媒体が何らかの役割を果たすことは意識しません。inetd(1M) の詳細については、inetd デーモンを参照してください。

ソケットとサービス

ほとんどのサーバーには、既知のインターネットポート番号または UNIX ファミリ名でアクセスします。既知の UNIX ファミリ名の例には、rlogin サービスがあります。例 6–6 に、リモートログインサーバーのメインループを示します。

DEBUG モードで動作していない限り、サーバーはその呼び出し元の制御端末との関連付けを解除します。

   (void) close(0);
   (void) close(1);
   (void) close(2);
   (void) open("/", O_RDONLY);
   (void) dup2(0);
   (void) dup2(0);
   setsid();

関連付けを解除することによって、サーバーは制御端末のプロセスグループからシグナルを受信しません。制御端末との関連付けを解除したあと、サーバーはエラーレポートを制御端末に送信できません。したがって、サーバーは syslog(3C) でエラーを記録する必要があります。

サービスの定義を取得するために、サーバーは getaddrinfo(3SOCKET) を呼び出します。

    bzero(&hints, sizeof (hints));
    hints.ai_flags = AI_ALL|AI_ADDRCONFIG;
    hints.ai_socktype = SOCK_STREAM;
    error = getaddrinfo(NULL, "rlogin", &hints, &aip);

aip に返される結果は、プログラムがサービス要求を待機するインターネットポートを定義します。標準のポート番号の一部は /usr/include/netinet/in.h で定義されています。

次に、サーバーはソケットを作成して、サービス要求を待機します。bind(3SOCKET) ルーチンを使用すると、サーバーは必ず指定された場所で待機します。リモートログインサーバーが待機するポート番号は制限されているため、サーバーはスーパーユーザーとして動作します。次のループに、サーバーのメインループ (本体) を示します。


例 6–6 サーバーのメインループ

    /* コネクション要求を待機する */
    for (;;) {
        faddrlen = sizeof (faddr);
        new_sock = accept(sock, (struct sockaddr *)&faddr, &faddrlen);
        if (new_sock == -1) {
            if (errno != EINTR && errno != ECONNABORTED) {
                perror("rlogind: accept");
            }
            continue;
        }
        if (fork() == 0) {
            close (sock);
            doit (new_sock, &faddr);
        }
        close (new_sock);
    }
    /*NOTREACHED*/

accept(3SOCKET) は、クライアントがサービスを要求するまでメッセージをブロックします。さらに、SIGCHLD などのシグナルによる割り込みを受けた場合、accept(3SOCKET) は失敗を示す値を返します。accept(3SOCKET) からの戻り値を調べて、エラーが発生している場合は syslog(3C) でエラーを記録します。

次に、サーバーは子プロセスをフォークし、リモートログインプロトコル処理の本体を呼び出します。コネクション要求を待ち行列に入れるために親プロセスが使用するソケットは、子プロセスで閉じられます。accept(3SOCKET) が作成したソケットは、親プロセスで閉じられます。クライアントのアドレスがサーバーアプリケーションの doit() ルーチンに渡され、クライアントが認証されます。

ソケットとクライアント

この節では、クライアントリモートログインプロセスで行われる処理について説明します。サーバー側と同様に、まずリモートログインのサービス定義の位置を確認します。

    bzero(&hints, sizeof (hints));
	    hints.ai_flags = AI_ALL|AI_ADDRCONFIG;
	    hints.ai_socktype = SOCK_STREAM;

	    error = getaddrinfo(hostname, servicename, &hints, &res);
	    if (error != 0) {
		  (void) fprintf(stderr, "getaddrinfo: %s for host %s service %s\n",
		                 gai_strerror(error), hostname, servicename);
		  return (-1);
	    }

getaddrinfo(3SOCKET) は、res にあるアドレスの一覧の先頭を返します。希望のアドレスを見つけるには、ソケットを作成し、一覧に返される各アドレスに接続して、動作するアドレスが見つかるまで繰り返します。

for (aip = res; aip != NULL; aip = aip->ai_next) {
		/*
		 * ソケットを開く。アドレスタイプは、提供される
		 * getaddrinfo() によって変わる。
		 */
		sock = socket(aip->ai_family, aip->ai_socktype,
		    aip->ai_protocol);
		if (sock == -1) {
			perror("socket");
			freeaddrinfo(res);
			return (-1);
		}

		/* ホストに接続する */
		if (connect(sock, aip->ai_addr, aip->ai_addrlen) == -1) {
			perror("connect");
			(void) close(sock);
			sock = -1;
			continue;
		}
		break;
	}

ソケットが作成され、希望のサービスに接続されます。sock はバインド解除されているので、connect(3SOCKET) ルーチンは暗黙的に sock をバインドします。

コネクションレス型のサーバー

サービスの中にはデータグラムソケットを使用するものがあります。rwho(1) サービスは、LAN に接続されたホストについての状態情報を提供します。ネットワークトラフィックが重くなるため、in.rwhod(1M) は実行しないでください。rwho サービスは、特定のネットワークに接続されたすべてのホストに情報をブロードキャストします。rwho サービスは、データグラムソケットを使用する例の 1 つです。

rwho(1) サーバープロセスを実行するホスト上のユーザーは、ruptime(1) を使用して別のホストの現在の状態を取得できます。次の例に、典型的な出力例を示します。


例 6–7 ruptime(1) プログラムの出力

itchy up 9:45, 5 users, load 1.15, 1.39, 1.31
scratchy up 2+12:04, 8 users, load 4.67, 5.13, 4.59
click up 10:10, 0 users, load 0.27, 0.15, 0.14
clack up 2+06:28, 9 users, load 1.04, 1.20, 1.65
ezekiel up 25+09:48, 0 users, load 1.49, 1.43, 1.41
dandy 5+00:05, 0 users, load 1.51, 1.54, 1.56
peninsula down 0:24
wood down 17:04
carpediem down 16:09
chances up 2+15:57, 3 users, load 1.52, 1.81, 1.86

各ホストには、rwho(1) サーバープロセスによって状態情報が周期的にブロードキャスト送信されます。このサーバープロセスも状態情報を受信します。このサーバープロセスはまた、データベースを更新します。このデータベースは、各ホストの状態のために解釈されます。サーバーはそれぞれ個別に動作し、ローカルネットワークとそのブロードキャスト機能によってのみ結合されます。

大量のネットトラフィックが生成されるため、ブロードキャストを使用することは非効率的です。サービスが広範囲に渡り、頻繁に使用されない限り、周期的なブロードキャストに手間がかかり簡潔さが失われます。

次に、rwho(1) サーバープロセスの簡単な例を示します。このコードは、まず、ネットワーク上のほかのホストからブロードキャストされた状態情報を受信し、次に、自分が動作しているホストの状態情報を提供します。最初のタスクは、プログラムのメインループで行われます。rwho(1) ポートで受信したパケットが別の rwho(1) サーバープロセスから送信されたことを確認したあと、到着時刻を記録します。次に、パケットはホストの状態でファイルを更新します。一定の時間内にホストからの通信がない場合、データベースルーチンはホストが停止していると想定し、この情報を記録します。ホストが稼働している間にはサーバーが停止していることもあるので、このアプリケーションはよくエラーになります。


例 6–8 rwho(1) サーバープロセス

main()
{
   ...
   sp = getservbyname("who", "udp");
   net = getnetbyname("localnet");
   sin.sin6_addr = inet_makeaddr(net->n_net, in6addr_any);
   sin.sin6_port = sp->s_port;
   ...
   s = socket(AF_INET6, SOCK_DGRAM, 0);
   ...
   on = 1;
   if (setsockopt(s, SOL_SOCKET, SO_BROADCAST, &on, sizeof on)
         == -1) {
      syslog(LOG_ERR, "setsockopt SO_BROADCAST: %m");
      exit(1);
   }
   bind(s, (struct sockaddr *) &sin, sizeof sin);
   ...
   signal(SIGALRM, onalrm);
   onalrm();
   while(1) {
      struct whod wd;
	      int cc, whod, len = sizeof from;
      cc = recvfrom(s, (char *) &wd, sizeof(struct whod), 0,
         (struct sockaddr *) &from, &len);
      if (cc <= 0) {
      if (cc == -1 && errno != EINTR)
         syslog(LOG_ERR, "rwhod: recv: %m");
      continue;
      }
      if (from.sin6_port != sp->s_port) {
         syslog(LOG_ERR, "rwhod: %d: bad from port",
            ntohs(from.sin6_port));
         continue;
      }
      ...
      if (!verify( wd.wd_hostname)) {
         syslog(LOG_ERR, "rwhod: bad host name from %x",
            ntohl(from.sin6_addr.s6_addr));
         continue;
      }
      (void) sprintf(path, "%s/whod.%s", RWHODIR, wd.wd_hostname);
      whod = open(path, O_WRONLY|O_CREAT|O_TRUNC, 0666);
      ...
      (void) time(&wd.wd_recvtime);
      (void) write(whod, (char *) &wd, cc);
      (void) close(whod);
   }
   exit(0);
}

2 つ目のサーバータスクは、そのホストの状態の供給です。このタスクでは、周期的にシステム状態情報を取得し、その情報をメッセージにパッケージ化し、このメッセージをローカルネットワーク上でブロードキャストして、ほかの rwho(1) サーバープロセスに知らせる必要があります。このタスクはタイマーで実行されます。このタスクはシグナルによって起動されます。

状態情報は、ローカルネットワーク上でブロードキャスト送信されます。ブロードキャストをサポートしないネットワークでは、マルチキャストを使用してください。

ソケットの拡張機能

分散型アプリケーションを構築する場合、通常は、これまでに説明したメカニズムで十分対応できます。この節では、拡張機能について説明します。

帯域外データ

ストリームソケットの抽象化には、帯域外データが含まれます。帯域外データは、接続されたストリームソケットペア間の論理的に独立した伝送チャネルです。帯域外データは通常データとは無関係に配信されます。帯域外データ機能が使用される場合、一度に 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);

yes1 で返される場合、次の読み取りはマークのあとのデータを返します。yes1 でない場合は、帯域外データが到着したと想定して、次の読み取りは帯域外シグナルを送信する前にクライアントによって送信されたデータを提供します。割り込みシグナルまたは終了シグナルを受信したときに出力をフラッシュするリモートログインプロセス内のルーチンを以下に示します。このコードは通常データを破棄を示すマークまで読み取った後、帯域外バイトを読み取ります。

プロセスは、初めにマークまでを読み取らずに、帯域外データの読み取りまたは先読みを行うこともできます。基底のプロトコルが通常データと一緒に帯域内にある緊急データを配信するときに、その存在だけを前もって通知する場合、このようなデータにアクセスすることはより困難になります。このようなタイプのプロトコルの例としては、TCP (インターネットファミリにソケットストリームを提供するときに使用されるプロトコル) があります。このようなプロトコルでは、MSG_OOB フラグを指定して recv(3SOCKET) を呼び出したときに、帯域外バイトが到着していないことがあります。このような場合、呼び出しはエラー EWOULDBLOCK を返します。また、入力バッファー内の帯域内データの量によっては、ピアはバッファーが空になるまで (通常のフロー制御によって) 緊急データを送信できなくなる場合があります。この場合、プロセスが待ち行列に入ったデータを十分に読み取って入力バッファーをクリアしてからでないと、ピアは緊急データを送信できません。


例 6–9 帯域外データの受信時における端末入出力のフラッシュ

#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) 呼び出しを発行してソケットを非ブロックに設定します。


例 6–10 非ブロックソケットの設定

#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) 呼び出しを実行します。


例 6–11 ソケットを非同期にする

#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 を使用する手順は次のとおりです。

  1. signal(3C) 呼び出しまたは sigvec(3UCB) 呼び出しを使用して、SIGIO シグナルハンドラを設定する。

  2. fcntl(2) を使用してプロセス ID またはプロセスグループ ID を設定し、シグナルの経路をそれ自体のプロセス ID またはプロセスグループ ID に指定する。ソケットのデフォルトのプロセスグループはグループ 0

  3. ソケットを非同期に変換する (非同期ソケット入出力を参照)。

次のコードに、特定のプロセスがあるソケットに対して要求を行うときに、保留中の要求の情報を受信できるようにする例を示します。SIGURG のハンドラを追加すると、このコードは SIGURG シグナルを受信する目的でも使用できます。


例 6–12 入出力要求の非同期通知

#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);
}

シグナルとプロセスグループ ID

SIGURGSIGIO の場合、各ソケットにはプロセス番号とプロセスグループ 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 が挙げられます。このシグナルは、任意の子プロセスがその状態を変更した場合にプロセスに配信されます。通常、サーバーはこのシグナルを使用して、明示的に終了を待機せずに、あるいは終了状態を周期的にポーリングせずに、終了した子プロセスの「リープ (取得)」を行います。たとえば、前述の例のリモートログインサーバーループは次のように拡張できます。


例 6–13 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 つの要素を使用します。

TCP では、これらの 4 つの組は一意である必要があります。UDP にはこのような要求はありません。ホストは複数のネットワークに常駐でき、ユーザーは割り当てられているポート番号に直接アクセスできません。したがって、ユーザープログラムは必ずしもローカルアドレスとローカルポートに使用する適切な値を認識できるとは限りません。この問題を避けるため、アドレスの一部を指定せずにおき、必要に応じてシステムにこれらの部分を適切に割り当てることができます。これらの組の各部は、ソケット API のさまざまな部分によって指定できます。

bind(3SOCKET)

ローカルアドレスまたはローカルポート (あるいはこの両方)

connect(3SOCKET)

外部アドレスと外部ポート

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 つの基準でローカルポート番号を選択します。

クライアントのポート番号と 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 つの新しい機能をサポートするように拡張されました。

ゼロコピーとチェックサム負荷解除は互いに機能的には依存していませんが、最高の性能を得るには連携して動作する必要があります。チェックサム負荷解除には、ネットワークインタフェースのハードウェアサポートが必要です。このハードウェアサポートがない場合、ゼロコピーは有効になりません。

ゼロコピーには、仮想メモリーページの再マッピングを適用する前に、アプリケーションがページ型のバッファーを供給することが必要です。負荷が高い書き込み時コピーの失敗を避けるには、アプリケーションは伝送側に大きな循環バッファーを使用する必要があります。一般的なバッファー割り当ては 16 の 8K バッファーです。

ソケットオプション

setsockopt(3SOCKET) と getsockopt(3SOCKET) を使用すると、ソケットのオプションを設定および取得できます。たとえば、送信バッファー空間または受信バッファー空間を変更できます。次に、呼び出しの一般的な書式を示します。

setsockopt(s, level, optname, optval, optlen);

および

getsockopt(s, level, optname, optval, optlen);

オペレーティングシステムはいつでもこれらの値を適切に調整できます。

次に、setsockopt(3SOCKET) 呼び出しと getsockopt(3SOCKET) 呼び出しの引数を示します。

s

オプションの適用先であるソケット

level

sys/socket.h 内の記号定数 SOL_SOCKET が示すプロトコルレベル (ソケットレベルなど) を指定する

optname

オプションを指定する、sys/socket.h で定義されている記号定数

optval

オプションの値を示す

optlen

オプションの値の長さを示す

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 で定義) に設定されます。データグラムソケットの場合、typeSOCK_DGRAM です。

inetd デーモン

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) によって起動されたサーバーのソケットはファイル記述子 01 上のクライアントに接続されます。したがって、サーバーはすぐに、読み取り、書き込み、送信、または受信を行うことができます。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 構造体を示します。


例 6–14 net/if.h ヘッダーファイル

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) を示します。


例 6–15 インタフェースフラグの取得

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) コマンドを示します。


例 6–16 インタフェースのブロードキャストアドレス

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) を使用します。

マルチキャストの使用

IP マルチキャストは、タイプ SOCK_DGRAM SOCK_RAW のソケット AF_INET6 AF_INET でのみサポートされます。IP マルチキャストはまた、インタフェースドライバがマルチキャストをサポートするサブネットワーク上でのみサポートされます。

IPv4 マルチキャストデータグラムの送信

マルチキャストデータグラムを送信するには、sendto(3SOCKET) 呼び出しで宛先アドレスとして 224.0.0.0 から 239.255.255.255 までの範囲の IP マルチキャストアドレスを指定します。

デフォルトでは、IP マルチキャストデータグラムは生存期間 (TTL) 1 で送信されます。この値では、データグラムは単一のサブネットワーク外には転送されません。ソケットオプション IP_MULTICAST_TTL を指定すると、後続のマルチキャストデータグラムの TTL を 0 から 255 までの任意の値に設定できます。したがって、マルチキャストの配信範囲を制御できます。

    u_char ttl;
    setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, &ttl,sizeof(ttl))

TTL 0 のマルチキャストデータグラムはどのサブネット上でも伝送されませんが、送信ホストが宛先グループに属しており、送信側ソケットでマルチキャストループバックが有効な場合は、ローカルに配信できます。最初の配信先 (ホップ) となるサブネットが 1 つまたは複数のマルチキャストルーターに接続されている場合、1 より大きな TTL を持つマルチキャストデータグラムを複数のサブネットに配信できます。配信範囲の制御に意味を持たせるために、マルチキャストルーターは TTL しきい値という概念をサポートします。このしきい値は、一定の TTL より少ないデータグラムが一定のサブネットを超えることを回避します。このしきい値は、次のような初期 TTL の値を使用して、マルチキャストデータグラムの規約を実施します。

0

同じホストに制限される

1

同じサブネットに制限される

32

同じサイトに制限される

64

同じ地域に制限される

128

同じ大陸に制限される

255

配信範囲内で制限されない

サイトと地域は厳密には定義されず、サイトはローカルの事柄としてさらに小さな管理ユニットに分割できます。

アプリケーションは、上記の TTL 以外に初期 TTL を選択できます。たとえば、アプリケーションはマルチキャスト照会を送信することによって (つまり、TTL を 0 から開始して、応答を受信するまで、TTL を大きくしていく照会のこと)、ネットワークリソースの拡張リング検索を実行できます。

マルチキャストルーターは、TTL の値にかかわらず、224.0.0.0 から 224.0.0.255 までの宛先アドレスを持つマルチキャストデータグラムを転送しません。この範囲のアドレスは、経路指定プロトコルとその他の低レベルトポロジの発見または保守プロトコル (ゲートウェイ発見、グループメンバーシップ報告など) の使用に予約されています。

ホストが複数のマルチキャスト可能なインタフェースを持つ場合でも、各マルチキャスト伝送は単一のネットワークインタフェースから送信されます。ホストがマルチキャストルーターでもあり、TTL が 1 より大きい場合には、発信元以外のインタフェースにもマルチキャストを転送できます。ソケットオプションを使用すると、特定のソケットからの後続の転送用のデフォルトを変更できます。

    struct in_addr addr;
    setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, &addr, sizeof(addr))

addr は、希望する発信インタフェースのローカル IP アドレスです。デフォルトインタフェースに戻すには、アドレス INADDR_ANY を指定します。インタフェースのローカル IP アドレスを取得するには、SIOCGIFCONF ioctl を使用します。インタフェースがマルチキャストをサポートするかどうかを判断するには、 SIOCGIFFLAGS ioctl を使用してインタフェースフラグを取り出し、IFF_MULTICAST フラグが設定されているかどうかをテストします。このオプションは、インターネットトポロジと明確な関係があるマルチキャストルーターなどのシステムサービスを主な対象としています。

送信ホスト自体が属しているグループにマルチキャストデータグラムが送信された場合、デフォルトでは、データグラムのコピーが IP 層によってローカル配信用にループバックされます。次のように別のソケットオプションを使用すると、送信側は明示的に、後続のデータグラムがループバックされるかどうかを制御できます。

    u_char loop;
    setsockopt(sock, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop))  

loop の値は、ループバックを無効にする場合は 0、ループバックを有効にする場合は 1 です。このオプションを使用すると、自分自身の伝送を受信するというオーバーヘッドを排除できるので、単一のホストに単一のインスタンスしか持たないアプリケーションの性能が上がります。単一のホストに複数のインスタンスを持つアプリケーションや送信側が宛先グループに属さないアプリケーションは、このオプションを使用してはなりません。

送信ホストが別のインタフェースの宛先グループに属している場合、1 を超える初期 TTL で送信されたマルチキャストデータグラムは、他方のインタフェース上の送信ホストに配信できます。このような配信には、ループバック制御オプションは何の効果もありません。

IPv4 マルチキャストデータグラムの受信

IP マルチキャストデータグラムを受信するためには、ホストは 1 つまたは複数の IP マルチキャストグループのメンバーになる必要があります。プロセスは、次のソケットオプションを使用して、マルチキャストグループに加わるようにホストに求めることができます。

    struct ip_mreq mreq;
    setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) 

mreq は次の構造体です。

    struct ip_mreq {
        struct in_addr imr_multiaddr;   /* 加わるマルチキャストグループ */
        struct in_addr imr_interface;   /* 加わるインタフェース */
    }  

各メンバーシップは単一のインタフェースに関連付けられます。したがって、複数のインタフェース上にある同じグループに加わることができます。デフォルトのマルチキャストインタフェースを選択するには、imr_interface アドレスに in6addr_any を指定します。特定のマルチキャスト可能なインタフェースを選択するには、ホストのローカルアドレスの 1 つを指定します。

メンバーシップを取り消すには、次のコードを使用します。

    struct ip_mreq mreq;
    setsockopt(sock, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq))  

mreq には、メンバーシップの追加に使用した値と同じ値が入ります。ソケットを閉じるか、ソケットを保持しているプロセスを停止すると、そのソケットに関連付けられたメンバーシップは取り消されます。特定のグループ内で複数のソケットがメンバーシップを要求でき、ホストは最後の要求が取り消されるまでそのグループのメンバーにとどまります。

任意のソケットがデータグラムの宛先グループのメンバーシップを要求した場合、カーネル IP 層は受信マルチキャストパケットを受け入れます。特定のソケットがマルチキャストデータグラムを受信するかどうかは、ソケットに関連付けられた宛先ポートとメンバーシップ、または、raw ソケットのプロトコルタイプによって決定されます。特定のポートに送信されたマルチキャストデータグラムを受信するには、ローカルアドレスを未指定のまま (INADDR_ANY などに指定) ローカルポートにバインドします。

bind(3SOCKET) の前に次に示すコードがあると、複数のプロセスを同じ SOCK_DGRAM UDP ポートにバインドできます。

    int one = 1;
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one))  

この場合、共有ポートに向けられた各受信マルチキャストまたは受信ブロードキャスト UDP データグラムは、そのポートにバインドされているすべてのソケットに配信されます。下位互換性の理由から、この配信は単一キャストの受信データグラムには適用されません。データグラムの宛先ポートにバインドされているソケットの数にかかわらず、単一キャストデータグラムが複数のソケットに配信されることはありません。SOCK_RAW ソケットは、SO_REUSEADDR オプションがなくても単一の IP プロトコルタイプを共有できます。

マルチキャストに関連する新しいソケットオプションの説明は、<netinet/in.h> を参照してください。IP アドレスはすべて、ネットワークバイトオーダーで渡されます。

IPv6 マルチキャストデータグラムの送信

IPv6 マルチキャストデータグラムを送信するには、sendto(3SOCKET) 呼び出しで宛先アドレスとして ff00::0/8 という範囲内の IP マルチキャストアドレスを指定します。

デフォルトでは、IP マルチキャストデータグラムはホップ制限 1 で送信されます。この値では、データグラムは単一のサブネットワーク外には転送されません。ソケットオプション IPV6_MULTICAST_HOPS を指定すると、後続のマルチキャストデータグラムのホップ制限を 0 から 255 までの任意の値に設定できます。したがって、マルチキャストの配信範囲を制御できます。

    uint_l;
    setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &hops,sizeof(hops))

ホップ制限 0 のマルチキャストデータグラムはどのサブネットにも伝送できませんが、次の場合、データグラムはローカルに配信できます。

最初の配信先 (ホップ) となるサブネットが 1 つまたは複数のマルチキャストルーターに接続されている場合、1 より大きなホップ制限を持つマルチキャストデータグラムを複数のサブネットに配信できます。IPv4 マルチキャストアドレスと異なり、IPv6 マルチキャストアドレスには、アドレスの最初の部分にコード化された明示的な配信範囲情報が含まれます。定義されている配信範囲を次に示します (X は未指定)。

ffX1::0/16

ノード - ローカルな配信範囲 — 同じノードに制限される

ffX2::0/16

リンク - ローカルな配信範囲

ffX5::0/16

サイト - ローカルな配信範囲

ffX8::0/16

組織 - ローカルな配信範囲

ffXe::0/16

グローバルスコープ

アプリケーションは、マルチキャストアドレスの配信範囲とは個別に、異なるホップ制限値を使用できます。たとえば、アプリケーションはマルチキャスト照会を送信することによって (つまり、ホップ制限を 0 から開始して、応答を受信するまで、ホップ制限を大きくしていく照会のこと)、ネットワークリソースの拡張リング検索を実行できます。

ホストが複数のマルチキャスト可能なインタフェースを持つ場合でも、各マルチキャスト伝送は単一のネットワークインタフェースから送信されます。ホストがマルチキャストルーターでもあり、ホップ制限が 1 より大きい場合には、発信元以外のインタフェースにもマルチキャストを転送できます。ソケットオプションを使用すると、特定のソケットからの後続の転送用のデフォルトを変更できます。

    uint_t ifindex;

    ifindex = if_nametoindex )"hme3");
    setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_IF, &ifindex,
          sizeof(ifindex))

ifindex は、希望する発信インタフェースのインタフェースインデックスです。デフォルトインタフェースに戻すには、値 0 を指定します。

送信ホスト自体が属しているグループにマルチキャストデータグラムが送信された場合、デフォルトでは、データグラムのコピーが IP 層によってローカル配信用にループバックされます。別のソケットオプションを使用すると、送信側は明示的に、後続のデータグラムをループバックするかどうかを制御できます。

    uint_t loop;
    setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &loop, 
            sizeof(loop))  

loop の値は、ループバックを無効にする場合は 0、ループバックを有効にする場合は 1 です。単一のホストにインスタンスを 1 つしか持たないアプリケーション (ルーターやメールデーモンなど) では、このオプションを使用するとアプリケーション自体の伝送を受信するオーバーヘッドが排除されるため、性能が向上します。このオプションは、単一のホスト上に複数のインスタンスを持つアプリケーション (会議システムプログラムなど) や送信側が宛先グループに属さないアプリケーション (時間照会プログラムなど) に使用してはなりません。

送信ホストが別のインタフェースの宛先グループに属している場合、1 を超えるホップ制限で送信されたマルチキャストデータグラムは、他方のインタフェース上の送信ホストに配信できます。このような配信には、ループバック制御オプションは何の効果もありません。

IPv6 マルチキャストデータグラムの受信

IP マルチキャストデータグラムを受信するためには、ホストは 1 つまたは複数の IP マルチキャストグループのメンバーになる必要があります。プロセスは、次のソケットオプションを使用して、マルチキャストグループに加わるようにホストに求めることができます。

    struct ipv6_mreq mreq;
    setsockopt(sock, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) 

mreq は次の構造体です。

    struct ipv6_mreq {
        struct in6_addr ipv6mr_multiaddr;   /* IPv6 マルチキャストアドレス */
        unsigned int    ipv6mr_interface;   /* インタフェースインデックス */
    }  

各メンバーシップは単一のインタフェースに関連付けられます。したがって、複数のインタフェース上にある同じグループに加わることができます。デフォルトのマルチキャストインタフェースを選択するには、ipv6_interface 0 を指定します。マルチキャスト可能なインタフェースを選択するには、ホストのインタフェースの 1 つのインタフェースインデックスを指定します。

グループから抜けるには、次のコードを使用します。

    struct ipv6_mreq mreq;
    setsockopt(sock, IPPROTO_IPV6, IP_LEAVE_GROUP, &mreq, sizeof(mreq))  

mreq には、メンバーシップの追加に使用した値と同じ値が入ります。ソケットを閉じるか、ソケットを保持しているプロセスを停止すると、そのソケットに関連付けられたメンバーシップは取り消されます。複数のソケットは特定のグループ内の 1 つのメンバーシップを要求できます。このとき、ホストは最後の要求が取り消されるまでそのグループのメンバーに残ります。

任意のソケットがデータグラムの宛先グループのメンバーシップを要求した場合、カーネル IP 層は受信マルチキャストパケットを受け入れます。特定のソケットがマルチキャストデータグラムを受信するかどうかは、ソケットに関連付けられた宛先ポートとメンバーシップ、または、raw ソケットのプロトコルタイプによって決定されます。特定のポートに送信されたマルチキャストデータグラムを受信するには、ローカルアドレスを未指定のまま (INADDR_ANY などに指定) ローカルポートにバインドします。

bind(3SOCKET) の前に次に示すコードがあると、複数のプロセスを同じ SOCK_DGRAM UDP ポートにバインドできます。

    int one = 1;
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one))  

この場合、ポートにバインドされているすべてのソケットは、共有ポートに向けられたすべての受信マルチキャスト UDP データグラムを受信します。下位互換性の理由から、この配信は単一キャストの受信データグラムには適用されません。データグラムの宛先ポートにバインドされているソケットの数にかかわらず、単一キャストデータグラムが複数のソケットに配信されることはありません。SOCK_RAW ソケットは、SO_REUSEADDR オプションがなくても単一の IP プロトコルタイプを共有できます。

マルチキャストに関連する新しいソケットオプションの説明は、<netinet/in.h> を参照してください。IP アドレスはすべて、ネットワークバイトオーダーで渡されます。