ソケット通信を一緒に理解しよう！！

# はじめに
「ソケット通信ってなんやねん」、そう思った経験はみなさんもあると思います。
私もそのうちの一人です 👍
個人的に初学者の方がつまづくポイントが多い概念なのかなと感じていました。
本記事では、そのモヤモヤを解消すべくソケットの基礎概念に触れていこうと思います。
この記事を読み終わった後、皆さんはソケットを理解し、ウキウキになれるはずです！

***では、一緒に「見えない通信」の扉を開きましょう〜*** 👽


# 対象読者
- ***ソケット通信を基礎から理解したい方！！***

# ソケット通信とは何か？
まず、ソケットとは何なのかみていきましょう。
## ソケットとは
**ソケット** は、`オペレーティングシステム (OS)` が提供する、プロセス間通信のエンドポイント、つまり「**出入り口**」のことです。
同じコンピュータ内の異なるプログラム同士、あるいは、ネットワークを介した異なるコンピュータ上のプログラム同士がデータのやり取りをする際に、この「ソケット」が窓口の役割を果たします。

<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763692/10ef9d5c-6763-42f9-adb3-1877b80aab64.png">

:::note info
各種OSは「**`ソケットライブラリ`**」を持っており、そのライブラリ内のプログラムを呼び出すことでソケット通信を実現しています。
:::


まずは、ソケット通信が『ソケット』という出入り口を使ってデータをやり取りする仕組みだということがイメージできればOKです！

次に理解しておきたいのが、「**OSI参照モデル**」になります。
## OSI参照モデルとの関係性
ネットワークの仕組みを理解する際に欠かせないのが、「**OSI参照モデル**」。
これは、ネットワークの機能や仕組みを７つの階層（`レイヤー`：L）に分類したもので、多くの方が下のイメージを見たことがあるのではないでしょうか。
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763692/fe35d08c-0211-4072-a715-b84945fadd2d.png">

各層について軽く触れておきます。
- *アプリケーション層*（L7）
    - ユーザーが操作するアプリケーションが提供する機能に必要な通信サービスを実現するためのプロトコル群
    - `HTTP`, `DNS` など

- *プレゼンテーション層*（L6）
    - データの表現形式を整形するプロトコル群
    - `MPEG`, `JPEG` など

- *セッション層*（L5）
    - 通信セッションの確立・維持・終了を管理するプロトコル群
    - `NetBIOS`, `WebSocket` など

- *トランスポート層*（L4）
    - データ転送の信頼性やフロー制御、ポート番号によるプロセスの識別を行うプロトコル群
    - `TCP`, `UDP`など

- *ネットワーク層*（L3）
    - 起点から終点までの通信を実現するプロトコル群
    - `IP`, `ICMP` など

- *データリンク層*（L2）
    - 隣接するノード間の通信を実現するプロトコル群
    - `Ethernet`, `MACアドレス`など

- *物理層*（L1）
    - 通信のための物理的な仕様についてのプロトコル群
    - `IEEE`, `ISDN` など


ソケットは主に、**トランスポート層**（第4層）で動作します。**アプリケーション層**(第7層)からのデータ送受信のリクエストを受け、TCPやUDPといったプロトコルを使ってデータをセグメント化し、ヘッダー情報を付加してネットワーク層へと渡します。
これにより、アプリケーションはネットワークの複雑な詳細を意識することなく、ソケットという窓口を通じて通信できるようになっています。

## TCP と UDPの違い
ソケット通信の具体的なプロトコルとして **`TCP`・`UDP`** があげられます。
まずは、TCP, UDP についてざっくり解説します！
### TCP について
**TCP**(`Transmission Control Protocol`)は、データの信頼性を最も重視した通信プロトコルです。送信元と宛先の双方で情報の確認のやり取りをする *スリーウェイハンドシェイク*[^1] を必ず行うため高い信頼性が確保されています。

この *スリーウェイハンドシェイク* によって通信経路が確立された後、「データの順序付け（順番通りに届ける）」、「エラー検出」、「再送（届かなかったデータを送り直す）」といった仕組みが働き、正確なデータ伝送を保証します。

このようなプロトコルを、「***コネクション型のプロトコル*** 」と表現します。

TCPは主に、以下の用途で使用されます。
- Webの閲覧 
- メールの送受信 
- ファイル転送 
などなど、、、

`TCP` は、**データの欠損や順序の狂いが許されない場面** で広く利用されています。

### UDP について
**UDP**(`User Datagram Protocol`)は、送受信を開始する前に通信相手との接続を確立をしません。
単にデータパケット(データグラム) を目的地に一方的に送信するだけを行います。そのため、TCPよりも **高速** ですが、データの到着保証や順序保証がないため、**信頼性には劣り**ます。

このようなプロトコルを、「***コネクションレス型のプロトコル***」と表現します。

UDPは主に、以下の用途で使用されます。
- 動画ストリーミング
- オンラインゲーム
- VoIP(Voice over Internet Protocol)
- DNS(Domain Name System)
などなど、、、

`UDP` は、リアルタイム性や速度が最優先され、多少のデータ欠損や順序の入れ替わりが許容される場面で広く利用されています。

:::note info
**ポイント**
TCPとUDPについて触れる際、よく「**信頼性**」という言葉が登場します。
これは「*スリーウェイハンドシェイク* 」の有無だけでなく、
`TCP` はシーケンス番号や確認応答番号(ACK)など、さまざまな情報が含まれているヘッダを持っているのに対し、`UDP` のヘッダには必要最低限の情報しか含まれていないといという違いを理解しておくと良いでしょう。
:::

# ソケット通信のライフサイクル
続いてソケット通信が開始されてから終了するまでのライフサイクルについて解説していこうと思います。
このライフサイクルを理解する上で重要となってくるのが、サーバー側とクライアント側の関係性です。
全体像は下の画像の通りです。
<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763692/e528765f-7687-4090-8cfb-e514ea0cc9e0.png">

それでは順を追ってみていきましょう！
## ソケット操作のフロー
### 1. ソケットの作成
まずは、通信の出入り口になるソケットを **socket()** 関数を用いて、ソケットオブジェクトの作成をしていきます。
この時、`サーバー側` と `クライアント側` の両方にソケットを作成する必要があります。


### 2. IPアドレスとポート番号へのバインド
ソケットを作成しただけでは、サーバーはまだクライアントからの接続を受け付けられません。サーバーはクライアントが接続できる「場所」を明確にする必要があります。
そこで、ソケットを特定のIPアドレスとポートに紐付ける **`bind()`** 関数を用意します。
もしこのバインドに失敗した場合（例えば、すでにポートが使われている場合など）、エラーが発生し、処理は続行できません。

**クライアント側** は、この段階では、特に何も行いません。サーバーの準備が整うのを待ちます。場合によっては、クライアント側でも`bind()`の設定を行います。

### 3. 接続の待ち受け
バインドされたソケットを、クライアントからの接続を待ち受ける **リッスン状態** にします。
- *サーバー側*
    - `listen()` 関数を実行し、接続待ち状態にします
    - この`listen()` 自体は通常ブロック（処理停止）しません

- *クライアント側*
    - こちらは引き続き、特に何も行いません

### 4. クライアント側からの接続要求とサーバーの受け入れ
サーバーの準備が整ったら、クライアントがサーバーへ接続を試み、サーバーがそれを受け入れる通信が確立されます。
- *クライアント側*
    - サーバーのIPアドレスとポート番号を指定して、**`connect()`**  関数を実行し、接続要求を送信します
    - `TCP` の場合、ここで*スリーウェイハンドシェイク* が行われ、サーバーとの間で通信経路の確立を試みます
    - 接続に失敗した際は、エラーとなります

- *サーバー側*
    - リッスンしているソケットがクライアントからの`connect()` 要求を受け取ると、`accept()` 関数を実行します。この`accept()`関数は、**接続要求が来るまで処理をブロックします**
    - 接続要求が正常に受け入れられると、そのクライアント専用の新しいソケットインスタンスが生成されます
    - 元のリスニングソケットは、引き続き次のクライアントからの接続を待ち続けます
    **👉🏻** サーバーは多数の異なるクライアントを同時に処理でき、それぞれが独自のソケットを持つことが可能となります

下の図は`N個` のクライアントが、それぞれ個別のソケットを通じて`1つ` のサーバーと通信する様子を表しています。
<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763692/87ee02ba-921a-4fd7-a3d6-800a67c9f548.png">



#### 補足：*多数の同時接続をするサーバー*
ソケット通信は、サーバーとクライアント間で確立された接続を通じてデータをやり取りします。
特にサーバーでは、多数のクライアントからの同時接続に対応する必要があるため、その処理方法はパフォーマンスに大きく影響します。

従来の同期的な **`ブロッキングI/Oモデル`**[^2] では、各クライアント接続を処理するために、個別のスレッドやプロセスを割り当てることが一般的です。これは実装がシンプルですが、問題点があります。
接続数が増えると、各スレッドやプロセスがそれぞれメモリを消費し、OSはそれらの間でCPUの実行権を頻繁に切り替えると、**オーバーヘッドを多発させます**。

オーバーヘッドとなることで、処理が断片化し全体のパフォーマンスが急速に低下する原因につながります。



::: note info
余談
人間も頻繁にタスクを切り替える作業をしていると全体としての効率を低下させてしまい、一度中断されると元の作業の集中力をとり戻すまで平均23分15秒かかると言われています。
私もよく脳内スタックオーバーフローを起こしてしまいます、、🫣
[*It takes 23 mins to recover after an interruption*](https://addyo.substack.com/p/it-takes-23-mins-to-recover-after)
:::

#### 高パフォーマンスなサーバーはどのように実現しているのか？
リアルタイムAPIのような遅延がゆるされない高性能システムでは、このスケーリングの限界を克服するために、**ノンブロッキングI/O** や **イベント駆動型アーキテクチャ** といった技術が採用されています。

これらのアプローチでは、ソケットの読み書きがすぐに完了しない場合でも、**処理をブロックせずに他のソケットの処理へ移ります**。
そして、OSが特定のソケットがデータの読み書き準備ができたことを検知した際にのみ、アプリケーションに通知する仕組みをとります。

これを実現するために、select[^3]、poll[^4]のようなシステムコールを用います。
さらに、Linux系のOSに備わっている **epoll**、BSDやmacOSの**kqueue**を使用することで、接続数が増えても高いパフォーマンスを維持し、効率的に処理を行えるようになります。

これらのイベント通知メカニズムは、`アイドル待機`(I/O操作の完了をCPUが待つ無駄な時間) や`冗長なポーリング`（アプリケーションがI/Oの状態を繰り返し確認する非効率な処理） を回避することで、CPU使用率とレイテンシを大幅に削減します。

### 5. データの送受信
接続が確立されたら、いよいよクライアントとサーバー間でデータのやり取りが始まります。
このデータ送受信は、もちろん双方向で行われます。
点線で囲った意図として、ここではループ処理を行います。
データの送受信が一度のやり取りで全て終わるとは限らないため、一般的にバッファサイズを決めてそのデータ量を繰り返し送受信します。

- *サーバー側*
    - `accept()` で得られた新しいソケットを使って、クライアントからデータを受信（**`read()`**）したり、クライアントへデータを送信（**`write()`**）したりします

- *クライアント側*
    - `connect()` で確立されたソケットを使って、サーバーへデータを送信(**`write()`**)したり、サーバーからデータを受信（**`read()`**）したりします

もしここでループ処理がなければ、デッドロックが発生し`read()`が両者待ち状態になってしまいます。

#### ソケットとファイルディスクリプタについて
Unix系システムでは、ソケットは**ファイルディスクリプタ**[^5] として扱われます。
これはファイルを開いて操作するのと同様に、ソケットへの書き込みや読み取りができます。
つまり、`read()` や `write()` といった一般的なシステムコールがソケットにも使用できるのはこのためです。

プロセスがファイルを開くと、OSはそのファイルを表すエントリを作成し、そのファイルに関する情報をカーネル内に保存します。
このエントリが整数で表され、これが**ファイルディスクリプタ**となります。
例えば、1つのプロセスが複数のファイルやソケットを開くと、それぞれにファイルディスクリプタと呼ばれる整数（たとえば 3, 4, 5...）が割り当てられます。

:::note warn
0, 1, 2 はそれぞれ標準入力（`stdin`）、標準出力（`stdout`）、標準エラー出力（`stderr`）として予約されています
:::
カーネル内部では、ファイルディスクリプタ(整数)をキーとして、該当するオープンファイルの情報（ファイルの種類、位置、モードなど）が管理されています。

以下の画像は、ファイルディスクリプタのイメージです。
<img width="700" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763692/c8119ec1-5f7c-42ce-9c55-c55eba0553aa.png">

同様に、ネットワークソケットを開いた場合も、それは整数で表され、**ソケットディスクリプタ**と呼ばれます。
つまり、プロセスにとって、開いているファイルもネットワークソケットも、OSから見れば本質的には「***整数で表現されたリソース***」として一貫して扱われています。


### 6. サーバー ・ クライアント間の接続を閉じる
クライアントとサーバー間の通信が終了したら、利用していたソケットを　**`close()`** 関数で閉じます。
これはメモリリークを防ぎます。
特に負荷が高いサーバーではリソースの枯渇に繋がるのでそのような事態を防ぐための重要な処理となっています。
- *サーバー側*
    - クライアントとの通信が終了した専用ソケットを閉じます
    - これにより、そのソケットが使用していたシステムリソースが解放され、OSに返却されます

- *クライアント側*
    - サーバーとの通信が終了したら、使用していたソケットを閉じます
    - 同様にリソースを解放します

## ソケットの内部構造を意識する
TCPソケットの場合、システムは各接続のステートマシンを維持しています。
これは、ソケットが単にオープンやクローズされるだけでなく、そのライフサイクル中に複数の状態に遷移しています。
一般的なTCPソケットの状態には、以下の画像の状態が存在し、接続の確立、維持、および切断のさまざまなフェーズを表しています。
画像をよくみていただくと、TCPの*スリーウェイハンドシェイク* でよく見る「`SYN`」・「`ACK`」・「`FIN`」といったフラグのやり取りを確認できると思います。
<img width="700" src="https://detailed.wordpress.com/wp-content/uploads/2016/09/tcp_states1.png">

> 引用：[TCP (Transmission Control Protocol) RFC 793
](https://detailed.wordpress.com/2016/09/12/tcp-transmission-control-protocol/)

状態遷移の知識が単なる理論ではなく、**ポート枯渇**や**ソケットリークの診断**, **予期せぬリソースの消費**といった実用的な問題解決をする上で重要な役割を担っています。

## ソケットの一意性
ここまでソケットのライフサイクルを見てきましたが、OSは実際に多数の同時接続をどのように区別し、管理しているのか気になりますよね。

全てのソケットは以下の **5つのタプル** によって一意に識別されます。
- *プロトコル* (TCP / UDP)
- *送信元IPアドレス*
- *送信元ポート番号*
- *宛先IPアドレス*
- *宛先ポート番号*

この組み合わせにより、それらがすべて同じリモートサーバーとポートと通信していても、**OSは複数の同時接続を区別することができます**。

### Unixドメインソケットについて
ソケットにはもう1つの重要なタイプがあります。
それは『 **Unixドメインソケット（UDS）** 』というものです。

**`UDS`** はネットワークソケットではなく、**同じホスト上のプロセス間通信（`IPC`: Inter-Process Communication）** に使用されます。
IPアドレスとポート番号を使用する代わりに、UDSはファイルシステム上のファイルパス、例えば `/tmp/app.sock` のような拡張子に`.sock`を含んだファイルパスをアドレスとして使用します。(※ `.sock`がないこともあります)

<img width="700" src="https://img2020.cnblogs.com/blog/1006507/202012/1006507-20201214205003088-762539292.png">

> 引用：[Getting Started With Unix Domain Sockets](https://medium.com/swlh/getting-started-with-unix-domain-sockets-4472c0db4eb1)

`UDS` は、通信で通常必要となるIPルーティングやプロトコル処理（OSI参照モデルのレイヤー3以降）を完全にバイパスします。
そのため、ネットワークソケットよりも格段に**高速で効率的な通信** を実現します。
実際、`PostgreSQL` や `Redis` のような高性能アプリケーションでは、パフォーマンスとセキュリティを重視するローカル通信において、UDSをデフォルトで使用するケースが多いです。

# 暗号化の重要性
ここで重要な注意点として、**ソケットは本質的に安全ではない** ということです。
これは、明示的に暗号化されない限り、生のデータをそのまま送信してしまいます。

ソケットを介した安全な通信は、通常、**TLS**(Transport Layer Security)　を介して実現されます。`TLS` は既存のソケット接続をラッピングし、その接続を介して送信されるすべてのデータが暗号化され、認証されることを保証します。
TLSを使用しない生のソケットや設定ミスのあるソケットは、**MITM(中間者攻撃)** や **パケット盗聴** の標的になる可能性があります。

# 現代のアーキテクチャによるソケットの役割
現代では、分散システムやマイクロサービスといったソフトウェアアーキテクチャが存在しますが、ソケットベースの通信は至る所で使用されています。
- **RESTful API**
    - 通常は TCP 上の HTTP を通じて実装され、マイクロサービス間通信において非常に一般的な選択肢です
- **gRPC**
    - `HTTP/2`（TCPベース）上に構築され、双方向ストリーミングや効率的なバイナリ通信を可能にしています
- **ロードバランサー**
    - `Nginx` や `Envoy` は TCPソケット経由で L7ルーティングを行います
- **サービスメッシュ**
    - `Istio` などは `Envoy` を通じてアプリケーション層の通信を制御・監視します

- **高性能システム**
    - HFT(高頻度取引)やリアルタイムゲームなど、低レイテンシーが求められる場面では、`TCP / UDP` ソケット上に独自のバイナリプロトコルを直接実装することもあります

- **分散システム**
    - `Kafka` や `Cassandra` のような分散データベースやメッセージングシステムは、ノード間通信に TCP ソケットを用い、効率的なデータ同期と可用性を実現しています
    - `Redis` もクラスター構成において同様のアプローチをとります

# まとめ
本記事は、「**ソケット通信**」の基礎概念から、OSI参照モデルにおける位置づけ、TCP/UDPの違い、ライフサイクル、そして現代の分散システムやマイクロサービスにおけるその重要性までをまとめました。
ソケットが、現代のネットワークコンピューティングのまさに基盤であることに気づけたのではないでしょうか。

私自身、ソケットがどのように機能するかを理解することで、今後のシステム開発においてネットワーク通信のパフォーマンス改善やスケーラブルなバックエンドシステムの構築に役に立つと強く感じました！

# 参考
https://qiita.com/megadreams14/items/32a3eed4661e55419e1c#comments

https://envader.plus/article/27

https://ameblo.jp/bakery-diary/entry-12754672383.html#1

https://wa3.i-3-i.info/word14383.html

https://detailed.wordpress.com/2016/09/12/tcp-transmission-control-protocol/

https://qiita.com/toshihirock/items/b643ed0edd30e6fd1f14

https://medium.com/swlh/getting-started-with-unix-domain-sockets-4472c0db4eb1


[^1]:**スリーウェイハンドシェイク**
こちらは私が以前投稿した「[ブラウザでWebサイトが表示されるまでの仕組みを整理してみた](https://qiita.com/fujifuji1414/items/f9c53b451fa4890b8bfc#%E3%83%96%E3%83%A9%E3%82%A6%E3%82%B6%E3%81%A8web%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%E9%96%93%E3%81%AE%E9%80%9A%E4%BF%A1)」という記事の*ブラウザとWebサーバー間の通信*というセクションで詳しく触れているのでそちらを参考にしてください。

[^2]: **ブロッキングI/O**
入出力をやっている最中は他の処理を進めないで入出力が終わるのを待ってるI/O（入出力処理）のこと

[^3]:**select**
複数のソケットを同時に効率的に監視し、いずれかのソケットがデータ受信可能になった時点でブロッキング状態（関数が返ってこない状態）を解除し、アプリケーションに通知するシステムコール
特徴としては、管理できるファイルディスクリプタ数に上限があります。

[^4]:**poll**
select同様、ベント発生時にブロッキングを解除して通知する点は共通していますが、ファイルディスクリプタ数に厳密な上限がありません。

[^5]: **ファイルディスクリプタ**
プログラムからファイルを操作する際、操作対象のファイルを識別・同定するために割り当てられる一意の整数値