localhostでTCPには「メッセージ境界」がないことを観察する

ネットワーク・RFCカテゴリを表すパンダのイラスト ネットワーク・RFC

この記事について
この記事は、生成AIを活用した自動生成フローで作成しています。RFC 9293とMicrosoft LearnのTcpClient公式資料を確認し、localhostでTCPのbyte streamを観察しながら、実プロトコルで必要な「区切り」の設計まで整理しています。

検証ステータス:📘 公式仕様確認済み・実行未検証

TCPでは、送信側が Write を2回呼んでも、受信側が2回に分かれて受け取れる保証はありません。TCPが扱うのはメッセージの列ではなく、順序付きのbyte streamだからです。

この違いを知らないと、「たまにJSONが途中で切れる」「2件のデータがくっつく」といった不具合につながります。

まず試す

PowerShellを2つ開きます。

受信側:

$l = [Net.Sockets.TcpListener]::new(
    [Net.IPAddress]::Loopback,
    50555
)

$l.Start()
$c = $l.AcceptTcpClient()
$s = $c.GetStream()

$b = New-Object byte[] 1024
$n = $s.Read($b, 0, $b.Length)

[Text.Encoding]::UTF8.GetString($b, 0, $n)

$s.Dispose()
$c.Dispose()
$l.Stop()

送信側:

$c = [Net.Sockets.TcpClient]::new('127.0.0.1', 50555)
$s = $c.GetStream()

foreach ($x in 'HELLO', 'WORLD') {
    $b = [Text.Encoding]::UTF8.GetBytes($x)
    $s.Write($b, 0, $b.Length)
}

$s.Dispose()
$c.Dispose()

受信側で HELLOWORLD と見える場合もあれば、1回のReadでは途中までしか取れない場合もあります。

送信回数と受信回数は対応しない

sequenceDiagram
  participant C as Client
  participant T as TCP byte stream
  participant S as Server
  C->>T: Write "HELLO"
  C->>T: Write "WORLD"
  T-->>S: Read "HELLOWORLD" のこともある
  T-->>S: Read "HEL" + 次のReadで残り のこともある

TCPが保証するのは「順序」や「信頼性」であって、アプリが行ったWriteの境界ではありません。

bufferを3byteにしてみる

受信bufferを3byteへ縮めると、1回のReadでは全体を取れないことが目で分かります。

$b = New-Object byte[] 3

この実験で、「Readしたら1メッセージ完成」という設計が危険だと分かります。

実プロトコルではどう区切るか

代表的な方法は3つあります。

方法特徴
区切り文字改行まで1件テキスト向き
固定長常に128byte単純だが柔軟性低め
length prefix先頭4byteに本文長バイナリ/API向き

たとえば改行区切りなら、Readするたびに文字列化して終わりではなく、受信bufferへためて「改行が来たところまで」を1件として取り出します。

仕事でどう使えるか

  • TCPソケット通信

  • 独自プロトコル

  • 固定長電文

  • 機器通信

  • TCP上で流れるJSONやCSV

  • 古い業務システムとの連携

「テスト環境では毎回1回で受信できた」だけでは、正しい実装の根拠になりません。通信量、OS、ネットワーク状況、送信タイミングが変わると分割単位も変わり得ます。

注意点

  • Read() の戻り値 n は実際に受信したbyte数

  • bufferサイズいっぱいまで必ず埋まるわけではない

  • Read() = 0 は接続終了を示す重要な状態

  • UTF-8文字は複数byteなので、文字境界も考慮が必要

  • 本番プロトコルでは最大メッセージ長を決める

まとめ

  • TCPはbyte streamであり、メッセージ境界を保持しない

  • Write回数とRead回数は一致しない

  • アプリ側で区切りルールが必要

  • 改行、固定長、length prefixなどを使う

  • Readの戻り値と接続終了を正しく扱う

公式情報・一次情報

文書情報

記事タイトル
localhostでTCPには「メッセージ境界」がないことを観察する
作成日
更新日
Source URL
https://papanda925.com/?p=15399

ライセンス: 本記事のうち、当サイトが権利を有する本文・自作図表は、特記なき限り CC BY 4.0 で利用できます。生成AIを活用して作成・編集した内容を含みます。コードについて、別途ライセンス表示またはリンク先GitHubリポジトリのライセンスがある場合は、その条件を優先します。引用・第三者資料・画像・商標等は本ライセンスの対象外です。 利用ポリシー

タイトルとURLをコピーしました