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

ソケットの基本的な使用

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

ソケットの作成

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 シグナル (ソケットの拡張機能を参照) によって、出力の完了、入力の有効性、および例外条件の非同期通知を指定できます。