C++によるネットワークプログラミングについて

AI実装検定のご案内

C++によるネットワークプログラミングは、低レイヤで高い制御性を持つ一方、誤解や落とし穴が非常に多い分野です。

表面的に動くコードを書けても、「高負荷」「複数接続」「切断」「遅延」「セキュリティ」といった現実的条件に直面すると破綻しやすいのが特徴です。

本記事では、TCP/UDPの本質 → ソケットAPI → I/Oモデル → プロトコル設計 → 実務での注意点という順序で、
「間違いなく理解すべきポイント」に絞って整理します。

目次

ネットワーク通信の基本構造

TCPとUDPの本質的な違い

項目TCPUDP
信頼性あり(再送・順序保証)なし
順序保証ありなし
境界なし(ストリーム)あり(データグラム)
遅延比較的大きい小さい
主な用途HTTP, HTTPS, SSH, DBDNS, VoIP, ゲーム

重要な理解

  • TCPは「バイト列のストリーム」
    → メッセージ境界は保持されない
  • UDPは「1送信=1データグラム」
    → 境界は保持されるが、欠落・順序逆転はあり得る

この違いを誤解すると、設計レベルで破綻します。

C++におけるソケットプログラミングの位置づけ

C++自体は長らくネットワークAPIを標準で持っていません。

そのため実務では以下のいずれかを使います。

  • POSIXソケット(Linux / macOS)
  • Winsock(Windows)
  • 抽象化ライブラリ(Boost.Asio など)

TCPサーバの基本フロー

  1. socket()
  2. bind()
  3. listen()
  4. accept()
  5. send() / recv()
  6. close()(Windowsは closesocket()

ここで重要なのは

accept() は「新しい通信専用ソケット」を返す

という点です。

listen用ソケットと通信ソケットは役割が異なります。

OS差分の正確な理解

Linux / macOS(POSIX)

  • クローズ:close(fd)
  • エラー取得:errno
  • ヘッダ:sys/socket.h, netinet/in.h など

Windows(Winsock)

  • 初期化必須:WSAStartup()
  • 後処理:WSACleanup()
  • クローズ:closesocket()
  • エラー取得:WSAGetLastError()
  • 一般的なヘッダ:winsock2.h

クロスプラットフォーム対応を自前でやるのはコストが高く、実務では Boost.Asio 等の利用が現実的です。

ブロッキングI/OとノンブロッキングI/O

ブロッキングI/O

  • recv() はデータが来るまで待つ
  • 実装は簡単
  • 同時接続数が増えるとスケールしにくい

ノンブロッキングI/O

  • recv() は即時復帰
  • データがなければ EAGAIN / EWOULDBLOCK
  • イベント通知(select / poll / epoll / kqueue)が必要

実務上の注意

ノンブロッキングでは

  • send() が部分送信で戻る
  • send() 自体が失敗(EAGAIN)する
    という2パターンがあり得ます。

そのため「送れた分だけ進める」「送れなければ次の書き込み可能イベントを待つ」という制御が必須です。

TCPで必ず理解すべき3つの落とし穴

TCPはメッセージ境界を保持しない

send("HELLO", 5)

しても、受信側では

  • 2バイト + 3バイト
  • 5バイト一括
  • 他のデータと結合
    など、任意の形で届きます。

プロトコルレベルで境界を定義する必要がある

代表的な方法

  • 長さプレフィックス方式(例:先頭4バイトにサイズ)
  • 区切り文字方式(\n など)
  • 固定長ヘッダ + 可変長ボディ

※ HTTPも「常に長さを前置する」わけではなく、Content-Length / chunked / 接続クローズなど 複数の終端定義方式があります。

send() / recv() は部分的にしか処理しない

  • 必要サイズに達するまで 必ずループ処理
  • 戻り値を厳密にチェックする

ネットワークバイトオーダー(エンディアン)

  • ネットワークバイトオーダー:ビッグエンディアン
  • 使用関数:
    • htons, htonl
    • ntohs, ntohl

異なるアーキテクチャ間通信では必須です。

同時接続処理モデル

1接続1スレッド

  • 実装が簡単
  • スレッド数増加で非効率

スレッドプール

  • CPU利用効率が良い
  • 実装コスト中程度

イベント駆動

  • select / poll:FD数増加で線形コスト
  • epoll(Linux)/ kqueue(macOS):大量接続向き

非同期I/O(Boost.Asio)

  • OS差分吸収
  • 高性能
  • 設計思想の理解が必要

UDPプログラミングの現実

UDPは「軽い」=「簡単」ではありません。

UDPを使う場合、必要に応じて以下を自前実装します。

  • シーケンス番号
  • 再送制御
  • 順序整列
  • 輻輳制御

多少欠けても良い・低遅延が最優先という要件にのみ適します。

TLS / HTTPSはライブラリ前提で考える

TLSは単なる暗号化ではなく

  • 証明書検証
  • 鍵交換
  • SNI / ALPN
    など複雑な要素を含みます。

現実的な選択肢

  • OpenSSL(低レイヤ・高難度)
  • Boost.Asio + OpenSSL
  • libcurl(HTTPクライアント)
  • Boost.Beast(HTTPサーバ)

実務で必須の追加知識(見落とされがち)

SIGPIPE対策(Unix系)

切断済みソケットへの send でプロセスが終了する可能性あり。

MSG_NOSIGNAL 等で対策が必要。

TIME_WAIT とポート再利用

サーバ再起動時の bind() 失敗対策としてSO_REUSEADDR を検討。

Nagleアルゴリズム

低遅延が必要な場合は TCP_NODELAY を検討。

まとめ

C++ネットワークプログラミングは

  • TCP=ストリーム
  • 境界はアプリ層で定義
  • 部分送受信は常に起こる
  • I/Oモデル選択が設計の要
  • 実務ではライブラリ活用が合理的

以上、C++によるネットワークプログラミングについてでした。

最後までお読みいただき、ありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次