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

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

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


注 –

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


SunOS 4 のバイナリ互換性

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

ソケットの概要

ソケットは、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 環境がサポートする 4 つのタイプのソケットを示します。

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

インタフェースセット

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

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

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

XNS 5 (Unix03) ソケットインタフェースを使用するアプリケーションを構築する方法については、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 で定義されています。AF_INET6AF_INET 、および AF_UNIX では、SOCK_STREAMSOCK_DGRAM または SOCK_RAW のタイプがサポートされます。インターネットファミリでストリームソケットを作成する例です。

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);                /* Allow queue of 5 connections */
fromlen = sizeof(from);
newsock = accept(s, (struct sockaddr *) &from, &fromlen);

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

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

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

コネクションエラー

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

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

表 8–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 引数が重要です。次のうちの 1 つまたは複数が必要である場合、flags 引数 (sys/socket.h で定義) は 0 以外の値として指定できます。

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 つの例に、インターネットファミリのストリームコネクションの開始と受け入れを示します。

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

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

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


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

#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#define TRUE 1   
/*
 * This program creates a socket and then begins an infinite loop.
 * Each time through the loop it accepts a connection and prints
 * data from it. When the connection breaks, or the client closes
 * the connection, the program accepts a new connection.
*/
main() {
    int sock, length;
    struct sockaddr_in6 server;
    int msgsock;
    char buf[1024];
    int rval;
    /* Create socket. */
    sock = socket(AF_INET6, SOCK_STREAM, 0);
    if (sock == -1) {
        perror("opening stream socket");
        exit(1);
    }
    /* Bind socket using wildcards.*/
    bzero (&server, sizeof(server));
    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);
    }
    /* Find out assigned port number and print it out. */
    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));
    /* Start accepting connections. */
    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, sizeof(buf))) == -1)
                perror("reading stream message");
            if (rval == 0)
                printf("Ending connection\n");
            else
                /* assumes the data is printable */
                printf("-->%s\n", buf);
        } while (rval > 0);
        close(msgsock);
    } while(TRUE);
    /*
     * Since this program has an infinite loop, the socket "sock" is
     * never explicitly closed. However, all sockets are closed
     * automatically when a process is killed or terminates normally.
     */
    exit(0);
}

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


例 8–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 . . ."   
/* 
 * This program creates a socket and initiates a connection with 
 * the socket given in the command line. Some data are sent over the 
 * connection and then the socket is closed, ending the connection.
 * The form of the command line is: streamwrite hostname portnumber 
 * Usage: pgm host port 
 */ 
main(int argc, char *argv[])
{
    int sock, errnum, h_addr_index;
    struct sockaddr_in6 server;
    struct hostent *hp;
    char buf[1024];
    /* Create socket. */
    sock = socket( AF_INET6, SOCK_STREAM, 0);
    if (sock == -1) {
        perror("opening stream socket");
        exit(1);
    }
    /* Connect socket using name specified by command line. */
    bzero (&server, sizeof (server));
    server.sin6_family = AF_INET6;
    hp = getipnodebyname(argv[1], AF_INET6, AI_DEFAULT, &errnum);
/*
 * getipnodebyname returns a structure including the network address
 * of the specified host.
 */
    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) {
                /* Try next address */
                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);
}

ストリームソケットに 1 対 1 の SCTP コネクションのサポートを追加できます。次の例のコードでは、既存のプログラムに -p を追加することにより、使用するプロトコルをプログラムで指定できるようにしています。


例 8–3 ストリームソケットへの SCTP サポートの追加

#include <stdio.h>
#include <netdb.h>
#include <string.h>
#include <errno.h>

int
main(int argc, char *argv[])
{
    struct protoent *proto = NULL;
    int c;
    int s;
    int protocol;

    while ((c = getopt(argc, argv, "p:")) != -1) {
        switch (c) {
        case 'p':
            proto = getprotobyname(optarg);
            if (proto == NULL) {
                fprintf(stderr, "Unknown protocol: %s\n",
                        optarg);
                return (-1);
            }
            break;
        default:
            fprintf(stderr, "Unknown option: %c\n", c);
            return (-1);
        }
    }

    /* Use the default protocol, which is TCP, if not specified. */
    if (proto == NULL)
        protocol = 0;
    else
        protocol = proto->p_proto;

    /* Create a IPv6 SOCK_STREAM socket of the protocol. */
    if ((s = socket(AF_INET6, SOCK_STREAM, protocol)) == -1) {
        fprintf(stderr, "Cannot create SOCK_STREAM socket of type %s: "
                "%s\n", proto != NULL ? proto->p_name : "tcp",
                strerror(errno));
        return (-1);
    }
    printf("Success\n");
    return (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) がセットをクリアします。

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) 呼び出しでいつ新しいコネクションをピックアップできるかどうかタイミングを判定する方法を示します。このプログラムは、コネクション要求を受け入れ、データを読み取り、単一のソケットで切断します。


例 8–4 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
/*
 * This program uses select to check that someone is
 * trying to connect before calling accept.
 */
main() {
    int sock, length;
    struct sockaddr_in6 server;
    int msgsock;
    char buf[1024];
    int rval;
    fd_set ready;
    struct timeval to;
    /* Open a socket and bind it as in previous examples. */
    /* Start accepting connections. */
    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, sizeof(buf))) == -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) 手順は省略できます。

図 8–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) を使用します。呼び出しの前に、fromlenfrom バッファーのサイズに設定されます。fromlen にはデータグラムの配信元であるアドレスのサイズが設定されて返されます。

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

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

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


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

#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 . . ."   
/*
 * Here I send a datagram to a receiver whose name I get from
 * the command line arguments. The form of the command line is:
 * dgramsend hostname portnumber
 */
main(int argc, char *argv[])
{
    int sock, errnum;
    struct sockaddr_in6 name;
    struct hostent *hp;
    /* Create socket on which to send. */
    sock = socket(AF_INET6,SOCK_DGRAM, 0);
    if (sock == -1) {
        perror("opening datagram socket");
        exit(1);
    }
    /*
     * Construct name, with no wildcards, of the socket to ``send''
     * to. getinodebyname returns a structure including the network
     * address of the specified host. The port number is taken from
     * the command line.
     */
    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]));
    /* Send message. */
    if (sendto(sock,DATA, sizeof DATA ,0,
        (struct sockaddr *) &name,sizeof name) == -1)
        perror("sending datagram message");
    close(sock);
    exit(0);
}

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


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

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
/*
 * This program creates a datagram socket, binds a name to it, then
 * reads from the socket.
 */
 main()
{
    int sock, length;
    struct sockaddr_in6 name;
    char buf[1024];
    /* Create socket from which to read. */
    sock = socket(AF_INET6, SOCK_DGRAM, 0);
    if (sock == -1) {
        perror("opening datagram socket");
        exit(1);
    }
    /* Create name with wildcards. */
    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);
    }
    /* Find assigned port value and print it out. */
    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));
    /* Read from the socket. */
    if (read(sock, buf, 1024) == -1 )
        perror("receiving datagram packet");
    /* Assumes the data is printable */
    printf("-->%s\n", buf);
    close(sock);
    exit(0);
}

標準ルーチン

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

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

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

ホスト名とサービス名

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

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

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

struct addrinfo        *res, *aip;
struct addrinfo        hints;
int                    error;

/* Get host address.  Any type of address will do. */
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;            /* official name of host */
    char  **h_aliases;        /* alias list */
    int   h_addrtype;         /* hostaddrtype(e.g.,AF_INET6) */
    int   h_length;           /* length of address */
    char  **h_addr_list;      /* list of addrs, null terminated */
};
/*1st addr, net byte order*/
#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 構造体を返すルーチンです。

/*
 * Assumes that a network number fits in 32 bits.
 */
struct netent {
   char     *n_name;      /* official name of net */
   char     **n_aliases;  /* alias list */
   int      n_addrtype;   /* net address type */
   int      n_net;        /* net number, host byte order */
};

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

プロトコル名 – protoent

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

struct protoent {
    char    *p_name;       /* official protocol name */
    char    **p_aliases    /* alias list */
    int     p_proto;       /* protocol number */
};

サービス名 – servent

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

struct servent {
    char    *s_name;        /* official service name */
    char    **s_aliases;    /* alias list */
    int     s_port;         /* port number, network byte order */
    char    *s_proto;       /* protocol to use */
};

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

sp = getservbyname("telnet", (char *) 0);

任意のプロトコルを使用する Telnet サーバーのサービス仕様を返します。次の呼び出しは、

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

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

その他のルーチン

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

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

インタフェース 

機能説明 

memcmp(3C)

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

memcpy(3C)

s2n バイトを 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 サービスがあります。例 8–7 に、リモートログインサーバーのメインループを示します。

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

(void) close(0);
(void) close(1);
(void) close(2);
(void) open("/", O_RDONLY);
(void) dup2(0, 1);
(void) dup2(0, 2);
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, &api);

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

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


例 8–7 サーバーのメインループ

/* Wait for a connection request. */
for (;;) {
    faddrlen = sizeof (faddr);
    new_sock = accept(sock, (struct sockaddr *)api->ai_addr,
            api->ai_addrlen)
    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) {
    /*
     * Open socket.  The address type depends on what
     * getaddrinfo() gave us.
     */
    sock = socket(aip->ai_family, aip->ai_socktype,
          aip->ai_protocol);
    if (sock == -1) {
        perror("socket");
        freeaddrinfo(res);
        return (-1);
    }

    /* Connect to the host. */
    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) を使用して別のホストの現在の状態を取得できます。次の例に、典型的な出力例を示します。


例 8–8 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) サーバープロセスから送信されたことを確認し、到着時刻を記録します。次に、パケットはホストの状態でファイルを更新します。一定の時間内にホストからの通信がない場合、データベースルーチンはホストが停止していると想定し、この情報を記録します。ホストが稼働している間にはサーバーが停止していることもあるので、このアプリケーションはよくエラーになります。


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

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


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

#include <sys/ioctl.h>
#include <sys/file.h>
...
oob()
{
    int out = FWRITE;
    char waste[BUFSIZ];
    int mark = 0;
 
    /* flush local terminal output */
    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) 呼び出しを発行してソケットを非ブロックに設定します。


例 8–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, 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) 呼び出しを実行します。


例 8–12 ソケットを非同期にする

#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 シグナルを受信する目的でも使用できます。


例 8–13 入出力要求の非同期通知

#include <fcntl.h>
#include <sys/file.h>
 ...
signal(SIGIO, io_handler);
/* Set the process receiving SIGIO/SIGURG signals to us. */
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) を使用してソケットをユーザーのプロセスグループに割り当てても、SIGURGSIGIO を受信できるように設定できます。

/* oobdata is the out-of-band data handling routine */
sigset(SIGURG, oobdata);
int pid = -getpid();
if (ioctl(client, SIOCSPGRP, (char *) &pid) < 0) {
    perror("ioctl: SIOCSPGRP");
}

特定のプロトコルの選択

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 が返されます。

ソケットオプション

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)) {
            /* this is a IPv4-mapped IPv6 address */
            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 構造体を示します。


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

struct ifreq {
    #define IFNAMSIZ 16
    char ifr_name[IFNAMSIZ]; /* if name, e.g., "en0" */
    union {
        struct sockaddr ifru_addr;
        struct sockaddr ifru_dstaddr;
        char ifru_oname[IFNAMSIZ]; /* other if name */
        struct sockaddr ifru_broadaddr;
        short ifru_flags;
        int ifru_metric;
        char ifru_data[1]; /* interface dependent data */
        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
};

インタフェース構成を取得する呼び出しは以下の通りです。

/*
 * Do SIOCGIFNUM ioctl to find the number of interfaces
 *
 * Allocate space for number of interfaces found
 *
 * Do SIOCGIFCONF with allocated buffer
 *
 */
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) を示します。


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

struct ifreq *ifr;
ifr = ifc.ifc_req;
for (n = ifc.ifc_len/sizeof (struct ifreq); --n >= 0; ifr++) {
    /*
     * Be careful not to use an interface devoted to an address
     * family other than those intended.
     */
    if (ifr->ifr_addr.sa_family != AF_INET)
        continue;
    if (ioctl(s, SIOCGIFFLAGS, (char *) ifr) < 0) {
        ...
    }
    /* Skip boring cases */
    if ((ifr->ifr_flags & IFF_UP) == 0 ||
            (ifr->ifr_flags & IFF_LOOPBACK) ||
            (ifr->ifr_flags & (IFF_BROADCAST | IFF_POINTOPOINT)) == 0)
        continue;
}

次の例では、インタフェースのブロードキャストアドレスを取得するための SIOGGIFBRDADDR ioctl(2) コマンドを示します。


例 8–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_DGRAMSOCK_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;   /* multicast group to join */
    struct in_addr imr_interface;   /* interface to join on */
}

各メンバーシップは単一のインタフェースに関連付けられます。したがって、複数のインタフェース上にある同じグループに加わることができます。デフォルトのマルチキャストインタフェースを選択するには、 imr_interface アドレスに INADDR_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 multicast addr */
    unsigned int       ipv6mr_interface;    /* interface index */
}

各メンバーシップは単一のインタフェースに関連付けられます。したがって、複数のインタフェース上にある同じグループに加わることができます。デフォルトのマルチキャストインタフェースを選択するには、ipv6_interface0 を指定します。マルチキャスト可能なインタフェースを選択するには、ホストのインタフェースの 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 アドレスはすべて、ネットワークバイトオーダーで渡されます。

Stream Control Transmission Protocol (SCTP)

ストリーム制御伝送プロトコル (SCTP) は、TCP が提供するサービスと同様のサービスを提供する信頼性の高いトランスポートプロトコルです。さらに、SCTP はネットワークレベルの耐障害性を提供します。SCTP では、関連付けの両端でマルチホーミングがサポートされます。SCTP ソケット API は、TCP にならった 1 対 1 ソケットスタイルをサポートします。その他に SCTP ソケット API は、シグナリングで使用するために設計された 1 対多ソケットスタイルもサポートします。1 対多ソケットスタイルは、プロセスで使用されるファイル記述子の数を削減します。SCTP 関数の呼び出しを使用するには、libsctp ライブラリをリンクする必要があります。

SCTP 関連付けは 2 つの終端の間で設定します。各終端は、特定のタイプのサービス拒否 (DoS) 攻撃から保護するために、cookie による 4 ウェイハンドシェークを使用します。複数の IP アドレスによって終端を表現できます。

SCTP スタックの実装

この節では、IETF 勧告の Stream Control Transmission Protocol (RFC 2960) および Stream Control Transmission Protocol Checksum Change (RFC 3309) について、Solaris における実装の詳細を表に示します。ここでは、RFC 2960 勧告の実装例外について次の表に示します。Solaris オペレーティングシステムの SCTP プロトコルは、RFC 3309 のすべてと、RFC 2960 のセクションのうちこの表に明記されていないセクションのすべてを実装しています。

表 8–3 RFC 2960 との比較における Solaris SCTP 実装の例外

RFC 2960 のセクション 

Solaris 実装における例外 

3. SCTP Packet Format 

3.2 Chunk Field Descriptions: Solaris SCTP は、省略可能な ECNE および CWR を実装していません。 

3.3.2: Solaris SCTP は、Initiation (INIT) の Optional ECN、Host Name Address、および Cookie Preserving パラメータを実装していません。 

3.3.3: Solaris SCTP は、Initiation Acknowledgement、Optional ECN、および Host Name Address パラメータを実装していません。 

5. Association Initialization 

5.1.2, Handle Address Parameters: セクション (B) の Optional Host Name は実装されていません。 

6. User Data Transfer 

6.8, Adler-32 Checksum Calculation: RFC 3309 によって、このセクションは廃止されています。 

10. Interface with Upper Layer 

Solaris SCTP は、IETF TSVWG SCTP ソケット API ドラフトを実装しています。 


注 –

TSVWG SCTP ソケット API の Solaris 10 実装は、Solaris 10 が最初に出荷されたときに公開されていた API ドラフトのバージョンに基づいています。


SCTP ソケットインタフェース

socket() 呼び出しは、IPPROTO_SCTP のソケットを作成するとき、SCTP 固有のソケット作成ルーチンを呼び出します。SCTP ソケットでソケットを呼び出すと、自動的に適切な SCTP ソケットルーチンが呼び出されます。1 対 1 ソケットの場合、各ソケットは 1 つの SCTP 関連付けに対応します。1 対 1 ソケットを作成するには、次の関数を呼び出します。

socket(AF_INET[6], SOCK_STREAM, IPPROTO_STCP);

1 対多スタイルソケットの場合、各ソケットは複数の SCTP 関連付けを処理します。各関連付けには、sctp_assoc_t と呼ばれる関連付け識別子があります。1 対多ソケットを作成するには、次の関数を呼び出します。

socket(AF_INET[6], SOCK_SEQPACKET, IPPROTO_STCP);

sctp_bindx()

int sctp_bindx(int sock, void *addrs, int addrcnt, int flags);

sctp_bindx() 関数は、SCTP ソケット上のアドレスを管理します。sock パラメータが IPv4 ソケットである場合、sctp_bindx() 関数に渡されるアドレスは IPv4 アドレスである必要があります。sock パラメータが IPv6 ソケットである場合、sctp_bindx() 関数に渡されるアドレスは IPv4 アドレスまたは IPv6 アドレスのどちらかになります。sctp_bindx() 関数に渡されるアドレスが INADDR_ANY または IN6ADDR_ANY であると、ソケットは使用可能なすべてのアドレスにバインドします。bind(3SOCKET) を使用して SCTP 終端をバインドします。

*addrs パラメータの値は、1 つ以上のソケットアドレスの配列へのポインタです。各アドレスは、それぞれ該当する構造体に含まれます。アドレスが IPv4 アドレスである場合、sockaddr_in 構造体または sockaddr_in6 構造体に含まれます。IPv6 アドレスである場合、sockaddr_in6 構造体に含まれます。アドレスタイプ群によって、アドレス長が異なります。呼び出し元は、配列内のアドレスの数を addrcnt パラメータによって指定します。

sctp_bindx() 関数は成功すると 0 を返します。失敗すると、sctp_bindx() 関数は -1 を返し、errno の値を該当するエラーコードに設定します。

各ソケットアドレスに同じポートが指定されていない場合、sctp_bindx() 関数は失敗し、errno の値を EINVAL に設定します。

flags パラメータは、現在定義されている次のフラグの 0 以上に対してビット単位の論理和演算を実行することによって求められます。

SCTP_BINDX_ADD_ADDR は、指定されたアドレスを関連付けに追加するように SCTP に指示します。SCTP_BINDX_REM_ADDR は、指定されたアドレスを関連付けから削除するように SCTP に指示します。この 2 つのフラグは相互に排他です。両方が指定された場合、sctp_bindx() は失敗して errno の値を EINVAL に設定します。

呼び出し元は、関連付けからすべてのアドレスを削除することはできません。このような試みは拒否され、sctp_bindx() 関数は失敗して errno の値が EINVAL に設定されます。アプリケーションは、bind() 関数を呼び出したあとに sctp_bindx(SCTP_BINDX_ADD_ADDR) を使用することにより、追加のアドレスを終端に関連付けることができます。またアプリケーションは、sctp_bindx(SCTP_BINDX_REM_ADDR) を使用することにより、待機しているソケットに関連付けられているアドレスを削除できます。アドレスを削除するために sctp_bindx(SCTP_BINDX_REM_ADDR) を使用したあとは、新しい関連付けを受け入れても、削除されたアドレスは再び関連付けられません。終端が動的アドレスをサポートする場合、SCTP_BINDX_REM_ADDR または SCTP_BINDX_ADD_ADDR を使用すると、ピアにメッセージが送信されてピアのアドレスリストが変更されます。接続されている関連付けにおけるアドレスの追加および削除は、オプションの機能です。この機能をサポートしない実装では、EOPNOTSUPP が返されます。

アドレス群が AF_INET または AF_INET6 ではない場合、sctp_bindx() 関数は失敗して EAFNOSUPPORT を返します。sctp_bindx() に渡される sock パラメータのファイル記述子が無効である場合、sctp_bindx() 関数は失敗して EBADF を返します。

sctp_opt_info()

int sctp_opt_info(int sock, sctp_assoc_id_t id, int opt, void *arg, socklen_t *len);

sctp_opt_info() 関数は、sock パラメータに記述されるソケットに関連付けられている SCTP レベルのオプションを返します。1 対多スタイルの SCTP ソケットの場合、id パラメータの値は特定の関連付けを指します。1 対 1 スタイルの SCTP ソケットの場合、id パラメータは無視されます。opt パラメータの値は、取得する SCTP ソケットオプションを指定します。arg パラメータの値は、呼び出し元プログラムによって割り当てられるオプション固有の構造体バッファーです。*len パラメータの値は、オプションの長さです。

opt パラメータは、次の値を取ることができます。

SCTP_RTOINFO

調整可能な再伝送タイムアウト(RTO) を初期化および設定するために使用されるプロトコルのパラメータを返します。プロトコルのパラメータは次の構造体を使用します。

struct sctp_rtoinfo {
    sctp_assoc_t    srto_assoc_id;
    uint32_t        srto_initial;
    uint32_t        srto_max; 
    uint32_t        srto_min;
};
srto_assoc_id

対象になる関連付けを指定するこの値は、呼び出し元プログラムが提供します。

srto_initial

この値は初期 RTO 値です。

srto_max

この値は最大 RTO 値です。

srto_min

この値は最小 RTO 値です。

SCTP_ASSOCINFO

関連付け固有のパラメータを返します。パラメータは次の構造体を使用します。

struct sctp_assocparams {
    sctp_assoc_t    sasoc_assoc_id;
    uint16_t        sasoc_asocmaxrxt;
    uint16_t        sasoc_number_peer_destinations;
    uint32_t        sasoc_peer_rwnd;
    uint32_t        sasoc_local_rwnd;
    uint32_t        sasoc_cookie_life;
};
sasoc_assoc_id

対象になる関連付けを指定するこの値は、呼び出し元プログラムが提供します。

sasoc_assocmaxrxt

この値は、関連付けに対する再伝送の最大数を指定します。

sasoc_number_peer_destinations

この値は、ピアが持つアドレスの数を指定します。

sasoc_peer_rwnd

この値は、ピアの受信ウィンドウの現在値を指定します。

sasoc_local_rwnd

この値は、ピアの送信先の、最後に報告された受信ウィンドウを指定します。

sasoc_cookie_life

この値は、関連付けの cookie の存続時間を指定し、cookie を発行する際に使用されます。

時間の値を使用するすべてのパラメータはミリ秒単位です。

SCTP_DEFAULT_SEND_PARAM

sendto(3SOCKET) 関数の呼び出しが関連付けに関して使用するパラメータのデフォルトセットを返します。パラメータは次の構造体を使用します。

struct sctp_sndrcvinfo {
    uint16_t        sinfo_stream;
    uint16_t        sinfo_ssn;
    uint16_t        sinfo_flags;
    uint32_t        sinfo_ppid;
    uint32_t        sinfo_context;
    uint32_t        sinfo_timetolive;
    uint32_t        sinfo_tsn;
    uint32_t        sinfo_cumtsn;
    sctp_assoc_t    sinfo_assoc_id;
};
sinfo_stream

この値は、sendmsg() 呼び出しのデフォルトストリームを指定します。

sinfo_ssn

この値は常に 0 です。

sinfo_flags

この値は、sendmsg() 呼び出しのデフォルトフラグです。フラグは次の値を取ることができます。

  • MSG_UNORDERED

  • MSG_ADDR_OVER

  • MSG_ABORT

  • MSG_EOF

  • MSG_PR_SCTP

sinfo_ppid

この値は、sendmsg() 呼び出しのデフォルトのペイロードプロトコル識別子です。

sinfo_context

この値は、sendmsg() 呼び出しのデフォルトコンテキストです。

sinfo_timetolive

この値は、ミリ秒単位で期間を指定します。この期間を経過すると、伝送が開始されていないメッセージは期限切れになります。値が 0 の場合は、メッセージが期限切れにならないことを示します。MSG_PR_SCTP フラグが設定されている場合、sinfo_timetolive に指定された期間内に伝送が正常に完了しないメッセージは期限切れになります。

sinfo_tsn

この値は常に 0 です。

sinfo_cumtsn

この値は常に 0 です。

sinfo_assoc_id

この値は、呼び出し元アプリケーションによって設定され、対象になる関連付けを指定します。

SCTP_PEER_ADDR_PARAMS

指定されたピアのアドレスのパラメータを返します。パラメータは次の構造体を使用します。

struct sctp_paddrparams {
    sctp_assoc_t               spp_assoc_id;
    struct sockaddr_storage    spp_address;
    uint32_t                   spp_hbinterval;
    uint16_t                   spp_pathmaxrxt;
};
spp_assoc_id

対象になる関連付けを指定するこの値は、呼び出し元プログラムが提供します。

spp_address

この値は、対象になるピアのアドレスを指定します。

spp_hbinterval

この値は、ミリ秒単位でハートビート間隔を指定します。

spp_pathmaxrxt

この値は、あるアドレスを到達不能と見なすまでの、再伝送を試みる最大回数を指定します。

SCTP_STATUS

関連付けに関する現在の状態情報を返します。パラメータは次の構造体を使用します。

struct sctp_status {
    sctp_assoc_t             sstat_assoc_id;
    int32_t                  sstat_state;
    uint32_t                 sstat_rwnd;
    uint16_t                 sstat_unackdata;
    uint16_t                 sstat_penddata;
    uint16_t                 sstat_instrms;
    uint16_t                 sstat_outstrms;
    uint32_t                 sstat_fragmentation_point;
    struct sctp_paddrinfo    sstat_primary;
};
sstat_assoc_id

対象になる関連付けを指定するこの値は、呼び出し元プログラムが提供します。

sstat_state

この値は、関連付けの現在の状態を示し、関連付けは次の状態を取ることができます。

SCTP_IDLE

SCTP 終端がいずれの関連付けとも関連付けられていません。socket() 関数が終端を開いた直後、または、終端が閉じられた直後、終端はこの状態になります。

SCTP_BOUND

bind() の呼び出しのあと、SCTP 終端が 1 つ以上のローカルアドレスにバインドされています。

SCTP_LISTEN

リモート SCTP 終端からの関連付け要求を終端が待機しています。

SCTP_COOKIE_WAIT

SCTP 終端が INIT チャンクを送信し、INIT-ACK チャンクを待機しています。

SCTP_COOKIE_ECHOED

SCTP 終端が、ピアの INIT-ACK チャンクから受信した cookie をピアにエコーバックしました。

SCTP_ESTABLISHED

SCTP 終端はピアとデータを交換できます。

SCTP_SHUTDOWN_PENDING

SCTP 終端が上位層から SHUTDOWN プリミティブを受信しました。この終端は上位層からデータを受け入れません。

SCTP_SHUTDOWN_SEND

SCTP_SHUTDOWN_PENDING 状態にあった SCTP 終端が SHUTDOWN チャンクをピアに送信しました。SHUTDOWN チャンクが送信されるのは、終端からピアへの未処理データがすべて確認されたあとだけです。終端のピアが SHUTDOWN ACK チャンクを送信すると、終端は SHUTDOWN COMPLETE チャンクを送信し、関連付けが閉じられたと見なされます。

SCTP_SHUTDOWN_RECEIVED

SCTP 終端がピアから SHUTDOWN チャンクを受信しました。この終端はそのユーザーから新しいデータを受け入れません。

SCTP_SHUTDOWN_ACK_SEND

SCTP_SHUTDOWN_RECEIVED 状態の SCTP 終端がピアに SHUTDOWN ACK チャンクを送信しました。終端が SHUTDOWN ACK チャンクを送信するのは、その終端からのすべての未処理データをピアが確認したあとだけです。終端のピアが SHUTDOWN COMPLETE チャンクを送信すると、関連付けは閉じられます。

sstat_rwnd

この値は、関連付けピアの現在の受信ウィンドウです。

sstat_unackdata

この値は、未確認 DATA チャンクの数です。

sstat_penddata

この値は、受信を待機している DATA チャンクの数です。

sstat_instrms

この値は、着信ストリームの数です。

sstat_outstrms

この値は、発信ストリームの数です。

sstat_fragmentation_point

メッセージ、SCTP ヘッダー、および IP ヘッダーを含めたサイズが sstat_fragmentation_point の値を超える場合、メッセージは分割されます。この値は、パケットの着信先アドレスのパス最大伝送ユニット (P-MTU) と同じです。

sstat_primary

この値は、プライマリピアのアドレスに関する情報です。この情報は次の構造体を使用します。

struct sctp_paddrinfo {
    sctp_assoc_t               spinfo_assoc_id;
    struct sockaddr_storage    spinfo_address;
    int32_t                    spinfo_state;
    uint32_t                   spinfo_cwnd;
    uint32_t                   spinfo_srtt;
    uint32_t                   spinfo_rto;
    uint32_t                   spinfo_mtu;
};
spinfo_assoc_id

対象になる関連付けを指定するこの値は、呼び出し元プログラムが提供します。

spinfo_address

この値は、プライマリピアのアドレスです。

spinfo_state

この値は、SCTP_ACTIVE または SCTP_INACTIVE の 2 つの値のいずれかを取ります。

spinfo_cwnd

この値は、ピアアドレスの輻輳ウィンドウです。

spinfo_srtt

この値は、ピアアドレスに関する現在の平滑化された RTT の計算値です。ミリ秒単位で示されます。

spinfo_rto

この値は、ピアアドレスに関する現在の伝送タイムアウト値です。ミリ秒単位で示されます。

spinfo_mtu

この値は、ピアアドレスに関する P-MTU です。

sctp_opt_info() 関数は成功すると 0 を返します。失敗すると、sctp_opt_info() 関数は -1 を返し、errno の値を該当するエラーコードに設定します。sctp_opt_info() に渡される sock パラメータのファイル記述子が無効である場合、sctp_opt_info() 関数は失敗して EBADF を返します。sctp_opt_info() 関数に渡される sock パラメータのファイル記述子がソケットではない場合、sctp_opt_info() 関数は失敗して ENOTSOCK を返します。関連付け ID が 1 対多スタイル SCTP ソケットに対して無効である場合、sctp_opt_info() 関数は失敗して errno の値を EINVAL に設定します。指定されたオプションに対して入力バッファー長が短すぎる場合、sctp_opt_info() 関数は失敗して errno の値を EINVAL に設定します。ピアのアドレスのアドレス群が AF_INET または AF_INET6 ではない場合、sctp_opt_info() 関数は失敗して errno の値を EAFNOSUPPORT に設定します。

sctp_recvmsg()

ssize_t sctp_recvmsg(int s, void *msg, size_t len, struct sockaddr *from, socklen_t *fromlen, struct sctp_sndrcvinfo *sinfo , int *msg_flags);

sctp_recvmsg() 関数は、s パラメータによって指定される SCTP 終端からのメッセージの受信を有効にします。呼び出し元プログラムによって次の属性を指定できます。

msg

このパラメータは、メッセージバッファーのアドレスです。

len

このパラメータは、メッセージバッファーの長さです。

from

このパラメータは、送信元のアドレスを含むアドレスへのポインタです。

fromlen

from パラメータのアドレスに関連付けられたバッファーのサイズです。

sinfo

このパラメータは、呼び出し元プログラムで sctp_data_io_events が有効な場合のみ有効です。sctp_data_io_events を有効にするには、ソケットオプション SCTP_EVENTS() によって setsockopt 関数を呼び出します。sctp_data_io_events が有効である場合、アプリケーションは着信メッセージごとに sctp_sndrcvinfo 構造体の内容を受信します。このパラメータは、sctp_sndrcvinfo 構造体へのポインタです。構造体はメッセージの受信時にデータを取り込みます。

msg_flags

このパラメータは、存在するメッセージフラグを含みます。

sctp_recvmsg() 関数は、受信するバイト数を返します。エラーが発生した場合、sctp_recvmsg() 関数は -1 を返します。

s パラメータの渡されたファイル記述子が有効でない場合、sctp_recvmsg() 関数は失敗して errno の値を EBADF に設定します。s パラメータに渡されたファイル記述子がソケットでない場合、sctp_recvmsg() 関数は失敗して errno の値を ENOTSOCK に設定します。msg_flags パラメータに値 MSG_OOB が含まれる場合、sctp_recvmsg() 関数は失敗して errno の値を EOPNOTSUPP に設定します。関連付けが確立していない場合、sctp_recvmsg() 関数は失敗して errno の値を ENOTCONN に設定します。

sctp_sendmsg()

ssize_t sctp_sendmsg(int s, const void *msg, size_t len, const struct sockaddr *to, socklen_t tolen, uint32_t ppid, uint32_t flags, uint16_t stream_no, uint32_t timetolive, uint32_t context);

sctp_sendmsg() 関数は、SCTP 終端からメッセージを送信する際の拡張 SCTP 機能を有効にします。

s

この値は、メッセージを送信する SCTP 終端を指定します。

msg

この値は、sctp_sendmsg() 関数によって送信されるメッセージです。

len

この値は、メッセージの長さで、バイト単位で表されます。

to

この値は、メッセージの着信先アドレスです。

tolen

この値は、着信先アドレスの長さです。

ppid

この値は、アプリケーション固有のペイロードプロトコル識別子です。

stream_no

この値は、メッセージのターゲットストリームです。

timetolive

この値は、メッセージが正常にピアに送信されなかった場合に期限切れになるまでの期間で、ミリ秒単位で示されます。

context

メッセージの送信時にエラーが発生した場合に、この値が返されます。

flags

この値は、0 以上の次のフラグビットに対するビット単位の論理和演算を実行することによって求められます。

MSG_UNORDERED

このフラグが設定されている場合 sctp_sendmsg() 関数はメッセージを順不同で配信します。

MSG_ADDR_OVER

このフラグが設定されている場合、sctp_sendmsg() 関数は関連付けのプライマリ着信先アドレスではなく、to パラメータのアドレスを使用します。このフラグは 1 対多スタイル SCTP ソケットの場合にのみ使用されます。

MSG_ABORT

このフラグが設定されている場合、指定された関連付けは ABORT シグナルをピアに送信して異常終了します。このフラグは 1 対多スタイル SCTP ソケットの場合にのみ使用されます。

MSG_EOF

このフラグが設定されている場合、指定された関連付けは適切に終了します。このフラグは 1 対多スタイル SCTP ソケットの場合にのみ使用されます。

MSG_PR_SCTP

このフラグが設定されている場合、timetolive パラメータに指定された期間内に伝送が正常に完了しないメッセージは期限切れになります。

sctp_sendmsg() 関数は、送信したバイト数を返します。エラーが発生した場合、sctp_sendmsg() 関数は -1 を返します。

s パラメータに渡されたファイル記述子が有効でない場合、sctp_sendmsg() 関数は失敗して errno の値を EBADF に設定します。s パラメータの渡されたファイル記述子がソケットでない場合、sctp_sendmsg() 関数は失敗して errno の値を ENOTSOCK に設定します。flags パラメータに値 MSG_OOB が含まれる場合、sctp_sendmsg() 関数は失敗して errno の値を EOPNOTSUPP に設定します。1 対 1 スタイルソケットで flags パラメータに値 MSG_ABORT または MSG_EOF が含まれる場合、sctp_sendmsg() 関数は失敗して errno の値を EOPNOTSUPP に設定します。関連付けが確立していない場合、sctp_sendmsg() 関数は失敗して errno の値を ENOTCONN に設定します。ソケットが停止していてそれ以上の書込みができない場合、sctp_sendmsg() 関数は失敗して errno の値を EPIPE に設定します。ソケットが非ブロックで、伝送待ち行列がいっぱいである場合、sctp_sendmsg() 関数は失敗して errno の値を EAGAIN に設定します。

制御メッセージ長が不正である場合、sctp_sendmsg() 関数は失敗して errno の値を EINVAL に設定します。指定された着信先アドレスが関連付けに属さない場合、sctp_sendmsg() 関数は失敗して errno の値を EINVAL に設定します。stream_no の値が関連付けによってサポートされる発信ストリームの数以外である場合、sctp_sendmsg() 関数は失敗して errno の値を EINVAL に設定します。指定された着信先アドレスのアドレス群が AF_INET または AF_INET6 でない場合、sctp_sendmsg() 関数は失敗して errno の値を EINVAL に設定します。

sctp_send()

ssize_t sctp_send(int s, const void *msg, size_t len, const struct sctp_sndrcvinfo *sinfo , int flags);

sctp_send() 関数は、1 対 1 スタイルソケットと 1 対多スタイルソケットで使用できます。sctp_send() 関数は、SCTP 終端からメッセージを送信する際の拡張 SCTP 機能を有効にします。

s

この値は、socket() 関数によって作成されるソケットです。

msg

この値は、sctp_send() 関数によって送信されるメッセージです。

len

この値は、メッセージの長さで、バイト単位で表されます。

sinfo

この値は、メッセージの送信のために使用されるパラメータです。1 対多スタイルソケットの場合、メッセージの送信先の関連付け ID をこの値に指定できます。

flags

この値は、sendmsg() 関数のフラグパラメータと同じです。

sctp_send() 関数は、送信したバイト数を返します。エラーが発生した場合、sctp_send() 関数は -1 を返します。

s パラメータの渡されたファイル記述子が有効でない場合、sctp_send() 関数は失敗して errno の値を EBADF に設定します。s パラメータの渡されたファイル記述子がソケットでない場合、sctp_send() 関数は失敗して errno の値を ENOTSOCK に設定します。sinfo パラメータの sinfo_flags フィールドに値 MSG_OOB が含まれる場合、sctp_send() 関数は失敗して errno の値を EOPNOTSUPP に設定します。1 対 1 スタイルソケットで sinfo パラメータの sinfo_flags フィールドに値 MSG_ABORT または MSG_EOF が含まれる場合、sctp_send() 関数は失敗して errno の値を EOPNOTSUPP に設定します。関連付けが確立していない場合、sctp_send() 関数は失敗して errno の値を ENOTCONN に設定します。ソケットが停止していてそれ以上の書込みができない場合、sctp_send() 関数は失敗して errno の値を EPIPE に設定します。ソケットが非ブロックで、伝送待ち行列がいっぱいである場合、sctp_send() 関数は失敗して errno の値を EAGAIN に設定します。

制御メッセージ長が不正である場合、sctp_send() 関数は失敗して errno の値を EINVAL に設定します。指定された着信先アドレスが関連付けに属さない場合、sctp_send() 関数は失敗して errno の値を EINVAL に設定します。stream_no の値が関連付けによってサポートされる発信ストリームの数以外である場合、sctp_send() 関数は失敗して errno の値を EINVAL に設定します。指定された着信先アドレスのアドレス群が AF_INET または AF_INET6 ではない場合、sctp_send() 関数は失敗して errno の値を EINVAL に設定します。

分岐関連付け

アプリケーションは、1 対多スタイルソケットで確立された関連付けを別のソケットとファイル記述子に分岐できます。散発的なメッセージの送信側または受信側が多数あり、元の 1 対多スタイルソケットのままである必要があるアプリケーションの場合に、別のソケットとファイル記述子を使用すると便利です。アプリケーションは、大量のデータトラフィックを伝送する関連付けを別のソケット記述子に分岐します。アプリケーションは、sctp_peeloff() 呼び出しを使用して、関連付けを別のソケットに分岐します。新しいソケットは 1 対 1 スタイルソケットです。sctp_peeloff() 関数の構文は次のとおりです。

int sctp_peeloff(int sock, sctp_assoc_t id);
sock

socket() システムコールから返される元の 1 対多スタイルソケット記述子。

id

別のファイル記述子に分岐する関連付けの識別子。

sock に渡されるソケット記述子が 1 対多スタイル SCTP ソケットではない場合、sctp_peeloff() 関数は失敗して EOPTNOTSUPP を返します。id() の値が 0 である場合、または、id の値が sock パラメータに渡されるソケット記述子の関連付けの最大数より大きい場合、sctp_peeloff 関数は失敗して EINVAL を返します。sctp_peeloff() 関数が新しいユーザーファイル記述子またはファイル構造体を作成できない場合、関数は失敗して EMFILE を返します。

sctp_getpaddrs()

sctp_getpaddrs() 関数は、関連付け内のすべてのピアを返します。

int sctp_getpaddrs(int sock, sctp_assoc_t id, void **addrs);

sctp_getpaddrs() 関数が正常に結果を返した場合、**addrs パラメータの値は、動的に割り当てられるパックされた配列である、各アドレスの適切なタイプの sockaddr 構造体を指します。呼び出し元スレッドは、sctp_freepaddrs() 関数によってメモリーを解放します。**addrs パラメータに値 NULL はありません。sock に指定されるソケット記述子が IPv4 ソケット用である場合、sctp_getpaddrs() 関数は IPv4 アドレスを返します。sock に指定されるソケット記述子が IPv6 ソケット用である場合、sctp_getpaddrs() 関数は IPv4 アドレスと IPv6 アドレスを混在して返します。1 対多スタイルソケットの場合、id パラメータは照会する関連付けを指定します。1 対 1 スタイルソケットの場合、sctp_getpaddrs() 関数は id パラメータを無視します。sctp_getpaddrs() 関数が正常に結果を返す場合、関連付け内のピアアドレスの数が返されます。ソケット上に関連付けがない場合、sctp_getpaddrs() 関数は 0 を返し、**addrs パラメータの値は未定義です。エラーが発生した場合、sctp_getpaddrs() 関数は -1 を返し、**addrs パラメータの値は未定義です。

sctp_getpaddrs() に渡される sock パラメータのファイル記述子が無効である場合、sctp_getpaddrs() 関数は失敗して EBADF を返します。sctp_getpaddrs() 関数に渡される sock パラメータのファイル記述子がソケットでない場合、sctp_getpaddrs() 関数は失敗して ENOTSOCK を返します。sctp_getpaddrs() 関数に渡される sock パラメータのファイル記述子が接続されていないソケットである場合、sctp_getpaddrs() 関数は失敗して ENOTCONN を返します。

sctp_freepaddrs()

sctp_freepaddrs() 関数は、それ以前の sctp_getpaddrs() の呼び出しによって割り当てられたすべての資源を解放します。sctp_freepaddrs() 関数の構文は次のとおりです。

void sctp_freepaddrs(void *addrs);

*addrs パラメータは、sctp_getpaddrs() 関数によって返されたピアのアドレスを含む配列です。

sctp_getladdrs()

sctp_getladdrs() 関数は、ソケット上のローカルでバインドされているすべてのアドレスを返します。sctp_getladdrs() 関数の構文は次のとおりです。

int sctp_getladdrs(int sock, sctp_assoc_t id, void **addrs);

sctp_getladdrs() 関数が正常に結果を返した場合、addrs の値は動的に割り当てられるパックされた配列の sockaddr 構造体を指します。sockaddr 構造体は各ローカルアドレスの適切なタイプです。呼び出し元アプリケーションは、sctp_freeladdrs() 関数を使用してメモリーを解放します。addrs パラメータの値が NULL であってはなりません。

sd パラメータによって参照されるソケットが IPv4 ソケットである場合、sctp_getladdrs() 関数は IPv4 アドレスを返します。sd パラメータによって参照されるソケットが IPv6 ソケットである場合、sctp_getladdrs() 関数は必要に応じて IPv4 アドレスと IPv6 アドレスを混在して返します。

1 対多スタイルソケットで sctp_getladdrs() 関数が起動されると、id パラメータは照会する関連付けを指定します。1 対 1 ソケットで sctp_getladdrs() 関数で動作する場合、id パラメータは無視されます。

id パラメータの値が 0 である場合、sctp_getladdrs() 関数は特定の関連付けに関係なくローカルでバインドされているアドレスを返します。sctp_getladdrs() 関数が正常に結果を返す場合、ソケットにバインドされているローカルアドレスの数を報告します。ソケットがバインドされていない場合、sctp_getladdrs() 関数は 0 を返し、*addrs の値は未定義です。エラーが発生した場合、sctp_getladdrs() 関数は -1 を返し、*addrs パラメータの値は未定義です。

sctp_freeladdrs()

sctp_freeladdrs() 関数は、それ以前の sctp_getladdrs() の呼び出しによって割り当てられたすべての資源を解放します。sctp_freeladdrs() 関数の構文は次のとおりです。

void sctp_freeladdrs(void *addrs);

*addrs パラメータは、sctp_getladdrs() 関数によって返されたピアのアドレスを含む配列です。

SCTP を使用したコーディング例

この節では、SCTP ソケットの 2 つの使用法を示します。


例 8–17 SCTP エコークライアント

/*
 * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/* To enable socket features used for SCTP socket. */
#define _XPG4_2
#define __EXTENSIONS__

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <netinet/sctp.h>
#include <netdb.h>

#define BUFLEN 1024

static void
usage(char *a0)
{
    fprintf(stderr, "Usage: %s <addr>\n", a0);
}

/*
 * Read from the network.
 */
static void
readit(void *vfdp)
{
    int   fd;
    ssize_t   n;
    char   buf[BUFLEN];
    struct msghdr  msg[1];
    struct iovec  iov[1];
    struct cmsghdr  *cmsg;
    struct sctp_sndrcvinfo *sri;
    char   cbuf[sizeof (*cmsg) + sizeof (*sri)];
    union sctp_notification *snp;

    pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);

    fd = *(int *)vfdp;

    /* Initialize the message header for receiving */
    memset(msg, 0, sizeof (*msg));
    msg->msg_control = cbuf;
    msg->msg_controllen = sizeof (*cmsg) + sizeof (*sri);
    msg->msg_flags = 0;
    cmsg = (struct cmsghdr *)cbuf;
    sri = (struct sctp_sndrcvinfo *)(cmsg + 1);
    iov->iov_base = buf;
    iov->iov_len = BUFLEN;
    msg->msg_iov = iov;
    msg->msg_iovlen = 1;

    while ((n = recvmsg(fd, msg, 0)) > 0) {
        /* Intercept notifications here */
        if (msg->msg_flags & MSG_NOTIFICATION) {
            snp = (union sctp_notification *)buf;
            printf("[ Receive notification type %u ]\n",
                snp->sn_type);
            continue;
        }
        msg->msg_control = cbuf;
        msg->msg_controllen = sizeof (*cmsg) + sizeof (*sri);
        printf("[ Receive echo (%u bytes): stream = %hu, ssn = %hu, "
            "flags = %hx, ppid = %u ]\n", n,
            sri->sinfo_stream, sri->sinfo_ssn, sri->sinfo_flags,
            sri->sinfo_ppid);
    }

    if (n < 0) {
        perror("recv");
        exit(1);
    }

    close(fd);
    exit(0);
}

#define MAX_STREAM 64

static void
echo(struct sockaddr_in *addr)
{
    int     fd;
    uchar_t     buf[BUFLEN];
    ssize_t    n;
    int    perr;
    pthread_t   tid;
    struct cmsghdr   *cmsg;
    struct sctp_sndrcvinfo  *sri;
    char    cbuf[sizeof (*cmsg) + sizeof (*sri)];
    struct msghdr   msg[1];
    struct iovec   iov[1];
    int    ret;
    struct sctp_initmsg   initmsg;
    struct sctp_event_subscribe events;

    /* Create a one-one SCTP socket */
    if ((fd = socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP)) == -1) {
        perror("socket");
        exit(1);
    }

    /*
     * We are interested in association change events and we want
     * to get sctp_sndrcvinfo in each receive.
     */
    events.sctp_association_event = 1; 
    events.sctp_data_io_event = 1; 
    ret = setsockopt(fd, IPPROTO_SCTP, SCTP_EVENTS, &events,
        sizeof (events));
    if (ret < 0) {
        perror("setsockopt SCTP_EVENTS");
        exit(1);
    }

    /*
     * Set the SCTP stream parameters to tell the other side when
     * setting up the association.
     */
    memset(&initmsg, 0, sizeof(struct sctp_initmsg));
    initmsg.sinit_num_ostreams = MAX_STREAM;
    initmsg.sinit_max_instreams = MAX_STREAM;
    initmsg.sinit_max_attempts = MAX_STREAM;
    ret = setsockopt(fd, IPPROTO_SCTP, SCTP_INITMSG, &initmsg,
        sizeof(struct sctp_initmsg));
    if (ret < 0) {
        perror("setsockopt SCTP_INITMSG");
        exit(1);
    }

    if (connect(fd, (struct sockaddr *)addr, sizeof (*addr)) == -1) {
        perror("connect");
        exit(1);
    }

    /* Initialize the message header structure for sending. */
    memset(msg, 0, sizeof (*msg));
    iov->iov_base = buf;
    msg->msg_iov = iov;
    msg->msg_iovlen = 1;
    msg->msg_control = cbuf;
    msg->msg_controllen = sizeof (*cmsg) + sizeof (*sri);
    msg->msg_flags |= MSG_XPG4_2;

    memset(cbuf, 0, sizeof (*cmsg) + sizeof (*sri));
    cmsg = (struct cmsghdr *)cbuf;
    sri = (struct sctp_sndrcvinfo *)(cmsg + 1);

    cmsg->cmsg_len = sizeof (*cmsg) + sizeof (*sri);
    cmsg->cmsg_level = IPPROTO_SCTP;
    cmsg->cmsg_type  = SCTP_SNDRCV;

    sri->sinfo_ppid   = 1;
    /* Start sending to stream 0. */
    sri->sinfo_stream = 0;

    /* Create a thread to receive network traffic. */
    perr = pthread_create(&tid, NULL, (void *(*)(void *))readit, &fd);

    if (perr != 0) {
        fprintf(stderr, "pthread_create: %d\n", perr);
        exit(1);
    }

    /* Read from stdin and then send to the echo server. */
    while ((n = read(fileno(stdin), buf, BUFLEN)) > 0) {
        iov->iov_len = n;
        if (sendmsg(fd, msg, 0) < 0) {
            perror("sendmsg");
            exit(1);
        }
        /* Send the next message to a different stream. */
        sri->sinfo_stream = (sri->sinfo_stream + 1) % MAX_STREAM;
    }

    pthread_cancel(tid);
    close(fd);
}

int
main(int argc, char **argv)
{
    struct sockaddr_in addr[1];
    struct hostent *hp;
    int error;

    if (argc < 2) {
        usage(*argv);
        exit(1);
    }

    /* Find the host to connect to. */ 
    hp = getipnodebyname(argv[1], AF_INET, AI_DEFAULT, &error);
    if (hp == NULL) {
        fprintf(stderr, "host not found\n");
        exit(1);
    }

    addr->sin_family = AF_INET;
    addr->sin_addr.s_addr = *(ipaddr_t *)hp->h_addr_list[0];
    addr->sin_port = htons(5000);

    echo(addr);

    return (0);
}


例 8–18 SCTP エコーサーバー

/*
 * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/* To enable socket features used for SCTP socket. */
#define _XPG4_2
#define __EXTENSIONS__

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/sctp.h>

#define BUFLEN 1024

/*
 * Given an event notification, print out what it is.
 */
static void
handle_event(void *buf)
{
    struct sctp_assoc_change *sac;
    struct sctp_send_failed  *ssf;
    struct sctp_paddr_change *spc;
    struct sctp_remote_error *sre;
    union sctp_notification  *snp;
    char    addrbuf[INET6_ADDRSTRLEN];
    const char   *ap;
    struct sockaddr_in  *sin;
    struct sockaddr_in6  *sin6;

    snp = buf;

    switch (snp->sn_header.sn_type) {
    case SCTP_ASSOC_CHANGE:
        sac = &snp->sn_assoc_change;
        printf("^^^ assoc_change: state=%hu, error=%hu, instr=%hu "
            "outstr=%hu\n", sac->sac_state, sac->sac_error,
            sac->sac_inbound_streams, sac->sac_outbound_streams);
        break;
    case SCTP_SEND_FAILED:
        ssf = &snp->sn_send_failed;
        printf("^^^ sendfailed: len=%hu err=%d\n", ssf->ssf_length,
            ssf->ssf_error);
        break;
    case SCTP_PEER_ADDR_CHANGE:
        spc = &snp->sn_paddr_change;
        if (spc->spc_aaddr.ss_family == AF_INET) {
            sin = (struct sockaddr_in *)&spc->spc_aaddr;
            ap = inet_ntop(AF_INET, &sin->sin_addr, addrbuf,
                INET6_ADDRSTRLEN);
        } else {
            sin6 = (struct sockaddr_in6 *)&spc->spc_aaddr;
            ap = inet_ntop(AF_INET6, &sin6->sin6_addr, addrbuf,
                INET6_ADDRSTRLEN);
        }
        printf("^^^ intf_change: %s state=%d, error=%d\n", ap,
            spc->spc_state, spc->spc_error);
        break;
    case SCTP_REMOTE_ERROR:
        sre = &snp->sn_remote_error;
        printf("^^^ remote_error: err=%hu len=%hu\n",
            ntohs(sre->sre_error), ntohs(sre->sre_length));
        break;
    case SCTP_SHUTDOWN_EVENT:
        printf("^^^ shutdown event\n");
        break;
    default:
        printf("unknown type: %hu\n", snp->sn_header.sn_type);
        break;
    }
}

/*
 * Receive a message from the network.
 */
static void *
getmsg(int fd, struct msghdr *msg, void *buf, size_t *buflen,
    ssize_t *nrp, size_t cmsglen)
{
    ssize_t  nr = 0;
    struct iovec iov[1];

    *nrp = 0;
    iov->iov_base = buf;
    msg->msg_iov = iov;
    msg->msg_iovlen = 1;

    /* Loop until a whole message is received. */
    for (;;) {
        msg->msg_flags = MSG_XPG4_2;
        msg->msg_iov->iov_len = *buflen;
        msg->msg_controllen = cmsglen;

        nr += recvmsg(fd, msg, 0);
        if (nr <= 0) {
            /* EOF or error */
            *nrp = nr;
            return (NULL);
        }

        /* Whole message is received, return it. */
        if (msg->msg_flags & MSG_EOR) {
            *nrp = nr;
            return (buf);
        }

        /* Maybe we need a bigger buffer, do realloc(). */
        if (*buflen == nr) {
            buf = realloc(buf, *buflen * 2);
            if (buf == 0) {
                fprintf(stderr, "out of memory\n");
                exit(1);
            }
            *buflen *= 2;
        }

        /* Set the next read offset */
        iov->iov_base = (char *)buf + nr;
        iov->iov_len = *buflen - nr;

    }
}

/*
 * The echo server.
 */
static void
echo(int fd)
{
    ssize_t   nr;
    struct sctp_sndrcvinfo *sri;
    struct msghdr  msg[1];
    struct cmsghdr  *cmsg;
    char   cbuf[sizeof (*cmsg) + sizeof (*sri)];
    char   *buf;
    size_t   buflen;
    struct iovec  iov[1];
    size_t   cmsglen = sizeof (*cmsg) + sizeof (*sri);

    /* Allocate the initial data buffer */
    buflen = BUFLEN;
    if ((buf = malloc(BUFLEN)) == NULL) {
        fprintf(stderr, "out of memory\n");
        exit(1);
    }

    /* Set up the msghdr structure for receiving */
    memset(msg, 0, sizeof (*msg));
    msg->msg_control = cbuf;
    msg->msg_controllen = cmsglen;
    msg->msg_flags = 0;
    cmsg = (struct cmsghdr *)cbuf;
    sri = (struct sctp_sndrcvinfo *)(cmsg + 1);

    /* Wait for something to echo */
    while ((buf = getmsg(fd, msg, buf, &buflen, &nr, cmsglen)) != NULL) {

        /* Intercept notifications here */
        if (msg->msg_flags & MSG_NOTIFICATION) {
            handle_event(buf);
            continue;
        }

        iov->iov_base = buf;
        msg->msg_iov = iov;
        msg->msg_iovlen = 1;
        iov->iov_len = nr;
        msg->msg_control = cbuf;
        msg->msg_controllen = sizeof (*cmsg) + sizeof (*sri);

        printf("got %u bytes on stream %hu:\n", nr,
               sri->sinfo_stream);
        write(0, buf, nr);

        /* Echo it back */
        msg->msg_flags = MSG_XPG4_2;
        if (sendmsg(fd, msg, 0) < 0) {
            perror("sendmsg");
            exit(1);
        }
    }

    if (nr < 0) {
        perror("recvmsg");
    }
    close(fd);
}

int
main(void)
{
    int    lfd;
    int    cfd;
    int    onoff = 1;
    struct sockaddr_in  sin[1];
    struct sctp_event_subscribe events;
    struct sctp_initmsg  initmsg;

    if ((lfd = socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP)) == -1) {
        perror("socket");
        exit(1);
    }

    sin->sin_family = AF_INET;
    sin->sin_port = htons(5000);
    sin->sin_addr.s_addr = INADDR_ANY;
    if (bind(lfd, (struct sockaddr *)sin, sizeof (*sin)) == -1) {
        perror("bind");
        exit(1);
    }

    if (listen(lfd, 1) == -1) {
        perror("listen");
        exit(1);
    }

    (void) memset(&initmsg, 0, sizeof(struct sctp_initmsg));
    initmsg.sinit_num_ostreams = 64;
    initmsg.sinit_max_instreams = 64;
    initmsg.sinit_max_attempts = 64;
    if (setsockopt(lfd, IPPROTO_SCTP, SCTP_INITMSG, &initmsg,
           sizeof(struct sctp_initmsg)) < 0) { 
        perror("SCTP_INITMSG");
        exit (1);
    }

    /* Events to be notified for */
    (void) memset(&events, 0, sizeof (events));
    events.sctp_data_io_event = 1;
    events.sctp_association_event = 1;
    events.sctp_send_failure_event = 1;
    events.sctp_address_event = 1;
    events.sctp_peer_error_event = 1;
    events.sctp_shutdown_event = 1;

    /* Wait for new associations */
    for (;;) {
        if ((cfd = accept(lfd, NULL, 0)) == -1) {
            perror("accept");
            exit(1);
        }

        /* Enable ancillary data */
        if (setsockopt(cfd, IPPROTO_SCTP, SCTP_EVENTS, &events,
               sizeof (events)) < 0) {
            perror("setsockopt SCTP_EVENTS");
            exit(1);
        }
        /* Echo back any and all data */
        echo(cfd);
    }
}