ネットワーク●●●○○

ソケット

プログラムからネットワーク通信を行うためのOSが提供する抽象。

概要

ソケットは、アプリケーションがネットワーク通信を行うときにOSが提供する「取っ手」となるインターフェースです。TCP/IPの再送制御やパケットの分割・組み立てはOSのカーネルが引き受け、プログラム側はソケットという抽象を通じて、ファイルに読み書きするのと同じ感覚でデータを送受信できます。相手の指定はIPアドレスポート番号の組で行います。

WebサーバもDBクライアントもブラウザも、ネットワークを使うプログラムは例外なく内部でソケットを開いています。普段はHTTPクライアントライブラリなどに隠れて見えませんが、「コネクションが切れる」「接続数が上限に達する」といった運用上の問題は、この層に翻訳して考えると急に見通しがよくなります。

なぜ生まれたか

TCP/IPが標準化された当初、プロトコルの仕様はあっても「プログラムからそれをどう使うか」の共通の形はありませんでした。アプリケーションを書くたびにパケットの組み立てや再送のような低レベルの処理を意識するのは非現実的です。そこで1983年、BSD UNIXが「通信の端点をファイルのように open して read/write する」というAPI——バークレーソケット——を導入しました。

UNIXにはもともと「あらゆるものをファイルとして扱う」という設計哲学があり、ソケットはネットワークをその世界観に取り込んだ発明でした。既存のファイル操作と同じ関数でネットワークも読み書きできるこの抽象は圧倒的に使いやすく、以後ほぼすべてのOSと言語がこのモデルを踏襲しています。今日のあらゆるネットワークプログラミングの語彙(bind、listen、accept、connect)は、この40年前のAPIに由来します。

詳細

コネクションが確立するまでの流れ

TCPのソケットには、待ち受ける側(サーバ)と接続しに行く側(クライアント)で役割の非対称があります。サーバ側は、ソケットを作成し、自分のIPアドレスとポート番号に紐付け(bind)、待ち受けを開始(listen)します。クライアントが接続してくる(connect)と、サーバはacceptによって「その相手専用の通信用ソケット」を新しく受け取ります。

サーバクライアントサーバクライアントsocket でソケットを作成bind でIPアドレスとポート番号に紐付けlisten で待ち受けを開始3ウェイハンドシェイクaccept が相手専用の通信用ソケットを返すclose でコネクションを閉じるconnect で接続要求write でデータを送信read で受け取り write で応答

ポイントは、待ち受け用のソケットと通信用のソケットが別物であることです。待ち受けソケットは1つのまま、acceptのたびに新しいソケットが生まれるので、1つのポートで同時に何千ものクライアントと通信できます。「1ポート1接続」ではありません。

大量接続をさばく: 多重化という主戦場

古典的なサーバは接続ごとにプロセススレッドを割り当てていましたが、接続数が万の単位になると生成コストとメモリが破綻します(C10K問題)。現代のサーバはepoll(Linux)やkqueue(BSD/macOS)といったOSの仕組みで「多数のソケットを1つのループで監視し、動きのあったものだけ処理する」イベント駆動方式を採ります。Node.jsやNginxの「シングルスレッドで大量の同時接続に強い」という特徴は、まさにこのソケット多重化の話です。

種類とバリエーション

ここまで述べたのはTCPに対応するストリームソケットですが、UDPに対応するデータグラムソケットもあり、こちらはconnectやacceptを経ずに1つのソケットで不特定多数と送受信できます。また、同じマシン内のプロセス同士が通信するUNIXドメインソケットは、ネットワークを経由しないぶん高速で、DockerデーモンやPostgreSQLのローカル接続などで広く使われています。なおWebSocketは名前が似ていますが、ブラウザ向けにHTTPの上で双方向通信を実現する別の(より上位の)プロトコルです。

運用の語彙をソケットに翻訳する

実務のトラブルは、ソケットの状態に翻訳できるものが多くあります。「Connection refused」は宛先のポートでlistenしているプログラムがいない状態、「Address already in use」は同じアドレスとポートに二重にbindしようとした状態です。サーバ再起動直後に後者が出るのは、閉じたばかりのコネクションがTIME_WAITという残留状態にあるためで、SO_REUSEADDRというオプションで回避するのが定番です。

また、ソケットはOSから見るとファイルディスクリプタの一種なので、プロセスごとの上限(ulimit)を食い潰すと「Too many open files」で新規接続を受けられなくなります。接続をこまめに張り直すコストが高いこと(TCPのハンドシェイクが毎回走る)から、DBやHTTPクライアントでは確立済みソケットを使い回すコネクションプーリングが常識です。「コネクションが切れる」「接続数の上限」「keep-alive の設定」——これらの運用語彙はすべて、ソケットという一枚の層の上に載っています。