UDPで送ったデータが、受信側では途中までしか見えない。次の受信で続きを読めると思って待つと、別のデータが返ってくる。こうした問題を調べるには、ネットワークの欠落と、受信処理が用意した領域の不足を分ける必要があります。Linuxの通常のUDP受信を例に、メッセージの境界と切り詰めの検出を整理します。
簡単にまとめると?
- 通常のUDP受信はデータグラム単位です。受信先の領域が小さいと、入らなかった末尾は失われ、次回の受信で同じデータグラムの続きを読めるわけではありません。
- Linuxのrecvmsgでは、結果のmsg_flagsにあるMSG_TRUNCが切り詰めを知らせます。受信時に指定する同名のフラグとは、役割を分けて読みます。
- 一部の内容が届いたことを、完全なメッセージの受信成功と扱わないことが大切です。長さ・切り詰めの有無・通信規約の条件を合わせて判定します。
バイト列を少しずつ読む場合との違い
データグラムは、ひとまとまりのメッセージとして扱う通信の単位です。ここでは、UDP_GROなどの受信オフロードを使わず、MSG_PEEKによる覗き読みもしない通常の受信を考えます。LinuxのUDPでは、一回の受信で一つのデータグラムを取り出します。
たとえば、ABCDEFという6バイトのデータグラムと、XYという2バイトのデータグラムが、この順で受信待ちになったとします。最初の受信に用意した領域が4バイトなら、ABCDEFのうちABCDしか収まりません。この受信で末尾EFは切り捨てられるため、次の受信はEFではなく、次のデータグラムXYを扱います。
これに対し、同じ文字列ABCDEFとXYを単なる連続したバイト列として読む独自モデルでは、4バイトずつの読取りをABCD、EFXYと表せます。後者の考え方をUDPへ持ち込むと、「次に読むと続きが来る」という前提が崩れます。この比較は、通信速度や実際のTCP受信回数の予測ではありません。

計算では、この二つの取り出し方を別々の関数にし、UDP型の一回目で切り詰めあり、二回目で切り詰めなしになることを確かめています。送受信の実測はしていません。また、UDP自体は配達・順序・重複の排除を保証するものではありません。図は受信待ちになった後の取り出し方だけを切り出したものです。
MSG_TRUNCは、入力と出力を区別する
Linuxのrecvmsgは、受信した内容に加えて、msghdrという構造体のmsg_flagsへ結果のフラグを返します。ここにMSG_TRUNCが立っていれば、データグラムが用意した領域より大きく、末尾が捨てられたことを示します。
一方、recvmsgなどを呼ぶ際のflags引数にも、同じ名前のMSG_TRUNCを指定できます。Linuxの対応するデータグラムソケットでは、実際にコピーできた長さではなく、元のデータグラムの長さを戻り値で知るための指定です。同じ名前でも、「結果として切り詰めを知らせる印」と「呼出し時に長さの返し方を指定する指示」を区別します。
この入力フラグを使って6という長さが返っても、4バイトの領域に6バイトが保存されたという意味ではありません。コピーされた内容と元の長さを混同して、領域の外まで読み進めないようにします。また、長さが分かっただけで、既に捨てられた末尾を回復できるわけでもありません。
領域の長さと受信した長さが同じ、という情報だけでも判断は足りません。今回の4バイト領域には、ちょうど4バイトのメッセージも、もっと大きなメッセージの先頭も収まります。結果の切り詰めフラグを確認できるAPIを選ぶと、この二つを区別できます。言語のラッパーがどの情報を公開するかも確認対象です。
ゼロバイトと待ち状態も、同じ意味にしない
UDPでは、長さがゼロのデータグラムを受信する場合があります。したがって、受信結果の0を見ただけで、ストリームの正常終了と同じ意味だと決めることはできません。ソケットの種類と、その呼出しで指定した条件を一緒に読みます。
受信待ちのデータがない非ブロッキング操作では、EAGAINやEWOULDBLOCKというエラー条件もあります。これは、正常に受け取った空のメッセージとは別です。アプリケーションの状態表には「完全なデータグラム」「切り詰め」「空のデータグラム」「今はデータなし」を分けて置くと、再試行や破棄の理由が曖昧になりにくくなります。
受信先の領域を増やすだけで解決するか
今回不足していたのは、アプリケーションが一回の受信に渡した領域です。カーネル側で受信待ちのデータを蓄える容量や、経路上で扱えるパケットの大きさとは、同じものではありません。一つの設定値を大きくして、三つの問題がまとめて解消すると考えないようにします。
受信側での比較材料は、通信規約が許す最大長、その長さを収める領域、切り詰めた際の扱いです。部分的なJSONや固定長の計測記録を通常のデータとして処理する前に、完全なメッセージだったかを確かめます。再送が必要なら、相手へ何を要求するか、同じメッセージをどう識別するかも通信規約に含めます。
MSG_WAITALLを使えば複数回分をつないで埋められる、という解決策もUDPには当てはまりません。Linuxの資料では、この指定はデータグラムソケットに効果がないとされています。受信方法の契約を変えずに、バッファだけを満たす発想へ寄せないことが大切です。
なお、UDP_GROを明示的に使う構成では、複数データグラム分をまとめた領域と、区切りを表す補助情報を受け取る場合があります。本稿の通常受信モデルを、その設定へ無条件に広げることはできません。まず有効なソケットオプションを確認し、その上でアプリケーションが受け取る単位を追います。
出典・参考資料
- Linux man-pages 6.19 — udp(7):DESCRIPTIONの通常受信・切り詰め、Socket optionsのUDP_GRO。
- Linux man-pages 6.19 — recv(2) / recvmsg(2):flags引数、recvmsgのmsg_flags、RETURN VALUE。掲載HTMLを2026年10月22日確認。文字列の比較と図は本稿の独自モデル。
