ソケット(英: socket)とは、BSD系UNIXを起源とするAPIであり、C言語によるアプリケーション開発でのプロセス間通信、特にコンピュータネットワークに関するライブラリを構成する。その起源を強調してBSDソケット、バークレーソケットなどとも呼ばれる。
ソケットはネットワーク内の通信エンドポイント(communication endpoint、通信端点[1][2])を識別して接続するための概念およびメカニズムである[3][4][5]。ソケットとエンドポイントは関連の強い概念であるため、同一視されることもある[6][7]が、一般的に「ソケット」と言えば抽象化された通信の概念やAPIを包含するものであり、厳密には別物である。
1983年にリリースされたUNIXオペレーティングシステム (OS) 4.2BSD で初めて API として実装された。ネットワークの抽象化インタフェースとしてのデファクトスタンダードとなっている。伝統的なSocket APIはC言語を対象とするが、他のプログラミング言語でも類似のインタフェースを用意していることが多い[注釈 1]。
ソケットの代替となるAPIとして、STREAMSベースの Transport Layer Interface (TLI) がある。しかし、BSDソケットは比較にならないほど普及しており、数多くの実装が存在する。
BSDソケットは、ホスト間の通信や1つのコンピュータ上のプロセス間の通信を可能とする。通信媒体としては様々な入出力機器やデバイスドライバを利用可能だが、その部分はオペレーティングシステムの実装に依存する。このインタフェース実装はTCP/IPを利用する際にはほとんどの場合で必要とされ、インターネットを支える基盤技術の一つとなっている。当初、カリフォルニア大学バークレー校でUNIX向けに開発された。最近の全てのオペレーティングシステムには間違いなくBSDソケットが何らかの形で実装されており、インターネットへの接続の標準インタフェースとなっている。
プログラマの観点から見ると、ソケットインタフェースは3つのレベルでアクセス可能である。最も強力で基本的なレベルは RAW(生)ソケットレベルである。RAWソケットが可能とする通信制御の自由度を必要とするアプリケーションは稀であり、インターネット関連技術の開発でのみ使われるべきとされている。
socketインタフェースは様々なネットワークプロトコルを抽象化している。これらのプロトコル群はprotocol familyとsocket typeに基づいてグループ分けされる[9]。Linuxカーネルでは20を超えるprotocol familyが設定されており、例えばIPv4を指定するAF_INET
やUNIXドメインソケットを指定するAF_UNIX
がある。全てのprotocol familyは<sys/socket.h>
で定義されている[10]。socket typeの例にはSOCK_STREAM
やSOCK_DGRAM
がある。familyとsocketの組み合わせでトランスポート層プロトコルに相当することが多い。
row:family/column:type | SOCK_STREAM
|
SOCK_DGRAM
|
---|---|---|
AF_INET /AF_INET6
|
TCP | UDP |
AF_UNIX
|
UNIXドメインソケット | UNIXドメインソケット |
BSDソケットにはいくつかのヘッダファイルがある。
<sys/socket.h>
<netinet/in.h>
<sys/un.h>
<arpa/inet.h>
<netdb.h>
TCP はコネクションの概念を提供する。TCPソケットを生成するには socket()
関数で AF_INET
または AF_INET6
と SOCK_STREAM
を引数に指定する。
単純なTCPサーバの設定は、以下のように行われる。
socket()
呼び出し)bind()
呼び出し)。bind()
を呼び出す前に、sockaddr_in
構造体を宣言し、その中身をクリアし(bzero()
または memset()
)、その sin_family
フィールドを AF_INET
か AF_INET6
に設定し、sin_port
フィールドに listen 対象ポート番号をネットワークバイトオーダで設定する。short int
をネットワークバイトオーダに変換するには、htons()
関数を使う。listen()
を呼び出す。accept()
呼び出しで受け付ける。これはコネクションが受信されるまでブロックし、受信したコネクションのソケット記述子を返す。最初の記述子は listen 用記述子のままであり、accept()
はそのソケットをクローズするまで何度でも呼び出せる。send()
とrecv()
、またはwrite()
とread()
を使用する。close()
を使ってオープンされた各ソケットをクローズする。なお、fork()
が行われた場合、各プロセスは見えているソケットを全てクローズしなければならず、複数のプロセスが同じソケットを同時に使ってはならない。C99 でのサーバー側のソースコード。
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(void)
{
// サーバーソケット作成
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == -1)
{
perror("socket");
return 1;
}
// struct sockaddr_in 作成
struct sockaddr_in sa = {0};
sa.sin_family = AF_INET;
sa.sin_port = htons(1100);
sa.sin_addr.s_addr = htonl(INADDR_ANY);
// バインド
if (bind(sock, (struct sockaddr*) &sa, sizeof(struct sockaddr_in)) == -1)
{
perror("bind");
goto bail;
}
// リッスン
if (listen(sock, 128) == -1)
{
perror("listen");
goto bail;
}
while (1)
{
// クライアントの接続を待つ
int fd = accept(sock, NULL, NULL);
if (fd == -1)
{
perror("accept");
goto bail;
}
// 受信
char buffer[4096];
int recv_size = read(fd, buffer, sizeof(buffer) - 1);
if (recv_size == -1)
{
perror("read");
close(fd);
goto bail;
}
// 受信内容を表示
buffer[recv_size] = '\0';
printf("message: %s\n", buffer);
// ソケットのクローズ
if (close(fd) == -1)
{
perror("close");
goto bail;
}
}
bail:
// エラーが発生した場合の処理
close(sock);
return 1;
}
TCPクライアントの設定は、以下のように行われる。
socket()
呼び出し)connect()
でサーバに接続する。このとき、sockaddr_in
構造体の sin_family
フィールドは AF_INET
か AF_INET6
とし、sin_port
には通信相手が listen しているはずのポート番号をネットワークバイトオーダで設定し、sin_addr
にもネットワークバイトオーダで相手の(IPv4 か IPv6 の)アドレスを設定する。send()
とrecv()
、またはwrite()
とread()
を使用する。close()
で行う。なお、fork()
が行われた場合、各プロセスは見えているソケットを全てクローズしなければならず、複数のプロセスが同じソケットを同時に使ってはならない。C99 でのクライアント側のソースコード。
#define _BSD_SOURCE
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define MESSAGE "Hello World!"
int main(void)
{
// ソケット作成
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == -1)
{
perror("socket");
return 1;
}
// struct sockaddr_in 作成
struct sockaddr_in sa = {0};
sa.sin_family = AF_INET;
sa.sin_port = htons(1100);
// localhost の IP アドレスを引く
struct hostent *hostent = gethostbyname("localhost");
if (hostent == NULL)
{
herror("gethostbyname");
goto bail;
}
memcpy(&sa.sin_addr, hostent->h_addr_list[0], sizeof(sa.sin_addr));
// 接続
if (connect(sock, (struct sockaddr*) &sa, sizeof(struct sockaddr_in)) == -1)
{
perror("connect");
goto bail;
}
// 送信
if (write(sock, MESSAGE, strlen(MESSAGE)) == -1)
{
perror("write");
goto bail;
}
// クローズ
close(sock);
return 0;
bail:
// エラーが発生した場合の処理
close(sock);
return 1;
}
上記のソースコードは gcc の場合 -std=c99
をつけることによりコンパイル可能であるが、-std=gnu99
を使用した場合は最初から定義されているので #define _BSD_SOURCE
はあっても無くてもどちらでも良い。#define _BSD_SOURCE
が無いと herror()
が使えない。
UDP はコネクションレスのプロトコルであり、パケットが必ず相手に届くことは保証されない。UDPパケットは順序通りに届くとは限らず、1つのパケットが複数個届くこともありうるし、全く届かないこともある。このようにほとんど何も保証されないため、UDP は TCP に比べてオーバヘッドが小さい。コネクションレスであるとは、2つのホスト間にコネクションやストリームといったものがなく、データが単に個々のパケット(datagram)として届けられることを意味している。
UDP のアドレス空間、すなわちUDPポート番号は、TCPポートとは別物である。
C99 でのサーバ側のソースコード。ポート番号 7654 で UDP サーバを開く。
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
int main(void)
{
// ソケット作成
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock == -1)
{
perror("socket");
return 1;
}
// struct sockaddr_in 作成
struct sockaddr_in sa = {0};
sa.sin_family = AF_INET;
sa.sin_port = htons(7654);
sa.sin_addr.s_addr = htonl(INADDR_ANY);
// バインド
if (bind(sock, (struct sockaddr *) &sa, sizeof(struct sockaddr_in)) == -1)
{
perror("bind");
goto bail;
}
while (1)
{
// 受信
char buffer[4096];
socklen_t addrlen = sizeof(struct sockaddr_in);
ssize_t recv_size = recvfrom(sock, (void *) buffer, sizeof(buffer) - 1, 0, (struct sockaddr *) &sa, &addrlen);
if (recv_size == -1)
{
perror("recvfrom");
goto bail;
}
// 受信内容表示
buffer[recv_size] = '\0';
printf("datagram: %s\n", buffer);
}
bail:
// エラーが発生した場合の処理
close(sock);
return 1;
}
bind()
は、ソケットとアドレス/ポートを結びつける。
最後の無限ループは recvfrom()
を使ってポート番号 7654 からのUDPパケットを受信する。引数は以下の通り。
C99 でのクライアント側のソースコード。ポート 7654、アドレス 127.0.0.1 に "Hello World!" という内容の UDP パケットを送る。
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define MESSAGE "Hello World!"
int main(void)
{
// ソケット作成
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock == -1)
{
perror("socket");
return 1;
}
// struct sockaddr_in 作成
struct sockaddr_in sa = {0};
sa.sin_family = AF_INET;
sa.sin_port = htons(7654);
sa.sin_addr.s_addr = inet_addr("127.0.0.1");
// 送信
int bytes_sent = sendto(sock, MESSAGE, strlen(MESSAGE), 0, (struct sockaddr*) &sa, sizeof(struct sockaddr_in));
if (bytes_sent == -1)
{
perror("sendto");
goto bail;
}
// クローズ
close(sock);
return 0;
bail:
// エラーが発生した場合の処理
close(sock);
return 1;
}
IP の場合は IP アドレス+ポート番号で接続先を指定するのに対して、UNIXドメインソケットの場合はファイルパスで指定する。
socket()
は通信の一方の終端を作成し、それを表すファイル記述子を返す。socket()
には以下の3つの引数がある。
domain
- 生成するソケットのプロトコルファミリを指定する。
AF_INET
- IPv4AF_INET6
- IPv6AF_UNIX
- UNIXドメインソケットtype
- ソケットのタイプ指定。
SOCK_STREAM
- 信頼性のあるストリーム指向サービス(TCPに対応)SOCK_DGRAM
- データグラムサービス(UDPに対応)SOCK_SEQPACKET
- SOCK_STREAM
とほぼ同じだが、受信したパケットを読み出す際にパケット全体を読み出さないと、残りを破棄する。SOCK_RAW
- IPレベルのプロトコル処理をユーザー側で用意するときに使用protocol
- 通常 0 を指定し、デフォルトの物が選ばれる。トランスポート層のプロトコルは、domain
と type
で指定されるが(TCPの場合、AF_INET
または AF_INET6
と SOCK_STREAM
、UDPの場合、同様の AF_
値と SOCK_DGRAM
)、明示的に指定することも可能で、その値は <netinet/in.h> に定義されている。エラーが発生すると -1 を返す。そうでない場合、新たに確保された記述子を表す整数を返す。
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
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(const char *name);
struct hostent *gethostbyaddr(const void *addr, int len, int type);
connect()
はコネクションの確立に成功すると 0 を返し、エラーが発生すると -1 を返す。
コネクションレスのソケットの場合(User Datagram Protocol)、connect()
は送受信の相手を指定するのに使われ、send()
と recv()
をコネクションレスのソケットで使えるようになる。
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
bind()
はソケットにアドレスを設定する。socket()
で生成された時点では、ソケットはアドレスファミリは指定されているが、アドレスは設定されていない。ソケットは、コネクションを受け付ける前にバインド(アドレス設定)される必要がある。以下の引数がある。
sockfd
- バインドすべきソケットの記述子addr
- バインドすべきアドレスを表す sockaddr
構造体へのポインタaddrlen
- sockaddr
構造体の大きさ成功すると 0 を返し、エラーが発生すると -1 を返す。
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
listen()
はバインドされたソケットでコネクション確立要求を待ち受ける。SOCK_STREAM
または SOCK_SEQPACKET
の場合のみ有効。以下の引数がある。
sockfd
- ソケット記述子backlog
- ある時点でペンディングにできる(キューイングできる)最大コネクション数。一般にOSが上限を設けている。コネクションは accept()
されるとデキューされる。成功すると 0 を返す。エラーが発生すると -1 を返す。
#include <sys/socket.h>
int listen(int sockfd, int backlog);
accept()
は、リモートホストからのコネクション確立要求を受け付ける。以下の引数がある。
sockfd
- listen していたソケットの記述子addr
- クライアントのアドレス情報を accept()
の中で格納するための sockaddr
構造体へのポインタaddrlen
- addr
が指す sockaddr
構造体の大きさを格納する socklen_t
へのポインタ。accept()
から戻ったとき、実際に格納されたデータ構造のサイズに更新されている。コネクションが確立された場合、それに対応したソケット記述子を返す。エラーが発生すると -1 を返す。
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
BSDソケットは、ブロッキングとノンブロッキングという2つのモードを持つ。ブロッキング・ソケットは、指定されたデータを全て送受信し終わるまで呼び出したライブラリ関数から戻ってこない。そのため、プログラムは決して届かないデータを待って動けなくなる可能性がある。これは、ソケットで listen を続けたい場合などに困る。
ブロッキングかノンブロッキングかを指定するには、fcntl()
か ioctl()
関数を使う。
socket()
で確保されたリソースは close()
を使うまで解放されない。これは、connect()
が成功するまで再試行を繰り返すような実装で注意が必要となる。socket()
を呼び出したら必ず対応する close()
を呼び出す必要がある。close を使うには <unistd.h> をインクルードする必要がある。
ソケットインタフェースの正当な標準定義は以下の POSIX 標準に含まれている。
この標準に関する情報と現在進行の作業については、the Austin website にある。
ソケットAPIのIPv6拡張は、RFC 3493 と RFC 3542 にある。
この記事は2008年11月1日以前にFree On-line Dictionary of Computingから取得した項目の資料を元に、GFDL バージョン1.3以降の「RELICENSING」(再ライセンス) 条件に基づいて組み込まれている。
java.net.Socket
クラスや、.NETのSystem.Net.Sockets.Socket
クラス[8]が挙げられる。
socket(2)
– JM Project Linux System Calls マニュアルconnect(2)
– JM Project Linux System Calls マニュアルbind(2)
– JM Project Linux System Calls マニュアルlisten(2)
– JM Project Linux System Calls マニュアルaccept(2)
– JM Project Linux System Calls マニュアルsocket(7)
– JM Project Linux Overview, Conventions and Miscellanea マニュアルtcp(7)
– JM Project Linux Overview, Conventions and Miscellanea マニュアルudp(7)
– JM Project Linux Overview, Conventions and Miscellanea マニュアル