本ページはプロモーションが含まれています

C言語ソケットプログラム


ソケットについて

ソケットは、ネットワーク上のホスト同士、またはUNIX上で動作するプロセス間で通信を行うためのAPIです。1980年代にUNIX(BSD)上で実装されて以来、通信ライブラリの標準とされています。ソケットを使うことで、物理的なデバイスの送受信処理の詳細を気にせずに、各種プロトコルに規定される手続きに従った通信プログラムを作成することができます。ソケットライブラリはC言語により実装されていますが、他の多くの言語に移植されています。

ソケットディスクリプタ

Linuxに代表されるようなUNIX系OSは、あらゆるI/Oをopen()read()write()close()のシステムコールにより、論理的なファイルの読み書きのように扱います。open()システムコールでファイルディスクリプタを取得し、それを識別子として個々のデバイスの入出力を操作しています。ソケットもファイルディスクリプタと同様な「ソケットディスクリプタ」をsokcet()システムコールにより取得します。取得したディスクリプタは、通信開始の手続きで呼び出す各ソケットAPIや、送受信のシステムコールの関数の引数に指定します。ソケットディスクリプタのことを普通は単純に「ソケット」と呼びます。

プロトコル

ソケットは、異なる種類のホストのアドレッシング設定やプロトコルの指定方法に対応できるよう抽象化されています。ほとんどの場合、ネットワークホスト間の通信では、インターネット・プロトコル(IP)の「TCP」か「UDP」プロトコルを使います。UNIX系OSのプロセス間通信には「UNIXドメインソケット」を使います。
IPv4 TCP

UDP
IPv6 TCP

UDP
プロセス間通信 UNIXドメインソケット

クライアント・サーバ

IPネットワークのホストには、常時起動して通信の到着を待ち受ける側の「サーバ」と、任意に起動してサーバに対し通信を呼びかけ、必要な通信を処理すると終了する「クライアント」があります。サーバプログラムとクライアントプログラムのそれぞれに、ソケットAPIを呼び出す手続きがあります。UNIXドメインのプロセス間通信でも、他のプロセスからのイベントを常時待ち受けるサーバ側と、任意に呼び出すクライアント側があります。

IPネットワークのソケット

ソケットの取得

最初にsocket()システムコールを呼び出し、ソケット(ディスクリプタ)を取得します。
int socket(int ProtocolFamily, int type, int protocol)
ProtocolFamily プロトコルファミリ PF_INET(AF_INET) IPv4を指定する


PF_INET6(AF_INET6) IPv6を指定する


PF_UNIX(AF_UNIX) UNIXドメインを指定する
type ソケットタイプ SOCK_STREAM ストリーム型(=TCP)


SOCK_DGRAM データグラム型(=UDP)
protocol プロトコル IPPROTO_TCP TCPプロトコル


IPPROTO_UDP UDPプロトコル


0 自動的に選択
戻り値にソケットの値が返ります。IP通信の一つのソケットは、
IPアドレス、プロトコル、ポート番号
からなる組み合わせを識別する番号となります。
プロトコルファミリは「PF_〜」と「AF_〜」のどちらでも指定できます。「AF_〜」はアドレスファミリの名前であり、プロトコルファミリとは正確には同じではありません。本来は、プロトコルファミリの中に、複数のアドレスファミリが定義されるような上下関係があるのですが、実際には両方がイコールで定義されており、そのまま現在まで運用されています。厳密にはPF_で指定する方が正しいようですが、一般的にはAF_の方を指定する事例が多いように見えます。
ソケットタイプはストリーム型かデータグラム型かを指定します。これは実質的なTCPかUDPのプロトコルの選択となります。TCPの場合はSOCK_STREAMを指定し、UDPの場合はSOCK_DGRAMを指定します。プロトコルには、通常0(自動選択)を指定します。

ソケットの手続き

ソケットを取得しただけでは通信はできません。使用するポート番号やIPアドレスを設定し、クライアントは接続の手続き、サーバは待受の手続きをしなければなりません。その手続きは、クライアントとサーバでそれぞれの方法があり、TCPとUDPでも異なります。
TCPクライアント
TCPクライアントは、socket()からサーバ接続まで次のように関数を呼び出します。
socket() ソケットの取得
connect() サーバへ接続
write()・read() 送信・受信
close() ソケットを閉じる(切断)
int connect(int socket, struct sockaddr *dest, unsigned int len)
socket ソケット
dest 接続先であるサーバの情報
len destのサイズ
destに指定するsockaddr構造体は次のように定義されています。
struct sockaddr {
    __uint8_t       sa_len;         // 構造体のサイズ
    sa_family_t     sa_family;      // アドレスファミリ
    char            sa_data[14];    // アドレス情報(アドレスファミリによって異なる)
};
sockaddr構造体のsa_dataは14バイトの配列となっており、この中にアドレスファミリに応じた情報が格納されることになっていますが、この配列サイズはあまり意味はなく、実際のところ、AF_INETAF_INET6AF_UNIXの各アドレスファミリに対応した個別の構造体にそれぞれの情報を格納し、そのsockaddr構造体型にキャストしてdestに指定します。すなわち、sa_familyに設定されるアドレスファミリに応じて構造体型が決まります。sa_dataを直接編集することはありません。
AF_INET(IPv4)の場合、sockaddr_in構造体に接続先のサーバ情報を格納します。
struct sockaddr_in {
    __uint8_t       sin_len;        // 構造体のサイズ   
    sa_family_t     sin_family;     // AF_INET
    in_port_t       sin_port;       // ポート番号
    struct in_addr  sin_addr;       // IPv4アドレス
    char            sin_zero[8];    // リザーブ
};
sa_lenはシステムによっては存在しない場合があります。sin_lenには何も設定しなくてもかまいません。sin_familyには、アドレスファミリAF_INETを設定します。
sin_portには、接続するサーバのポート番号を設定します(下の例では8000ですがサーバによります)。このとき、ポート番号の整数(unsigned short)を「ネットワークバイトオーダ」に変換して代入しなければなりません。
dest.sin_port = htons(8000);
バイトオーダとはデータがアドレス昇順か降順のどちらで格納されるかの順番で、いわゆるリトルエンディアンとビッグエンディアンのことです。ネットワーク上のデータは、アドレス昇順のビッグエンディアンとするよう規定されています。ホストのCPUがビッグエンディアンならば、short型やlong型の整数値をメモリに格納されている順のままに送信できますが、リトルエンディアンのシステムでは、数値データのオーダを変換する必要があります。整数値をネットワークバイトオーダに変換するには次の関数を使います。
htonl(x) htons(x)
これらはそれぞれlong型とshort型の整数データxを、現在のプラットフォームのエンディアンに従い適切にネットワークバイトオーダに変換して返します。ビッグエンディアンのシステムならば、これらの関数は引数のxをそのまま返すことになります。
sin_addrには、サーバのIP(v4)アドレスを設定します。inet_addr()は、IPアドレスを表す文字列をin_addr構造体に変換して返します。
dest.sin_addr.s_addr = inet_addr("192.168.0.1");
sockaddr_in構造体に必要な情報を設定したら、connect()の引数destsockaddr構造体にキャストして与えます。lenには、指定したsockaddr_in構造体のサイズを指定します。connect()は、TCPサーバへ接続を試み、成功すると0を返します。これ以降はこのソケットを使って、ファイルのようにread()write()でサーバとデータの送受信が行えます。
close()にてソケットを閉じると、クライアントが終了(切断)します。
以上をあわせたTCPクライアントの例を以下に示します。
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
    int s;
    struct sockaddr_in addr;
    char buf[10];

    if ((s = socket(AF_INET, SOCK_STREAM, 0)) > 0) {
        memset(&addr, 0, sizeof(addr));
        addr.sin_family = AF_INET;
        addr.sin_addr.s_addr = inet_addr(argv[1]);
        addr.sin_port = htons(8000);
        if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) == 0) {
            write(s, "12345", 6);
            read(s, buf, sizeof(buf));
            printf("%s\n", buf);
        }
        close(s);
    }
}
ポート番号
ポート番号は、ホストで動作するアプリケーションを識別するための番号です。IPアドレスによりネットワーク上のホスト(ネットワークインタフェース)を特定し、ポート番号によりホスト内でパケットを送受信するアプリケーションを特定します。
ポート番号は、0〜65535の範囲の値で、TCPとUDPのそれぞれに割り当てられます。同じ番号でもTCPとUDPで区別されます。ユーザアプリケーションが使用できるポート番号は1024番以降で、0〜1023はウェル・ノウン・ポートと呼び、あらゆるシステムで共通的に利用される基本的サービスのために予約されています。厳密には1024番以降の中でも41951番までは、公開されているアプリケーションやサービスが使うポートとしてIANAに登録される領域で、一時的、あるいはプライベート用は41952番以降と区分されています。しかしそれは厳格に守るようなルールではなく、1024番以降はユーザに開放されているものと考えて問題ありません。
クライアント・サーバのサーバ側は、自身のプログラム用の固定したポート番号で待ち受ける必要があります。クライアントはそのポートに向けて接続します。クライアント側のポートはソケットの内部で自動的に割り当てられるので、サーバに接続するたびに変動します。
TCPサーバ
TCPサーバは、socket()からクライアントの接続まで、次のように関数を呼び出します。
socket() ソケットの取得
bind() ソケットにIPアドレスとポート番号を割り当てる
listen() クライアントの待受準備
accept() クライアントからの接続待ち
write()・read() 送信・受信
close() ソケットを閉じる(サーバ終了)
int bind(int socket, struct sockaddr *myaddr, unsigned int len)
socket ソケット
myaddr サーバのアドレス情報
len destのサイズ
bind()はサーバとして使用するIPアドレスとポートをソケットに設定します。ここで設定するTCPのポート番号でクライアントの接続を受け付けます。myaddrには、クライアントの例と同様に、sockaddr_in構造体に必要な情報を設定し、sockaddr構造体にキャストして与えます。このとき、sin_addrには具体的なIPアドレスを指定できますが、通常は自動選択するようINADDR_ANYを設定します。こうすることで複数のネットワーク・インタフェースを持つホストの場合でも、それぞれのインタフェースに入る受信パケットをサーバのポート番号に従って誘導してくれます。従ってbind()は、ソケットにポート番号だけで関連付けが確定できます。アドレスはin_addr構造体のs_addr(long型)へネットワークバイトオーダで代入します。
myaddr.sin_addr.s_addr = htonl(INADDR_ANY);
int listen(int socket int limit)
socket ソケット
limit 同時に待ち受ける数(上限はOSによる)
listen()によりクライアントの接続が許可されます。limitに、同時に待受可能な数を設定します。これを5としている例がよく見られますが、もっと大きい数を指定できます。Linuxの場合は、/proc/sys/net/core/somaxconnで上限値が確認できます。Ubuntu20.04では4096でした。
int accept(int socket, struct sockaddr *cliaddr, unsigned int *len)
socket ソケット
cliaddr クライアントのアドレス情報
len cliaddrのサイズ
accept()を呼び出すと、クライアントからの接続が発生するまで待機します(関数がブロックします)。クライアントからの接続が発生し待機から戻ると、accept()は新たなソケットを戻り値に返します。このソケットは、接続してきたクライアント専用のものです。listen()まで使用するソケットは、不特定のクライアントからの待受のためのもので、いわゆる代表受付のようなものです。
また、accept()cliaddrlenには、接続したクライアントのアドレス情報とそのサイズが格納されて返ります。
accept()から返ったソケットを使いread()write()でクライアントと送受信ができます。このソケットをclose()すると、クライアントとの通信を終了(切断)します。その後、再びsocket()で取得した待受用のソケットを使って、accept()に戻り新たな接続を待つことができます(あるいは切断せずに複数のクライアントを同時接続するようにも実装できます)。
次に、TCPサーバの例を示します。
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
    int s1, s2;
    int fromlen;
    char buf[10];
    struct sockaddr_in addr, from;

    if ((s1 = socket(AF_INET, SOCK_STREAM, 0)) > 0) {
        memset(&addr, 0, sizeof(addr));
        addr.sin_family = AF_INET;
        addr.sin_addr.s_addr = htonl(INADDR_ANY);
        addr.sin_port = htons(8000);
        if (bind(s1, (struct sockaddr *)&addr, sizeof(addr)) == 0) {
            listen(s1, 5);
            fromlen = sizeof(from);
            if ((s2 = accept(s1, (struct sockaddr *)&from, &fromlen)) > 0) {
                printf("accept from %s\n", inet_ntoa(from.sin_addr));
                close(s1);
                read(s2, buf, sizeof(buf));
                printf("%s\n", buf);
                write(s2, "ABCDEFG", 8);
            }
        }
        close(s2);
    }
}
UDPクライアント
UDPクライアントは、socket()からサーバ接続まで、次のように関数を呼び出します。
socket() ソケットの取得
sendto()・recvfrom() 送信・受信
close() ソケットを閉じる(切断)
UDPクライアントは接続を確立しないで通信を行います。そのため、TCPのようにconnect()を呼び出しません。そのかわり、送信・受信のたびに相手先の情報をやりとりする、sendto()recvfrom()というUDP用の送受信関数を使います。
int sendto(int socket, void *msg, unsigned int len, int flags,
    struct sockaddr *dest, unsigned int addrlen)
socket ソケット
msg データ
len 送信するデータのサイズ
flag 通信設定(通常は0)
dest 送信先の情報
addrlen destのサイズ
sendto()は、socket()で取得したソケットを指定し、直接送信先の情報を毎回与えてデータを送信します。戻り値に送信した長さが返ります。destに指定するsockaddr構造体の内容とその長さaddrlenは、TCPクライアントのconnect()に指定するものと同じです。TCPとは異なり送信のたびに送信先情報をsendto()に与えます。UDPは接続を確立しないので、送信したデータが確実に相手に届くことは保証されません。また、連続して送信したデータがその順番通り相手に到着しない可能性があります。
recvfrom(int socket, void *msg, unsigned int len, int flags,
    struct sockaddr *src, unsigned int *addrlen)
socket ソケット
msg データ
len バッファのサイズ
flag 通信設定(通常は0)
src 送信元の情報
addrlen srcのサイズ
recvfrom()はUDP通信により自分に向けて送信されたデータを受信します。バッファmsgに受信データが格納されます。lenにはバッファのサイズを指定します。戻り値に受信したデータの長さが返ります。srcには、送信元のアドレス情報が格納されます。addrlenにはsrcのサイズ(sockaddr_in構造体)のサイズを与えてrecvfrom()を呼び出します。そして受信後にはsrcに格納される送信元のアドレス情報のサイズが格納されて返ります。UDPは接続を確立しないので、受信したデータが想定している相手からのものでない可能性もあります(意図せずポート番号がたまたま一致して迷い込んでくるなど)。
次に、UDPクライアントの例を示します。
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
    int s;
    struct sockaddr_in addr, from;
    char buf[10];
    int fromlen, len, i;

    if ((s = socket(AF_INET, SOCK_DGRAM, 0)) > 0) {
        memset(&addr, 0, sizeof(addr));
        addr.sin_family = AF_INET;
        addr.sin_addr.s_addr = inet_addr("192.168.137.22");
        addr.sin_port = htons(8000);
        len = sendto(s, "ABCDEFG", 7, 0, (struct sockaddr *)&addr, sizeof(addr));
        printf("send %d\n", len);
        fromlen = sizeof(from);
        len = recvfrom(s, buf, sizeof(buf), 0, (struct sockaddr *)&from, &fromlen);
        for (i = 0 ; i < len ; i++) {
            printf("%02x", buf[i]);
        }
        close(s);
    }
}
UDPサーバ
UDPサーバは、socket()からサーバ開始まで、次のように関数を呼び出します。
socket() ソケットの取得
bind() ソケットにIPアドレスとポート番号を割り当てる
sendto()・recvfrom() 送信・受信
close() ソケットを閉じる(サーバ終了)
bind()はTCPサーバと同様です。TCPサーバの場合はbind()に続いてlisten()accept()を呼び出しクライアントの接続を待ちますが、UDPサーバではそのままrecvfrom()sendto()で送受信を開始します。UDPは、サーバ・クライアントともにあまり違わず、異なるのは、サーバがbind()によりソケットにポート番号を固定していることだけです。UDP通信は、サーバ側のポートに向けてクライアントが最初に送信するところから始まります。
次に、UDPサーバの例を示します。
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
    int s;
    int fromlen, len;
    char buf[10];
    struct sockaddr_in addr, from;

    if ((s = socket(AF_INET, SOCK_DGRAM, 0)) > 0) {
        memset(&addr, 0, sizeof(addr));
        addr.sin_family = AF_INET;
        addr.sin_addr.s_addr = htonl(INADDR_ANY);
        addr.sin_port = htons(8000);
        if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) == 0) {
            fromlen = sizeof(from);
            len = recvfrom(s, buf, sizeof(buf), 0, (struct sockaddr *)&from, &fromlen);
            printf("recv from %s\n", inet_ntoa(from.sin_addr));
            printf("%s\n", buf);
            len = sendto(s, "ABCDEFG", 8, 0, (struct sockaddr *)&from, fromlen);
            printf("send %d\n", len);
        }
        close(s);
    }
}

IPv4・IPv6に依存しない実装

IPv6の場合、socket()のプロトコルファミリにPF_INET6AF_INET6)を指定します。そして、sockaddr構造体にキャストして指定する通信相手のアドレス情報は、IPv6用のsockaddr_in6に格納します。IPv6のTPC/UDPクライアント・サーバプログラムは、IPv4のsockaddr_inに設定する方法のようにsockaddr_in6に各情報を設定することになります。そのとき、IPv4アドレス値に変換するinet_addr()を、IPv6に対応するinet_pton()に置き換えるような方法があります。しかし、inet_pton()はリンクローカルアドレスのスコープが含まれないという不都合があります。
このようなIPv4に依存する関数をIPv6用に置き換える形での実装は難しいものがあります。また、現実のほとんどネットワークはIPv4とIPv6が混在しているので、IPv6に特化しない柔軟な実装にすることが要求されます。そこで、IPv4とIPv6のどちらにも対応してアドレス情報を作成するgetaddrinfo()を利用します。
getaddrinfo()は、条件を与えると、それに該当するsockaddr_insockaddr_in6構造体の情報の「リスト」を作成します。たとえばドメイン名を与えると、/etc/hostsを参照したりDNSを動かしたりいろいろ手段を使って、具体的なIPアドレスの情報を作成して返します。このとき、ドメイン名から得られるアドレス情報が複数ある場合は、それらをリストの形式で返します。あるいはプロトコルファミリやIPアドレスを具体的に指定して、sockaddr_insockaddr_in6の構造体に変換させる使い方もできます。上記のIPv4の例のようなsockaddr_in構造体のメンバに個別に代入しなくても、getaddrinfo()に必要な情報を与えることで、適切なアドレス情報の構造体を作成してくれます。
int getaddrinfo(const char *node, const char *service,
    const struct addrinfo *hints, struct addrinfo **res)
node IPv4・Ipv6アドレスの文字列表記/ドメイン名
サーバでINADDR_ANYにしたい場合はNULL
service サービス名(ftpなどの文字列)/ポート番号(数値の文字列)
hints 取得する条件を指定する
指定しない場合はNULL
res 作成したアドレス情報のリスト
dest 送信先の情報
addrlen destのサイズ
最小限に、サーバの場合はservice、クライアントの場合はnodeserviceを指定しgetaddrinfo()を呼び出せば、それに応じたアドレス情報が作成されます。ドメイン名を指定した場合は名前解決処理が自動的に行われます。
アドレス情報は、addrinfo構造体のリストとして引数resに返ります。addrinfo構造体は次のように定義されています。
struct addrinfo {
    int                 ai_flags;       // AI_PASSIVE, AI_NUMERICHOST..
    int                 ai_family;      // プロトコルファミリ PF_INET/PF_INET6
    int                 ai_socktype;    // ソケットタイプ SOCK_STREAM/SOCK_DGRAM
    int                 ai_protocol;    // プロトコル   0(=自動)
    size_t              ai_addrlen;     // ai_addrのサイズ
    char                *ai_canonname;  // canonical name for nodename
    struct sockaddr     *ai_addr;       // アドレス情報
    struct addrinfo     *ai_next;       // 次のアドレス情報へのポインタ
};
この構造体はconnect()bind()に指定するアドレスファミリやソケットタイプ、sockaddr構造体のアドレス情報が含まれています。resが参照するaddrinfo構造体のメンバを、そのままconnect()bind()の引数に与えることができます。
引数hintsは、addrinfo構造体となっており、このメンバに設定した条件に一致するものだけをresに返します。例えば、hintsai_familyにPF_INETを設定し、ai_socktypeSOCK_STREAMを設定してgetaddinfo()を呼び出すと、resにはIPv4のストリーム型のアドレス情報が返ります。hintsの指定が不要な場合はNULLを与えますが、大抵はプロトコルファミリとソケットタイプは指定します。
注意しなければならないのは、getaddrinfo()resに返すリストは動的に確保されているので、freeaddrinfo()を呼び出して開放する必要があります。
freeaddrinfo(struct addrinfo *res);
getaddrinfo()とは逆に、sockaddr構造体からホスト名とサービス名を取得するgetnameinfo()があります。サーバのaccept()の接続したクライアントを調べるときなどに利用できます。
int getnameinfo(const struct sockaddr *addr, socklen_t addrlen,
    char *host, socklen_t hostlen, char *serv, socklen_t servlen, int flags);
addr アドレス情報
addrlen アドレス情報のサイズ
host ホスト名のバッファ
hostlen ホスト名のバッファサイズ
serv サービス名のバッファ
servlen サービス名のバッファサイズ
flags ホストやサービスを数値形式で得る場合は、
NI_NUMERICHOST | NI_NUMERICSERV
を指定する。FQDNで取得したい場合は0。
次は、最初のTCPクライアントの例をもとに、getaddrinfo()を使うように変更した例です。
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[])
{
    int s;
    char buf[10];
    struct addrinfo hints;
    struct addrinfo *res = NULL;

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    if (getaddrinfo(argv[1], "8000", &hints, &res) != 0) {
        exit(0);
    }
    if ((s = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) > 0) {
        if (connect(s, res->ai_addr, res->ai_addrlen) == 0) {
            write(s, "12345", 6);
            read(s, buf, sizeof(buf));
            printf("%s\n", buf);
            close(s);
        }
    }
    freeaddrinfo(res);
}
この例はコマンドライン引数に任意のサーバアドレス(ドメインでも可)を指定します。それをgetaddrinfo()nodeに与えています。resは複数の候補をリストとして返しますが、この例ではリスト先頭のものを無条件で使っています(これは説明の簡略化のための横着なので、正しい方法は後述します)。
この例からわかる通り、socket()connect()の引数には、getaddrinfo()から得たresのメンバをそのまま与えています。
次は、TCPサーバの例です。
nodeにNULLを指定すると、INADDR_ANYの指定と同じになります。また、サーバの場合はhintsai_flagsAI_PASSIVEを指定します。
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[])
{
    int s1, s2;
    int fromlen;
    char buf[10];
    struct addrinfo hints;
    struct addrinfo *res;
    struct sockaddr_storage from;
    char host[NI_MAXHOST];

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;
    if (getaddrinfo(NULL, "8000", &hints, &res) != 0) {
        exit(0);
    }
    if ((s1 = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) > 0) {
        if (bind(s1, res->ai_addr, res->ai_addrlen) == 0) {
            listen(s1, 5);
            fromlen = sizeof(from);
            if ((s2 = accept(s1, (struct sockaddr *)&from, &fromlen)) > 0) {
                getnameinfo((struct sockaddr *)&from, fromlen, host, sizeof(host),
                    NULL, 0, NI_NUMERICHOST);
                printf("accept from %s\n", host);
                close(s1);
                read(s2, buf, sizeof(buf));
                printf("%s\n", buf);
                write(s2, "ABCDEFG", 8);
            }
        }
        close(s2);
    }
    freeaddrinfo(res);
}
accept()の引数fromには、接続したクライアントの情報が入りますが、この例では、sockaddr_storageという構造体変数で受けています。sockaddr_storage構造体とは、sockaddr_insockaddr_in6sockaddr_unの何れのアドレスファミリ用構造体でも受け入れるに十分大きいサイズで定義されています。つまり、同じ実装でIPv4とIPv6のどちらでも対応できるようになります。sockaddr_storage構造体の内容は、getnameinfo()で適切にパースできます。
getaddrinfo()から得られるaddrinfo構造体はリストになっています。上の例はリスト先頭を無条件で参照していますが、本来はリストを走査して確認すべきです。次は、上のTCPクライアントの例を、リスト走査するように修正した例です。
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[])
{
    int s;
    char buf[10];
    struct addrinfo hints;
    struct addrinfo *res = NULL;
    struct addrinfo *ai;

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    if (getaddrinfo(argv[1], "8000", &hints, &res) != 0) {
        exit(0);
    }
    for (ai = res; ai != NULL; ai = ai->ai_next) {
        if ((s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) > 0) {
            if (connect(s, ai->ai_addr, ai->ai_addrlen) == 0) {
                break;
            }
            close(s);
        }
    }
    freeaddrinfo(res);
    if (ai) { 
        write(s, "12345", 6);
        read(s, buf, sizeof(buf));
        printf("%s\n", buf);
        close(s);
    }
}
getaddrinfo()は、IPv4とIPv6の両方に対応しています。hintsに指定するai_familyを、AF_INET6にすることでIPv6アドレスでのサーバになります。
hints.ai_family = AF_INET6;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
if (getaddrinfo(NULL, "8000", &hints, &res) != 0) {
またクライアントでは、次のようにai_familyAF_UNSPECを設定するとIPv4・IPv6の両方に対応できます。コマンドラインに、IPv4アドレスとIPv6アドレスのどちらを指定しても動作します。
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
if (getaddrinfo(argv[1], "8000", &hints, &res) != 0) {
    exit(0);

selectによるソケット監視

accept()はクライアントの接続が起こるまでブロックし続けます。またread()は何かが受信されるまでブロックするので、接続や受信がなければ永久に関数から戻りません。ブロックしている間は他のクライアントを同時に処理することはできません。たとえば、接続したクライアントからの受信を待っている間に、他のクライアントの接続は受け付けられません。これはselect()を使うことで可能になります。
select()は、指定する1つ以上のソケットにイベントが発生するまで待機し、監視しているソケットの何れかで接続や受信が発生すると関数が返ります。また、タイムアウトを設定すると、指定時間内になにもアクションがない場合は関数が返ります。すなわち、accept()read()を呼び出す前に、select()が複数のソケットを代表してイベントを見張ります。それにより、accept()read()の中でブロックすることなく、平行して処理できるようになります。
select(int nfds, fd_set *rfds, fd_set *wfds, fd_set *exceptfds, struct timeval *to)
nfds 監視するソケットの中の最大値に+1した値
rfds 受信発生を監視するソケットを指定するフラグ変数
wfds 送信発生を監視するソケットを指定するフラグ変数
exceptfds エラー発生を監視するソケットを指定するフラグ変数
to タイムアウト時間
fd_set構造体に、イベント監視するソケットディスクリプタを設定します。fd_set構造体は次のように定義されています。
typedef struct fd_set {
    __int32_t fds_bits[32];
}
32ビット整数が32で1024ビットであり、0~1023番のディスクリプタをビットフラグで保持できます。fd_set構造体の変数を介して、監視するソケットの指定と、イベントが発生したソケットの通知が行われます。この構造体のビットフラグをセット・参照するマクロ関数が用意されています。
FD_ZERO(fd_set *fds) 全てのフラグをリセットする。
FD_SET(socket, fd_set *fds) ソケットについてセットする。
FD_ISSET(socket, fd_set *fds) ソケットについてセットかどうか調べる。
FD_CLR(socket, fd_set *fds) ソケットについてリセットする。
select()は主に接続と受信を監視する場合に使われます。送信とエラー発生を監視しない場合はNULLを指定します。またタイムアウトを設定しない場合はNULLを指定します。
FD_ZERO()fd_set構造体の変数を全てリセットし、FD_SET()に監視するソケットを指定すると、fd_set構造体の変数fdsには、そのソケットに対応したビットが1にセットされます。監視したいソケットが複数存在する場合は、それらのソケットについてFD_SET()を行います。fdsには、accept()の待受ソケットとその接続後に返るソケットのどちらも指定できます。つまり、クライアントの接続を待ちながら接続済みクライアントからの受信を待つことができます。
fd_set rfds;

FD_ZERO(&rfds);
FD_SET(s1, &rfds);
FD_SET(s2, &rfds);
select()の先頭の引数nfdsに与える値は、FD_SET()したソケットの中で、一番大きい値に+1した値を指定します。もし、select()で監視するソケットが1つだけならば、そのソケットディスクリプタの数値に+1した値を指定します。特にサーバで、複数のクライアントのソケットを監視する場合は、その中の最大値を求めなければなりません。
if (select(maxs + 1, &rfds, NULL, NULL, NULL) > 0)
タイムアウトはtimeval構造体で指定します。秒とマイクロ秒で指定できます。
struct timeval to;

to.tv_sec  = 3;
to.tv_usec = 0;
select()は、指定されたソケットの何れか(あるいは同時に)イベントが発生すると戻ります。戻り値はイベントが発生したソケットの数です。タイムアウトを指定して戻り値が0の場合は、タイムアウトしたことを示します。そして、引数として与えたfd_set構造体変数には、イベントが発生したソケットの位置のビットがセットされて戻されます(逆にイベントが発生しなかったソケットはリセットされます)。セットされているかどうかをFD_ISSET()で確認できます。
if (FD_ISSET(s, &rfds))
次は、TCPサーバでselect()により複数のクライアントを同時接続できるようにした例です。接続待のソケットs1と、接続済みソケットを両方監視します。新たなクライアントの接続を受け付けながら、接続済みのクライアントからの受信に応答します。select()に指定するためのfd_set構造体の変数rfdsの他に、接続済みソケットを記憶する目的でfdsという変数を設けています。これは、ソケットディスクリプタの配列で管理するなど、もっと他の方法があると思います。
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[])
{
    int s1, s2, s, maxs;
    int fromlen;
    char buf[10];
    struct addrinfo hints;
    struct addrinfo *res;
    struct sockaddr_storage from;
    char host[NI_MAXHOST];
    fd_set fds, rfds;

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;
    if (getaddrinfo(NULL, "8000", &hints, &res) != 0) {
        exit(0);
    }
    if ((s1 = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) > 0) {
        if (bind(s1, res->ai_addr, res->ai_addrlen) == 0) {
            listen(s1, 5);
            maxs = s1;
            FD_ZERO(&fds);
            FD_SET(s1, &fds);
            while (1) {
                rfds = fds;
                if (select(maxs + 1, &rfds, NULL, NULL, NULL) > 0) {
                    for (s = 0; s <= maxs ; s++) {
                        if (FD_ISSET(s, &rfds)) {
                            if (s == s1) {
                                fromlen = sizeof(from);
                                s2 = accept(s1, (struct sockaddr *)&from, &fromlen);
                                if (s2 > 0) {
                                    getnameinfo((struct sockaddr *)&from, fromlen,
                                        host, sizeof(host), NULL, 0, NI_NUMERICHOST);
                                    printf("accept %s\n", host);
                                    FD_SET(s2, &fds);
                                    maxs = (s2 > maxs)? s2 : maxs;
                                }
                            } else {
                                if (read(s, buf, sizeof(buf)) > 0) {
                                    printf("%s\n", buf);
                                    write(s, "ABCDEFG", 8);
                                } else {
                                    FD_CLR(s, &fds);
                                    close(s);
                                }
                            }
                        }
                    }
                    for (; !FD_ISSET(maxs, &fds); maxs--);
                }
            }
        }
    }
    freeaddrinfo(res);
}
次は、select()のタイムアウトを利用して、サーバからの受信待ちが3秒でタイムアウトするTCPクライアントの例です。
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>

int main(int argc, char *argv[])
{
    int s;
    char buf[10];
    struct addrinfo hints;
    struct addrinfo *res = NULL;
    struct addrinfo *ai;
    fd_set rfds;
    struct timeval to;
    int rs;

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    if (getaddrinfo(argv[1], "8000", &hints, &res) != 0) {
        exit(0);
    }
    for (ai = res; ai != NULL; ai = ai->ai_next) {
        if ((s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) > 0) {
            printf("sock\n");
            if (connect(s, ai->ai_addr, ai->ai_addrlen) == 0) {
                break;
            }
            close(s);
            printf("nothing\n");
        }
    }
    freeaddrinfo(res);
    if (ai) { 
        write(s, "12345", 6);
        while(1) {
            FD_ZERO(&rfds);
            FD_SET(s, &rfds);
            to.tv_sec  = 3;
            to.tv_usec = 0;
            rs = select(s + 1, &rfds, NULL, NULL, &to);
            if (rs > 0) {
                if (FD_ISSET(s, &rfds)) {
                    if (read(s, buf, sizeof(buf)) > 0) {
                        printf("%s\n", buf);
                    } else {
                        break;  // connection closed
                    }
                }
            } else if (rs == 0)
                printf("recv timeout\n");
            printf("rs %d\n", rs);
        }
        close(s);
    }
}

ソケットオプション

ソケットは、通常はデフォルト設定で適切に動作しますが、setsockopt()によりそれを任意に調整できます。setsockopt()は、socket()accept()で返るソケットに対し設定を行います。
setsockopt(int socket, int level, int opt, void *val, unsigned int len)
socket ソケット
level SOL_SOCKET(全プロトコルに対し)
IPPROTO_IP(ネットワーク層に対し)
IPPROTO_TCP(トランスポート層に対し)
opt オプション
val オプション値
len オプションのサイズ
また、現在の設定を参照するgetsockopt()があります。
getsockopt(int socket, int level, int opt, void *val, unsigned int *len)
多くのソケットオプションが用意されています。ネットワーク層、トランスポート層への詳細な調整項目がありますが、よく使われるのは、socket()が返すソケットに対する、levelにSOL_SOCKETを指定する全体設定です。その中でも次のオプションが定番としてよく使われます。
SO_REUSEADDR valに0か1
サーバが切断した直後に、経路上に残る未到達のパケットが廃棄されるまで待つために、そのポートは一定の時間使用禁止になります(この時間はOSに依存)。そのため、通常はサーバプログラムを閉じてすぐに起動すると必ずbind()で失敗します。この場合は、しばらくして(Linuxでは1分)再び立ち上げると成功します。このオプションを1(有効)にすると、使用許可を待たずに再利用できるようになります。このオプションはサーバでよく使われます。
SO_RCVBUF 受信バッファサイズを指定します。
SO_SNDBUF 送信バッファサイズを指定します。
次の例は、SO_REUSEADDRを設定したサーバの例です。
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[])
{
    int s1, s2;
    int fromlen;
    char buf[10];
    struct addrinfo hints;
    struct addrinfo *res;
    struct sockaddr_storage from;
    char host[NI_MAXHOST];
    int sopt;

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;
    if (getaddrinfo(NULL, "8000", &hints, &res) != 0) {
        exit(0);
    }
    if ((s1 = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) > 0) {
        sopt = 1;
        setsockopt(s1, SOL_SOCKET, SO_REUSEADDR, &sopt, sizeof(sopt)); 
        if (bind(s1, res->ai_addr, res->ai_addrlen) == 0) {
            listen(s1, 5);
            fromlen = sizeof(from);
            if ((s2 = accept(s1, (struct sockaddr *)&from, &fromlen)) > 0) {
                getnameinfo((struct sockaddr *)&from, fromlen, host, sizeof(host),
                    NULL, 0, NI_NUMERICHOST);
                printf("accept from %s\n", host);
                close(s1);
                read(s2, buf, sizeof(buf));
                printf("%s\n", buf);
                write(s2, "ABCDEFG", 8);
            }
        }
        close(s2);
    }
    freeaddrinfo(res);
}

UNIXドメインソケット

UNIXドメインソケットは、ネットワークのホスト間のようにUNIX(Linux)のプロセス間でデータ送受信を行います。プロセスはIPアドレスを持ちません。そのかわりプロセス間通信のソケットは、ファイルパスをアドレスのかわりの識別名にします。
UNIXドメインソケットのアドレス情報は、sockaddr_un構造体へ設定します。sun_pathに、この通信を識別する固有のパス名を設定します。クライアントは、接続するサーバ側のパス名を指定します。また、このパス名は実在するファイルのパスではありません。ソケットが開いている間、特殊なファイルとしてファイルシステムに出現します。また、パス名のファイルは、終了後にunlink()でリンクを解除する必要があります。
struct  sockaddr_un {
    __uint8_t       sun_len;        // 構造体のサイズ
    sa_family_t     sun_family;     // アドレスファミリ
    char            sun_path[104];  // パス名
};
socket()のプロトコルファミリにはPF_UNIXAF_UNIX)を指定します。UNIXドメインでのデータグラム型の場合は、クライアントもbind()しなければならないという違いがあります。以上のこと以外は、IPネットワークのソケットプログラムと変わりません。
次の例は、プロセス間通信のサーバ側です。
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
    int s1, s2;
    int fromlen, len, i;
    char buf[10];
    struct sockaddr_un addr;

    if ((s1 = socket(AF_UNIX, SOCK_STREAM, 0)) > 0) {
        memset(&addr, 0, sizeof(addr));
        addr.sun_family = AF_UNIX;
        strcpy(addr.sun_path, "/tmp/sockunix");
        if (bind(s1, (struct sockaddr *)&addr, sizeof(addr)) == 0) {
            listen(s1, 5);
            if ((s2 = accept(s1, NULL, NULL)) > 0) {
                close(s1);
                read(s2, buf, sizeof(buf));
                printf("%s\n", buf);
                write(s2, "ABCDEFG", 8);
            }
        }
        close(s2);
        unlink("/tmp/sockunix");
    }
}
次の例は、プロセス間通信のクライアント側です。
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
    int s;
    struct sockaddr_un addr;
    char buf[10];

    if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) > 0) {
        memset(&addr, 0, sizeof(addr));
        addr.sun_family = AF_UNIX;
        strcpy(addr.sun_path, "/tmp/sockunix");
        if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) == 0) {
            write(s, "12345", 6);
            read(s, buf, sizeof(buf));
            printf("%s\n", buf);
        }
        close(s);
    }
}