ソケット (BSD)

ソケットとは、BSD系UNIXを起源とするAPIであり、C言語によるアプリケーション開発でのプロセス間通信、特にコンピュータネットワークに関するライブラリを構成する。その起源を強調してBSDソケット、バークレーソケットなどとも呼ばれる。

1983年にリリースされたUNIXオペレーティングシステム  4.2BSD で初めて API として実装された。ネットワークの抽象化インタフェースとしてのデファクトスタンダードとなっている。伝統的なSocket APIはC言語を対象とするが、他のプログラミング言語でも類似のインタフェースを用意している事が多い。

ソケットの代替となるAPIとして、STREAMSベースの Transport Layer Interface  がある。しかし、BSDソケットは比較にならないほど普及しており、数多くの実装が存在する。

1. BSDソケットインタフェース
-----------------

BSDソケットは、ホスト間の通信や1つのコンピュータ上のプロセス間の通信を可能とする。通信媒体としては様々な入出力機器やデバイスドライバを利用可能だが、その部分はオペレーティングシステムの実装に依存する。このインタフェース実装はTCP/IPを利用する際にはほとんどの場合で必要とされ、インターネットを支える基盤技術の一つとなっている。当初、カリフォルニア大学バークレー校でUNIX向けに開発された。最近の全てのオペレーティングシステムには間違いなくBSDソケットが何らかの形で実装されており、インターネットへの接続の標準インタフェースとなっている。

プログラマの観点から見ると、ソケットインタフェースは3つのレベルでアクセス可能である。最も強力で基本的なレベルは RAWソケットレベルである。RAWソケットが可能とする通信制御の自由度を必要とするアプリケーションは稀であり、インターネット関連技術の開発でのみ使われるべきとされている。

socketインタフェースは様々なネットワークプロトコルを抽象化している。これらのプロトコル群は//と//に基づいてグループ分けされる。Linuxカーネルでは20を超えるprotocol familyが設定されており、例えばIPv4を指定するAF_INETやUNIXドメインソケットを指定するAF_UNIXがある。全てのprotocol familyはで定義されている。socket typeの例にはSOCK_STREAMやSOCK_DGRAMがある。familyとsocketの組み合わせでトランスポート層プロトコルに相当することが多い。



2. ヘッダファイル
----------

BSDソケットにはいくつかのヘッダファイルがある。

BSDソケットの中核となる関数とデータ構造 AF_INET と AF_INET6 アドレスファミリ。インターネット上で広く使われ、IPアドレスとTCP/UDPのポート番号が含まれる。 AF_UNIX アドレスファミリ。同一コンピュータ上で動作するプログラム間のローカルな通信に使用。ネットワーク上では使われない。 数値としてIPアドレスを操作する機能の定義 プロトコル名とホスト名を数値アドレスに変換する機能の定義。ローカルなデータやDNSを検索する。

3. TCP
------

TCP はコネクションの概念を提供する。TCPソケットを生成するには socket 関数で AF_INET または AF_INET6 と SOCK_STREAM を引数に指定する。

3.1. サーバ
--------

単純なTCPサーバの設定は、以下のように行われる。

TCPソケットを生成
そのソケットを listenポートにバインド。bind を呼び出す前に、sockaddr_in 構造体を宣言し、その中身をクリアし、その sin_family フィールドを AF_INET か AF_INET6 に設定し、sin_port フィールドに listen 対象ポート番号をネットワークバイトオーダで設定する。short int をネットワークバイトオーダに変換するには、htons 関数を使う。
そのソケットをコネクションの listen に使用するため、listen を呼び出す。
入ってきたコネクションを accept 呼び出しで受け付ける。これはコネクションが受信されるまでブロックし、受信したコネクションのソケット記述子を返す。最初の記述子は listen 用記述子のままであり、accept はそのソケットをクローズするまで何度でも呼び出せる。
遠隔のホストと通信を行う。sendとrecv、またはwriteとreadを使用する。
不要になったら、close を使ってオープンされた各ソケットをクローズする。なお、fork が行われた場合、各プロセスは見えているソケットを全てクローズしなければならず、複数のプロセスが同じソケットを同時に使ってはならない。C99 でのサーバー側のソースコード。

include
include
include
include <sys/types.h>
include <sys/socket.h>
include <netinet/in.h>
include <arpa/inet.h>int main { // サーバーソケット作成 int sock = socket; if  { perror; return 1; }

// struct sockaddr_in 作成 struct sockaddr_in sa = {0}; sa.sin_family = AF_INET; sa.sin_port = htons; sa.sin_addr.s_addr = htonl;

// バインド if  &sa, sizeof) == -1) { perror; goto bail; }

// リッスン if  == -1) { perror; goto bail; }

while  { // クライアントの接続を待つ int fd = accept; if  { perror; goto bail; }

// 受信 char buffer[4096]; int recv_size = read - 1); if  { perror; close; goto bail; }

// 受信内容を表示 buffer[recv_size] = '\0'; printf;

// ソケットのクローズ if  == -1) { perror; goto bail; } }

bail: // エラーが発生した場合の処理 close; return 1; }<WtXmlEndTag />

3.2. クライアント
-----------

TCPクライアントの設定は、以下のように行われる。

TCPソケットを生成する
connect でサーバに接続する。このとき、sockaddr_in 構造体の sin_family フィールドは AF_INET か AF_INET6 とし、sin_port には通信相手が listen しているはずのポート番号をネットワークバイトオーダで設定し、sin_addr にもネットワークバイトオーダで相手のアドレスを設定する。
サーバと通信する。sendとrecv、またはwriteとreadを使用する。
コネクションの終了と削除処理を close で行う。なお、fork が行われた場合、各プロセスは見えているソケットを全てクローズしなければならず、複数のプロセスが同じソケットを同時に使ってはならない。C99 でのクライアント側のソースコード。

define _BSD_SOURCE
include
include
include
include
include <sys/types.h>
include <sys/socket.h>
include <netinet/in.h>
include <arpa/inet.h>
define MESSAGE "Hello World!"int main { // ソケット作成 int sock = socket; if  { perror; return 1; }

// struct sockaddr_in 作成 struct sockaddr_in sa = {0}; sa.sin_family = AF_INET; sa.sin_port = htons;

// localhost の IP アドレスを引く struct hostent *hostent = gethostbyname; if  { herror; goto bail; } memcpy);

// 接続 if  &sa, sizeof) == -1) { perror; goto bail; }

// 送信 if ) == -1) { perror; goto bail; }

// クローズ close; return 0;

bail: // エラーが発生した場合の処理 close; return 1; }<WtXmlEndTag /> 上記のソースコードは gcc の場合 -std=c99 をつけることによりコンパイル可能であるが、-std=gnu99 を使用した場合は最初から定義されているので #define _BSD_SOURCE はあっても無くてもどちらでも良い。#define _BSD_SOURCE が無いと herror が使えない。

4. UDP
------

UDP はコネクションレスのプロトコルであり、パケットが必ず相手に届くことは保証されない。UDPパケットは順序通りに届くとは限らず、1つのパケットが複数個届くこともありうるし、全く届かないこともある。このようにほとんど何も保証されないため、UDP は TCP に比べてオーバヘッドが小さい。コネクションレスであるとは、2つのホスト間にコネクションやストリームといったものがなく、データが単に個々のパケットとして届けられることを意味している。

UDP のアドレス空間、すなわちUDPポート番号は、TCPポートとは別物である。

4.1. サーバ
--------

C99 でのサーバ側のソースコード。ポート番号 7654 で UDP サーバを開く。

include
include
include
include <sys/socket.h>
include <sys/types.h>
include <netinet/in.h>int main { // ソケット作成 int sock = socket; if  { perror; return 1; }

// struct sockaddr_in 作成 struct sockaddr_in sa = {0}; sa.sin_family = AF_INET; sa.sin_port = htons; sa.sin_addr.s_addr = htonl;

// バインド if  &sa, sizeof) == -1) { perror; goto bail; }

while  { // 受信 char buffer[4096]; socklen_t addrlen = sizeof; ssize_t recv_size = recvfrom buffer, sizeof - 1, 0,  &sa, &addrlen); if  { perror; goto bail; }

// 受信内容表示 buffer[recv_size] = '\0'; printf; }

bail: // エラーが発生した場合の処理 close; return 1; }<WtXmlEndTag />

bind は、ソケットとアドレス/ポートを結びつける。

最後の無限ループは recvfrom を使ってポート番号 7654 からのUDPパケットを受信する。引数は以下の通り。

ソケット
バッファへのポインタ
バッファの大きさ
フラグ
アドレス構造体
アドレス構造体の大きさ

4.2. クライアント
-----------

C99 でのクライアント側のソースコード。ポート 7654、アドレス 127.0.0.1 に "Hello World!" という内容の UDP パケットを送る。

include
include
include
include <sys/socket.h>
include <sys/types.h>
include <netinet/in.h>
include <arpa/inet.h>
define MESSAGE "Hello World!"int main { // ソケット作成 int sock = socket; if  { perror; return 1; }

// struct sockaddr_in 作成 struct sockaddr_in sa = {0}; sa.sin_family = AF_INET; sa.sin_port = htons; sa.sin_addr.s_addr = inet_addr;

// 送信 int bytes_sent = sendto, 0,  &sa, sizeof); if  { perror; goto bail; }

// クローズ close; return 0;

bail: // エラーが発生した場合の処理 close; return 1; }<WtXmlEndTag />

5. UNIXドメインソケット
---------------

IP の場合は IP アドレス＋ポート番号で接続先を指定するのに対して、UNIXドメインソケットの場合はファイルパスで指定する。

6. 関数
-----

6.1. socket
-------------

socket は通信の一方の終端を作成し、それを表すファイル記述子を返す。socket には以下の3つの引数がある。

domain - 生成するソケットのプロトコルファミリを指定する。
AF_INET - IPv4
AF_INET6 - IPv6
AF_UNIX - UNIXドメインソケット
type - ソケットのタイプ指定。
SOCK_STREAM - 信頼性のあるストリーム指向サービス
SOCK_DGRAM - データグラムサービス
SOCK_SEQPACKET - SOCK_STREAMとほぼ同じだが、受信したパケットを読み出す際にパケット全体を読み出さないと、残りを破棄する。
SOCK_RAW - IPレベルのプロトコル処理をユーザー側で用意するときに使用
protocol - 通常 0 を指定し、デフォルトの物が選ばれる。トランスポート層のプロトコルは、domain と type で指定されるが、明示的に指定することも可能で、その値は に定義されている。エラーが発生すると -1 を返す。そうでない場合、新たに確保された記述子を表す整数を返す。

include <sys/types.h>
include <sys/socket.h>int socket;<WtXmlEndTag />

6.2. gethostbyname と gethostbyaddr
--------------------------------------

gethostbyname と gethostbyaddr は名前やアドレスで指定されたインターネット上のホストを表す struct hostent というデータ構造へのポインタを返す。その中には、ネームサーバから得られた情報、/etc/hosts ファイルから得られた情報などが格納されている。ローカルにネームサーバが動作していない場合、/etc/hosts ファイルを参照する。以下の引数がある。

name - ホスト名を指定。例えば "www.wikipedia.org"
addr - in_addr 構造体へのポインタ。中身としてホストのアドレスを指定。
len - addr の長さをバイト数で指定。
type - アドレスのドメイン型を指定。例えば、AF_INETエラーが発生するとNULLポインタを返し、h_errno を見れば詳しいエラーの原因がわかる。そうでない場合、有効な struct hostent * が返される。

struct hostent *gethostbyname; struct hostent *gethostbyaddr;

6.3. connect
--------------

connect はコネクションの確立に成功すると 0 を返し、エラーが発生すると -1 を返す。

コネクションレスのソケットの場合、connect は送受信の相手を指定するのに使われ、send と recv をコネクションレスのソケットで使えるようになる。

include <sys/types.h>
include <sys/socket.h>int connect;<WtXmlEndTag />

6.4. bind
-----------

bind はソケットにアドレスを設定する。socket で生成された時点では、ソケットはアドレスファミリは指定されているが、アドレスは設定されていない。ソケットは、コネクションを受け付ける前にバインドされる必要がある。以下の引数がある。

sockfd - バインドすべきソケットの記述子
addr - バインドすべきアドレスを表す sockaddr 構造体へのポインタ
addrlen - sockaddr 構造体の大きさ成功すると 0 を返し、エラーが発生すると -1 を返す。

include <sys/types.h>
include <sys/socket.h>int bind;<WtXmlEndTag />

6.5. listen
-------------

listen はバインドされたソケットでコネクション確立要求を待ち受ける。SOCK_STREAM または SOCK_SEQPACKET の場合のみ有効。以下の引数がある。

sockfd - ソケット記述子
backlog - ある時点でペンディングにできる最大コネクション数。一般にOSが上限を設けている。コネクションは accept されるとデキューされる。成功すると 0 を返す。エラーが発生すると -1 を返す。

include <sys/socket.h>int listen;<WtXmlEndTag />

6.6. accept
-------------

accept は、リモートホストからのコネクション確立要求を受け付ける。以下の引数がある。

sockfd - listen していたソケットの記述子
addr - クライアントのアドレス情報を accept の中で格納するための sockaddr 構造体へのポインタ
addrlen - addr が指す sockaddr 構造体の大きさを格納する socklen_t へのポインタ。accept から戻ったとき、実際に格納されたデータ構造のサイズに更新されている。コネクションが確立された場合、それに対応したソケット記述子を返す。エラーが発生すると -1 を返す。

include <sys/types.h>
include <sys/socket.h>int accept;<WtXmlEndTag />

7. ブロッキングとノンブロッキング
------------------

BSDソケットは、ブロッキングとノンブロッキングという2つのモードを持つ。ブロッキング・ソケットは指定されたデータを全て送受信し終わるまで呼び出したライブラリ関数から戻ってこない。これは、ソケットで listen を続けたい場合に困ったことになる。プログラムは決して届かないデータを待って動けなくなる可能性がある。

ブロッキングかノンブロッキングかを指定するには、fcntl か ioctl 関数を使う。

8. 注意点
------

socket で確保されたリソースは close を使うまで解放されない。これは、connect が成功するまで再試行を繰り返すような実装で注意が必要となる。socket を呼び出したら必ず対応する close を呼び出す必要がある。close を使うには をインクルードする必要がある。

9. 参考文献
-------

ソケットインタフェースの正当な標準定義は以下の POSIX 標準に含まれている。

IEEE Std. 1003.1-2001 Standard for Information Technology -- Portable Operating System Interface .
Open Group Technical Standard: Base Specifications, Issue 6, December 2001.
ISO/IEC 9945:2002この標準に関する情報と現在進行の作業については、[1] にある。

ソケットAPIのIPv6拡張は、RFC 3493 と RFC 3542 にある。

10. 脚註
------

10.1. 出典
--------

11. 関連項目
--------

コンピュータネットワーク
Transmission Control Protocol 
User Datagram Protocol 
Winsock - Microsoft Windows用のソケットAPI

12. 外部リンク
---------

[2]