SS-Infra ← ポータルに戻る

ソケット接続

1. ソケット接続の概要と目的

ソケット接続(Socket Connection)は、ネットワーク上の2つのプログラムの間に通信路を確立し、データをやり取りするための仕組みです。

Webサイトの閲覧、メールの送受信、データベースへの問い合わせ。これらはすべて、内部ではソケット接続によってデータが流れています。普段は意識しませんが、ネットワークを使うプログラムは例外なくこの仕組みの上で動いています。

この章では、ソケット接続がどのようなものなのか、また、なぜ知っておく必要があるのかを確認します。

ソケット接続とは?

ソケット接続とは、OSが提供する通信機能を利用して、プログラムの間にデータの通り道を作る仕組みです。1980年代にBSD系のUNIXで実装された「バークレーソケット」が原型となり、現在ではほぼすべてのOSとプログラミング言語が同じ考え方のAPIを備えています。

ソケットは、通信の出入口を表す抽象的な存在です。通信相手を特定するには、次の組み合わせが必要になります。

IPアドレス + ポート番号
(例)192.0.2.10 : 443

この組み合わせを「エンドポイント」と呼び、1つの接続は送信元と宛先の両方のエンドポイントによって識別されます。

192.0.2.50:52314  ←→  192.0.2.10:443
(クライアント側)        (サーバー側)

同じサーバーの同じポートに多数の利用者が同時に接続できるのは、クライアント側のポート番号がそれぞれ異なるため、接続を区別できるからです。

このように、ネットワークの細かな手続きをOSに任せ、プログラムからは「読み書きできる通り道」として扱える点がソケットの大きな特徴です。

なお、UNIX系のOSでは、ソケットはファイルと同じようにファイルディスクリプタとして扱われます。そのため、ファイルの読み書きとよく似た手順でデータを送受信できます。

ソケット接続はどのような場面で登場するのか?

ソケット接続は、通信を行うプログラムの基盤として、あらゆる場面に現れます。

例えば、次のような場面が代表的な利用例です。

Web開発者やインフラ担当者にとって、ソケット接続は通信障害の原因を切り分けるうえで欠かせない前提知識です。

2. ソケット接続の仕組み・規格・主な特徴

ソケット接続は、OSが用意したAPIを決まった順序で呼び出すことで確立されます。

サーバー側は接続を待ち受ける側、クライアント側は接続を要求する側として、それぞれ異なる手順をたどります。

ここでは、通信方式の違いや、実際の記述例を確認します。

TCPとUDPの違いと特徴

ソケットには、使用する通信方式によっていくつかの種類があります。代表的なものは次の2つです。

このほか、同一ホスト内のプロセス同士をつなぐUNIXドメインソケットがあります。IPアドレスとポート番号ではなくファイルパスで指定し、ネットワークを経由しないため負荷が軽くなります。WebサーバーとPHP-FPMの連携などで用いられます。

TCPで接続が確立される流れを具体的に追うと、次のようになります。

  1. サーバー側がソケットを作成し、待ち受けるIPアドレスとポート番号を割り当てる(bind)。
  2. サーバー側が接続要求の受付を開始し、LISTEN 状態になる(listen)。
  3. クライアント側がソケットを作成し、サーバーへ接続を要求する(connect)。
  4. 両者の間で3回のやり取り(SYN → SYN+ACK → ACK)が行われ、接続が確立する。
  5. サーバー側が接続を受け取り、通信用のソケットを得る(accept)。
  6. 双方がデータを読み書きし、通信を終えたら接続を閉じる(close)。

この4の手続きを「3ウェイハンドシェイク」と呼びます。接続が確立した状態は ESTABLISHED として確認できます。

なお、ハンドシェイク自体はOSのカーネルが処理し、確立した接続は待機列(バックログ)に積まれます。accept は、その待機列から接続を1つ取り出す操作です。

ソケット通信の記述

実際のプログラムでは、言語が用意したソケットのAPIを順に呼び出します。

例えば、次のような記述になります。

【記述・構造の具体例】

サーバー側が接続を待ち受け、クライアント側から接続する例です。

# サーバー側
import socket

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind(("127.0.0.1", 50007))   # 待ち受けるアドレスとポート
    s.listen(5)                     # 受付を開始する
    conn, addr = s.accept()         # 接続が来るまで待機する
    with conn:
        data = conn.recv(1024)
        conn.sendall(data)
# クライアント側
import socket

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.settimeout(5)                 # タイムアウトを設定する
    s.connect(("127.0.0.1", 50007))
    s.sendall(b"hello")
    print(s.recv(1024))

このように、socket でソケットを作り、サーバー側は bind と listen で待ち受け、クライアント側は connect で接続するという手順は、言語が変わってもほぼ共通です。

なお、上の例で bind の第1引数に 127.0.0.1 を指定している点が重要です。この値を 0.0.0.0 にすると、外部のネットワークからも接続できる状態になります。同一サーバー内からのみ使うサービスであれば、127.0.0.1 に限定するのが原則です。

3. ソケット接続が使われる理由・メリットと活用シーン

ソケット接続が使われる理由は、ネットワーク通信の複雑な処理をOSが引き受け、プログラムからは単純な読み書きとして扱えることにあります。

通信相手が同じサーバー内でも地球の裏側でも、プログラムの書き方はほとんど変わりません。

ソケット接続を理解するメリット

ソケットの仕組みを知らないまま通信プログラムを扱うと、障害が起きたときに原因の切り分けができません。

ソケット接続を理解しておくことで、次のようなメリットが生まれます。

ただし、ソケットを直接扱う場面は実務では限られます。多くの場合はHTTPクライアントやデータベースドライバがその上に用意されており、開発者はそれらを通じて間接的に利用します。

システム開発や運用で活用する

実際の開発・運用では、以下のような場面で活用されます。

このように、接続が「確立できるか」「維持できるか」「適切に閉じられるか」が運用上の論点になります。

ソケット接続の確認方法

現在の待ち受け状況や接続状態は、コマンドで確認できます。

# 待ち受けているTCPポートとプロセスを一覧で確認する
ss -ltnp

# 確立している接続を確認する
ss -tn state established

# 状態ごとの接続数を集計する
ss -tan | tail -n +2 | awk '{print $1}' | sort | uniq -c

# 特定のポートを使っているプロセスを調べる
lsof -i :8080

なお、ss -ltnp でプロセス名まで表示するには、管理者権限が必要です。

外部から特定のポートへ到達できるかは、次のように確認します。

# ポートへの接続を試す
nc -zv example.com 443

# コマンドを追加せずにbashの機能で試す
timeout 3 bash -c 'cat < /dev/null > /dev/tcp/example.com/443' && echo "接続可"

ここで押さえておきたいのは、接続失敗の種類によって原因が異なるという点です。

同じ「接続できない」でも、前者はサーバー内部の問題、後者は経路上の問題を示します。この区別ができると、調査の方向を大きく絞れます。

Web上の確認ツールを利用すれば、外部のネットワークから自分のサーバーのポートが開いているかを確認できます。手元の環境からは接続できるのに外部からはつながらない、といった切り分けに有効です。

関連技術・関連用語との違い

ソケット接続と混同されやすい関連用語として、次のようなものがあります。

ソケットが「通信路そのもの」であるのに対し、HTTPやWebSocketは「その通信路の上で何をどう送るか」を定めたものです。

階層が異なるため、障害の調査では「ソケットの接続自体ができているか」と「その上のプロトコルが正しく動いているか」を分けて確認する必要があります。

4. ソケット接続の導入・運用時の注意点と課題

ソケット接続は、確立すること自体は容易ですが、適切に閉じること、数を管理することが運用上の課題になります。

また、待ち受けの設定を誤ると、意図せず外部へサービスを公開してしまう恐れがあります。

ここでは、実務上注意すべきポイントを説明します。

よくあるミス・設定ミス

よくある問題の一つが、受信したデータが1回の読み取りですべて届くと想定してしまうことです。

TCPはデータを「連続した流れ」として扱うため、送信側が一度にまとめて送ったデータが、受信側では複数回に分かれて届くことがあります。逆に、複数回に分けて送ったデータがまとまって届くこともあります。1回の recv で必要な分がすべて取得できる前提でコードを書くと、データが途中で切れる不具合につながります。受け取るデータの長さをあらかじめ決めるか、区切り文字を定めるなど、メッセージの境界を自分で管理する必要があります。

また、次のようなミスも頻出します。

運用・セキュリティ上の注意点

待ち受けアドレスの設定は、セキュリティに直結します。

【よくある失敗・注意点の例】

内部向けのデータベースが、外部からも接続できる状態になっている例です。

危険な状態(待ち受けが 0.0.0.0 になっている)

State   Recv-Q  Send-Q  Local Address:Port  Peer Address:Port  Process
LISTEN  0       151     0.0.0.0:3306        0.0.0.0:*          users:(("mysqld",pid=812,fd=23))
LISTEN  0       511     0.0.0.0:6379        0.0.0.0:*          users:(("redis-server",pid=655,fd=6))

本来あるべき状態(待ち受けが 127.0.0.1 に限定されている)

State   Recv-Q  Send-Q  Local Address:Port  Peer Address:Port  Process
LISTEN  0       151     127.0.0.1:3306      0.0.0.0:*          users:(("mysqld",pid=812,fd=23))
LISTEN  0       511     127.0.0.1:6379      0.0.0.0:*          users:(("redis-server",pid=655,fd=6))

この例では、0.0.0.0 で待ち受けているため、同一サーバー内からだけでなく、ネットワークが到達できるすべての場所から接続を試せる状態になっています。

データベースやキャッシュサーバーの類は、初期設定のまま認証が緩い場合も多く、外部に露出したまま運用されて情報が流出する事故が繰り返し報告されています。アプリケーションと同一サーバーで動かしているのであれば、待ち受けを 127.0.0.1 に限定するか、UNIXドメインソケットを使用してください。

重要なのは、localhost への接続テストではこの状態に気づけないという点です。待ち受けが 0.0.0.0 でも 127.0.0.1 でも、サーバー内部から自分自身へ接続を試せば同じように成功するため、外部へ開いているかどうかの判断材料になりません。上の例のように ss で待ち受けアドレスそのものを確認し、あわせて外部のネットワークから実際にポートへ接続を試す必要があります。

そのほか、運用面では次の点にも注意が必要です。

安全に利用するための対策

ソケット接続を適切に運用するためには、以下の対策が有効です。

特に、待ち受けアドレスの設定は、サーバー内部からの接続テストでは問題が見えません。外部からの到達性を実際に確かめる手順を、公開前の確認項目に含めておくことが重要です。

5. まとめと関連ツールのご案内

ここまで解説したソケット接続について、重要なポイントを振り返ります。

【お役立ちツールのご案内】

ここまで解説したソケット接続が、外部のネットワークから実際に到達できる状態かを確認したい場合は、当サイトの TCPポート開放確認ツール をぜひご活用ください。

ホスト名とポート番号を入力するだけで、外部から接続できるかどうかを確認できます。サーバー公開前の疎通確認や、意図せず外部へ開いているポートがないかの点検にお役立てください。