Berkeley socketsは、プロセス間通信(IPC)に使用されるインターネットドメインソケットおよびUnixドメインソケット用のアプリケーションプログラミングインターフェース(API)です。一般的には、リンク可能なモジュールのライブラリとして実装されています。 1983年にリリースされたUnixオペレーティングシステム4.2BSDに端を発しています。
ソケットとは、ネットワーク通信経路のローカルエンドポイントを表す抽象的な表現(ハンドル)です。BerkeleyソケットAPIは、Unixの哲学に基づき、データストリームの入出力のための共通インターフェースを提供するファイルディスクリプタとしてソケットを表現します。
Berkeleyソケットは、事実上の標準からほとんど変更されることなくPOSIX仕様の構成要素へと進化しました。POSIXソケットという用語は基本的にBerkeleyソケットと同義ですが、 Berkeley Software Distributionで最初に実装されたことからBSDソケットとも呼ばれています。
バークレーソケットは、1983年にリリースされたBSD Unix 4.2オペレーティングシステムにプログラミングインターフェースとして登場しました。しかし、カリフォルニア大学バークレー校がAT&T社の独自Unixのライセンス制約から解放されたバージョンのオペレーティングシステムとネットワークライブラリをリリースできたのは、1989年になってからのことでした。
現代のすべてのオペレーティングシステムは、Berkeleyソケットインターフェースのバージョンを実装しています。これは、インターネット上で動作するアプリケーションの標準インターフェースとなりました。独立した開発者によって作成されたMS Windows用のWinsock実装でさえ、この標準に厳密に従っています。
BSDソケットAPIはC言語で記述されています。他のほとんどのプログラミング言語も同様のインターフェースを提供しており、通常はC APIに基づいたラッパーライブラリとして記述されています。 [ 1 ]
BerkeleyソケットAPIが進化し、最終的にPOSIXソケットAPI [ 2 ]が誕生するにつれて、一部の関数は非推奨になったり削除されたりして、他の関数に置き換えられました。POSIX APIは再入可能になるように設計されており、IPv6もサポートしています。
STREAMSベースのトランスポート層インターフェース(TLI)APIは、ソケットAPIの代替手段を提供します。TLI APIを提供するシステムの多くは、BerkeleyソケットAPIも提供しています。
非Unixシステムでは、多くの場合、BerkeleyソケットAPIをネイティブネットワークAPIへの変換レイヤーを介して公開します。Plan 9 [ 3 ]とGenode [ 4 ]は、ファイルディスクリプタではなく制御ファイルを使用したファイルシステムAPIを使用しています。
Berkeleyソケットインターフェースは、複数のヘッダーファイルで定義されています。これらのファイルの名前と内容は、実装によって若干異なります。一般的には、以下のファイルが含まれます。

BerkeleyソケットAPIは通常、以下の機能を提供します。
socket()整数値で識別される特定のタイプの新しいソケットを作成し、それにシステムリソースを割り当てます。bind()これは通常サーバー側で使用され、ソケットをソケットアドレス構造、つまり指定されたローカルIPアドレスとポート番号に関連付けます。listen()サーバー側で使用され、バインドされたTCPソケットをリスニング状態にします。connect()クライアント側で使用され、ソケットに空いているローカルポート番号を割り当てます。TCPソケットの場合、新しいTCP接続の確立を試みます。accept()これはサーバー側で使用されます。リモートクライアントから受信した新しいTCP接続の作成試行を受け入れ、この接続のソケットアドレスペアに関連付けられた新しいソケットを作成します。send()、、、およびは、recv()データの送受信に使用されます。標準関数およびも使用できます。sendto()recvfrom()write()read()close()ソケットに割り当てられたリソースをシステムが解放するようにします。TCPの場合は、接続が終了します。gethostbyname()ホスト名とアドレスの解決に使用されますgethostbyaddr()。IPv4のみ対応。getaddrinfo()freeaddrinfo()ホスト名とアドレスを解決するために使用されます。IPv4、IPv6 。select()指定されたソケットのリストのうち、1 つ以上のソケットが読み取り可能、書き込み可能、またはエラーが発生するまで待機するために使用します。poll()は、ソケットセット内のソケットの状態を確認するために使用されます。セット内のいずれかのソケットに対して書き込み、読み取りが可能かどうか、またはエラーが発生したかどうかをテストできます。getsockopt()指定されたソケットの特定のソケットオプションの現在の値を取得するために使用されます。setsockopt()指定されたソケットに対して特定のソケットオプションを設定するために使用されます。この関数はsocket()通信用のエンドポイントを作成し、ソケットのファイルディスクリプタを返します。引数は3つです。
domainこれは、作成されるソケットのプロトコルファミリーを指定します。例: type次のいずれか: protocol実際に使用するトランスポートプロトコルを指定します。最も一般的なプロトコルは、 IPPROTO_TCP、IPPROTO_SCTP、IPPROTO_UDP、IPPROTO_DCCPです。これらのプロトコルはファイルで指定されます。値0を使用すると、選択したドメインとタイプからデフォルトのプロトコルを選択できます。<netinet/in.h>エラーが発生した場合は、この関数は-1を返します。それ以外の場合は、新しく割り当てられたディスクリプタを表す整数を返します。
bind()ソケットをアドレスに関連付けます。 でソケットを作成するとsocket()、プロトコルファミリのみが割り当てられ、アドレスは割り当てられません。ソケットが他のホストからの接続を受け入れるには、この関連付けを行う必要があります。この関数には次の 3 つの引数があります。
sockfdソケットを表す記述子my_addrsockaddrバインドするアドレスを表す構造体へのポインタ。addrlensocklen_t構造体のサイズを指定する型のフィールドsockaddr。bind()成功した場合は0を返し、エラーが発生した場合は-1を返します。
ソケットがアドレスに関連付けられた後、listen()着信接続のためにソケットを準備します。ただし、これはストリーム指向(接続指向)データモード、つまりソケットタイプ(SOCK_STREAM、SOCK_SEQPACKET)の場合にのみ必要です。listen()には2つの引数が必要です。
sockfd有効なソケット記述子。backlogは、同時にキューに登録できる保留中の接続数を表す整数です。通常、オペレーティングシステムはこの値に上限を設けています。接続が確立されると、キューから削除されます。成功した場合は0が返され、エラーが発生した場合は-1が返されます。
アプリケーションが他のホストからのストリーム指向接続をリッスンしている場合、そのようなイベントが通知され(select()関数を参照)、関数を使用して接続を初期化する必要がありますaccept()。この関数は、接続ごとに新しいソケットを作成し、リスニングキューから接続を削除します。関数には次の引数があります。
sockfd接続がキューに登録されているリスニングソケットの記述子。cliaddrクライアントのアドレス情報を受信するsockaddr構造体へのポインタ。addrlensocklen_t、に渡されるクライアント アドレス構造体のサイズを指定する場所へのポインタaccept()。accept()が を返すと、この場所に構造体のサイズ (バイト単位) が含まれます。accept()受け入れられた接続用の新しいソケット記述子を返します。エラーが発生した場合は、値-1を返します。以降のリモートホストとの通信はすべて、この新しいソケットを介して行われます。
accept()データグラムソケットは、受信側がリスニングソケットを使用してリクエストに即座に応答できるため、処理を必要としません。
connect()アドレスによって識別される特定のリモートホストと、ファイルディスクリプタによって識別されるソケットを介して直接通信リンクを確立します。
コネクション指向プロトコルを使用する場合、これは接続を確立します。特定の種類のプロトコルはコネクションレスであり、最も有名なのはユーザーデータグラムプロトコルです。コネクションレスプロトコルで使用する場合、connectデータの送受信に使用するリモートアドレスを定義し、sendやなどの関数を使用できるようにしますrecv。これらの場合、connect関数は他のソースからのデータグラムの受信を防止します。
connect()エラーコードを表す整数を返します。0は成功、-1はエラーを表します。歴史的に、BSD 派生システムでは、呼び出しが失敗した場合、ソケット記述子の状態は未定義です( Single Unix Specificationで指定されているとおり)。したがって、移植可能なアプリケーションは、呼び出しが失敗した場合に、ソケット記述子をすぐに閉じ、を使用して新しい記述子を取得する必要があります。[ 5 ]connectsocket()connect()
関数とgethostbyname()は、ドメインネームシステムまたはローカルホストの他のリゾルバメカニズム (ルックアップなど)gethostbyaddr()でホスト名とアドレスを解決するために使用されます。これらの関数は、インターネットプロトコルホストを記述する型のオブジェクトへのポインタを返します。これらの関数は、次の引数を使用します。/etc/hostsstruct hostent
nameホスト名を指定します。addrstruct in_addrホストのアドレスを含むポインタを指定します。lenの長さをバイト単位で指定しますaddr。typeAF_INETホストアドレスのアドレスファミリタイプ(例:)を指定します。エラーが発生した場合は関数が戻り値を返します。NULLその場合、外部の整数値をチェックして、一時的な障害なのか、無効なホストなのか、あるいは不明なホストなのかを確認します。それ以外の場合は、有効な値が返されます。h_errnostruct hostent*
これらの関数は厳密にはBSDソケットAPIの構成要素ではありませんが、ホストを検索するためのAPI関数と組み合わせて使用されることがよくあります。これらの関数は現在、ドメインネームシステムを照会するためのレガシーインターフェースとみなされています。完全にプロトコルに依存しない(IPv6をサポートする)新しい関数が定義されました。これらの新しい関数はgetaddrinfo()とgetnameinfo()で、新しいデータ構造に基づいています。[ 6 ][[addrinfo]]
この2つの関数は、BSDソケットAPI本体が4.2BSD(1983年)で登場したのと同じ年で、DNSが初めて作成された年でもあります。 [ 7 ]初期のバージョンではDNSに問い合わせることはなく、/etc/hostsのルックアップのみを実行していました。4.3BSD(1984年)バージョンでは、DNSが粗雑な方法で追加されました。ネームサービススイッチを使用した現在の実装は、Solarisと後のNetBSD 1.4(1999年)に由来します。[ 8 ]当初はNIS+用に定義されたNSSでは、DNSはこれらの関数によるルックアップの多くのオプションの1つにすぎず、現在でもその使用を無効にすることができます。[ 9 ]
BerkeleyソケットAPIは、ネットワークおよびプロセス間通信のための汎用インターフェースであり、さまざまなネットワークプロトコルとアドレスアーキテクチャの使用をサポートしています。
以下に、最新のLinuxまたはBSD実装で定義されているプロトコルファミリーの例(標準的なシンボル識別子が前に付いています)を示します。
通信用のソケットは、引数としてsocket()目的のプロトコルファミリー(PF_識別子)を指定することで、この関数によって作成されます。
ソケットインターフェースの当初の設計コンセプトでは、プロトコルタイプ(ファミリー)と、それぞれが使用できる特定のアドレスタイプを区別していました。プロトコルファミリーには複数のアドレスタイプが存在することが想定されていました。アドレスタイプは、PFの代わりにAFという接頭辞を使用して、追加のシンボル定数によって定義されていました。AF識別子は、プロトコルファミリーではなく、アドレスタイプを具体的に扱うすべてのデータ構造を対象としていました。しかし、プロトコルとアドレスタイプの分離というこのコンセプトは実装でサポートされず、AF定数は対応するプロトコル識別子によって定義され、 AF 定数とPF定数の区別は、実際的な意味を持たない技術的な議論として残されました。実際、両方の形式の適切な使用法には多くの混乱が存在します。[ 11 ]
POSIX.1—2008仕様ではPF定数は規定されておらず、AF定数のみが規定されている[ 12 ]。
生ソケットは、ホストの TCP/IP スタックによる処理をバイパスするシンプルなインターフェースを提供します。これにより、ユーザー空間でのネットワーク プロトコルの実装が可能になり、プロトコル スタックのデバッグに役立ちます。[ 13 ]生ソケットは、TCP/IP モデルのインターネット層で動作するICMPなどの一部のサービスで使用されます。
Berkeley製ソケットは、ブロッキングモードとノンブロッキングモードの2つのモードのいずれかで動作します。
ブロッキングソケットは、操作で指定されたデータの一部または全部を送信(または受信)するまで制御を返しません。ブロッキングソケットがすべてのデータを送信しないのは正常です。アプリケーションは戻り値をチェックして、送信または受信されたバイト数を判断し、まだ処理されていないデータがあれば再送信する必要があります。[ 14 ]ブロッキングソケットを使用する場合は、accept() に特別な注意を払う必要があります。クライアントが接続フェーズ中に切断した場合、accept() は読み取り可能を示した後もブロックする可能性があるためです。
ノンブロッキングソケットは、受信バッファにあるデータをそのまま返して、すぐに処理を続行します。正しく記述されていない場合、ノンブロッキングソケットを使用するプログラムは、ネットワークリンク速度の変動によって競合状態が発生しやすくなります。
オペレーティングシステムは、ソケットが閉じられるまで、ソケットに割り当てられたリソースを解放しません。これは、接続呼び出しが失敗して再試行される場合に特に重要です。
アプリケーションがソケットを閉じると、ソケットへのインターフェースのみが破棄されます。ソケットを内部的に破棄するのはカーネルの責任です。場合によっては、サーバー側でソケットが最大 4 分間TIME_WAIT状態になることがあります。 [ 15 ]
SVR4システムでは、の使用によりclose()データが破棄される可能性があります。これらのシステムでは、すべてのデータの配信を保証するために、または SO_LINGER の使用がshutdown()必要になる場合があります。[ 16 ]
伝送制御プロトコル(TCP)は、バイトストリームの送信に対してさまざまなエラー訂正機能とパフォーマンス機能を提供するコネクション指向socket()プロトコルです。プロセスは、プロトコルファミリ(PF INET、PF_INET6)、ストリームソケットのソケットモード(SOCK_STREAM)、およびTCPのIPプロトコル識別子(IPPROTO_TCP)のパラメータを指定して関数を呼び出すことにより、TCPソケットを作成します。
TCPサーバーを構築するには、以下の基本的な手順が必要です。
socket()。bind()ポート番号を設定した後、ソケットをリスニングポートにバインドします。listen()。accept())。これにより、着信接続が受信されるまでプロセスがブロックされ、受け入れられた接続のソケット記述子が返されます。最初の記述子はリスニング記述子として残り、accept()ソケットが閉じられるまでいつでもこのソケットを使用して再度呼び出すことができます。send()API関数とrecv()、汎用関数とを使用してリモートホストと通信しますwrite()。read()close()以下のプログラムは、ポート番号1100で待機するTCPサーバーを作成します。
#include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h>#include <arpa/inet.h> #include <netinet/in.h> #include <sys/socket.h> #include <sys/types.h> #include <unistd.h> int main ( void ) { int sockfd = socket ( PF_INET , SOCK_STREAM , IPPROTO_TCP ); if ( sockfd == -1 ) { fprintf ( stderr , "ソケットの作成に失敗しました! \n " ); return EXIT_FAILURE ; } struct sockaddr_in sa = { . sin_family = AF_INET , . sin_port = htons ( 1100 ), . sin_addr . s_addr = htonl ( INADDR_ANY ) }; if ( bind ( sockfd , ( struct sockaddr * ) & sa , sizeof ( sa )) == -1 ) { fprintf ( stderr , "ソケットのバインドに失敗しました! \n " ); close ( sockfd ); return EXIT_FAILURE ; } if ( listen ( sockfd , 10 ) == -1 ) { fprintf ( stderr , "ソケットのリッスンに失敗しました! \n " ); close ( sockfd ); return EXIT_FAILURE ; } while ( true ) { int connfd = accept ( sockfd , NULL , NULL ); if ( connfd == -1 ) { fprintf ( stderr , "接続の受け入れに失敗しました! \n " ); close ( sockfd ); returnEXIT_FAILURE ; } // 読み書き操作を実行... // read(connfd, buff, size) if ( shutdown ( connfd , SHUT_RDWR ) == -1 ) { fprintf ( stderr , "接続のシャットダウンに失敗しました! \n " ); close ( connfd ); close ( sockfd ); return EXIT_FAILURE ; } close ( connfd ); }close ( sockfd ); return EXIT_SUCCESS ; }TCPクライアントアプリケーションをプログラミングするには、以下の手順が必要です。
connect())に接続するには、AF_INETに設定されたsockaddr_in構造体、エンドポイントがリッスンしているポート(ネットワークバイトオーダー)に設定された、およびリッスンサーバーの IP アドレス(これもネットワークバイトオーダー)に設定された構造体を渡します。sin_familysin_portsin_addrsend()API関数とrecv()、汎用関数とを使用してリモートホストと通信しますwrite()。read()close()。#include <stdio.h> #include <stdlib.h> #include <string.h>#include <arpa/inet.h> #include <netinet/in.h> #include <sys/socket.h> #include <sys/types.h> #include <unistd.h> int main ( void ) { int sockfd = socket ( PF_INET , SOCK_STREAM , IPPROTO_TCP ); if ( sockfd == -1 ) { fprintf ( stderr , "ソケットの作成に失敗しました! \n " ); return EXIT_FAILURE ; } struct sockaddr_in sa = { . sin_family = AF_INET , . sin_port = htons ( 1100 ), }; int res = inet_pton ( AF_INET , "192.168.1.3" , & sa . sin_addr );if ( connect ( sockfd , ( struct sockaddr * ) & sa , sizeof ( sa )) == -1 ) { fprintf ( stderr , "接続確立に失敗しました! \n " ); close ( sockfd ); return EXIT_FAILURE ; } // 読み書き操作を実行... close ( sockfd ); return EXIT_SUCCESS ; }ユーザーデータグラムプロトコル(UDP)は、配信が保証されないコネクションレスプロトコルです。UDPパケットは、順不同で到着したり、複数回到着したり、まったく到着しない場合があります。このような最小限の設計のため、UDPはTCPよりもオーバーヘッドが大幅に少なくなっています。コネクションレスであるということは、2つのホスト間にストリームや永続的な接続という概念がないことを意味します。このようなデータはデータグラム(データグラムソケット)と呼ばれます。
UDPアドレス空間、すなわちUDPポート番号の空間(ISO用語ではTSAP)は、TCPポートの空間とは完全に分離している。
アプリケーションは、次のようにポート番号で UDP サーバーを設定できます7654。プログラムには、関数を使用して UDP データグラムを受信する無限ループが含まれていますrecvfrom()。
#include <stdbool.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h>#include <netinet/in.h> #include <sys/socket.h> #include <sys/types.h> #include <unistd.h>int main ( void ) { struct sockaddr_in sa = { . sin_family = AF_INET , . sin_addr . s_addr = htonl ( INADDR_ANY ), . sin_port = htons ( 7654 ) }; char buffer [ 1024 ]; ssize_t recsize ; socklen_t fromlen = sizeof ( sa );int sock = socket ( PF_INET , SOCK_DGRAM , IPPROTO_UDP ); if ( bind ( sock , ( struct sockaddr * ) & sa , sizeof ( sa )) == -1 ) { fprintf ( stderr , "ソケットのバインドに失敗しました! \n " ); close ( sock ); return EXIT_FAILURE ; }while ( true ) { recsize = recvfrom ( sock , ( void * ) buffer , sizeofbuffer , 0 , ( struct sockaddr * ) & sa , & fromlen ); if ( recsize < 0 ) { fprintf ( stderr , "%s \n " , strerror ( errno ) ); return EXIT_FAILURE ; } printf ( "recsize: %d \n " , ( int ) recsize ); sleep ( 1 ); printf ( "datagram: %.*s \n " , ( int ) recsize , buffer ); } }以下は、「Hello, world!」という文字列を含むUDPパケットを127.0.0.1ポート番号のアドレスに送信するクライアントプログラムです7654。
#include <errno.h> #include <stdlib.h> #include <stdio.h> #include <string.h>#include <arpa/inet.h> #include <netinet/in.h> #include <sys/socket.h> #include <sys/types.h> #include <unistd.h>int main ( void ) { struct sockaddr_in sa = { // アドレスは IPv4 です。sin_family = AF_INET , // IPv4 アドレスは uint32_t なので、オクテットの文字列表現を適切な値に変換します。sin_addr . s_addr = inet_addr ( "127.0.0.1" ), // ソケットは unsigned short なので、htons(x) は x がネットワークバイトオーダーであることを保証し、ポートを 7654 に設定します。sin_port = htons ( 7654 ) }; char buffer [ 200 ]; strcpy ( buffer , "Hello, world!" );// UDPを使用してインターネットデータグラムソケットを作成しますint sock = socket ( PF_INET , SOCK_DGRAM , IPPROTO_UDP ); if ( sock == -1 ) { // ソケットの初期化に失敗した場合は終了しますfprintf ( stderr , "ソケットの作成に失敗しました! \n " ); return EXIT_FAILURE ; }// sendto() を使用してメッセージを送信しますint bytes_sent = sendto ( sock , buffer , strlen ( buffer ), 0 , ( struct sockaddr * ) & sa , sizeof ( sa ));if ( bytes_sent < 0 ) { fprintf ( stderr , "パケット送信エラー: %s \n " , strerror ( errno )); return EXIT_FAILURE ; } close ( sock ); // ソケットを閉じるreturn 0 ; }このコードでは、bufferは送信するデータへのポインタであり、はbuffer_lengthデータのサイズを指定します。
ソケットインターフェースの正式な標準定義は、POSIX標準に含まれており、以下のように呼ばれています。
この規格および現在進行中の作業に関する情報は、オースティン市のウェブサイトで入手できます。
基本ソケットAPIに対するIPv6拡張機能は、RFC 3493およびRFC 3542に記載されています。