sendとrecv関数によるソケット送受信のタイムアウト完全ガイド
Windowsソケットプログラミングではsendとrecvが同期I/Oの代表格として広く使われています。デフォルトでは送信バッファに空きができるまでsendが止まり、到着データが存在するまでrecvがブロックされるため、ネットワーク状態の悪化や相手側のハングアップがそのまま応答不能に直結します。GUIを持つクライアント側はもちろんのこと、長時間稼働するサーバーでも、ユーザー操作性を保つためにタイムアウトの設計は避けて通れません。 Createiocompletionportdei O Wan Lepotowo Shi Zhuangsuru.html.
ソケットAPIがブロックする振る舞いは、デバッグ時における利便性と表裏一体です。テスト環境では一瞬で返ってくる処理が、本番のWAN経由や不安定なモバイル回線では数秒から数十秒の遅延に化けることも珍しくありません。タイムアウトが設定されていないと、再接続を開始するトリガーすら引けず、アプリケーション丸ごとが応答待ちで停止します。
タイムアウトを実装する経路は複数用意されています。setsockoptでソケット単体にSO_RCVTIMEOやSO_SNDTIMEOを適用する直接的な手法、fd_setとselectを組み合わせて複数ソケットを一括監視する古典的手法、WSAEventSelectとWSAWaitForMultipleEventsでイベント駆動に昇格させる手法、そしてioctlsocketでノンブロッキング化しWSAGetLastErrorでリトライを判定する方法があります。いずれを選ぶかは、同時接続数やコードベースの規模、運用要件に依存します。
以下ではそれぞれの方式の仕組みと特徴を紹介し、実務で遭遇しやすい落とし穴にも触れていきます。特性を俯瞰できる早見表も併せて掲載していますので、設計の初期段階で比較検討にお使いください。
| 方式 | 設定粒度 | 導入コスト | タイムアウト精度 | 向いている用途 |
|---|---|---|---|---|
| SO_RCVTIMEO / SO_SNDTIMEO | ソケット単位 | 低 | ミリ秒 | 単体接続のクライアント |
| select + timeval | 複数ソケット | 中 | 100ナノ秒 | 中規模サーバー |
| WSAEventSelect + WSAWaitForMultipleEvents | 複数ソケット | 高 | ミリ秒 | イベント駆動の中〜大規模 |
| ノンブロッキング + WSAGetLastError | ソケット単位 | 中 | システム依存 | 高頻度の単一ソケットI/O |
| I/O完了ポート | 大量ソケット | 高 | システム依存 | 高性能サーバー |
SO_RCVTIMEOとSO_SNDTIMEOで個別ソケットに設定する
最もシンプルなのがsetsockoptを使う方法です。SO_RCVTIMEOはrecv側、SO_SNDTIMEOはsend側に対応するタイムアウトで、DWORD型のリミット値をミリ秒単位で指定します。設定以降の該当I/Oすべてに作用し、recvがSOCKET_ERRORで返った際はWSAGetLastErrorがWSAETIMEDOUTになります。
DWORD tm = 5000; // 5秒
setsockopt(s, SOL_SOCKET, SO_RCVTIMEO,
reinterpret_cast<const char*>(&tm), sizeof(tm));
setsockoptの第四引数にはDWORD変数のポインタを、第五引数にはsizeof(DWORD)を渡します。accept直後にこのフローを挟めば、ソケット単位で異なるタイムアウトを柔軟に設定できます。Windows Socketsのヘッダーで定義された定数なので、追加のライブラリ参照は不要です。
この方式は手軽である反面、ソケットごとに重複したコードが必要で、大量のコネクションを扱うサービスでは煩雑になりがちです。selectやイベントオブジェクトによる多重監視と比べて、他のソケットの状況を参照せず個々のrecv完了だけを判定するため、ポーリングループに陥りやすい点にも留意してください。
select関数を使った多重監視でタイムアウトを仕込む
複数のソケットを束ねて監視したいときは、古くから使われてきたselect関数が有効です。timeval構造体にtv_secとtv_usecを指定すれば、すべてまとめて一つのタイムアウト値を設定できます。readfdsとwritefdsに監視対象を入れることで、送信バッファの空きと受信データの到着を同時に判定可能です。
selectは呼び出しごとにfd_setを再構築する必要があるため、ループごとにオーバーヘッドが発生します。タイムアウトをゼロにすれば事実上のポーリング、NULLを渡せば永久待ちとなるため、用途に応じた値を選んでください。応答可能なソケットのみをrecvに渡せばブロックせずに処理を前進させられます。
TCPサーバーやNAT越えが必要なチャット系プロトコルでは、1スレッドで数十セッションを扱う構成が現実的であり、selectの単純さは今も十分な選択肢です。ただしfd_setの上限は既定では64で、FD_SETSIZEを増やしても性能は線形には伸びないため、大規模化を見据えるなら次のセクションの手法に移るほうが得策です。
WSAEventSelectとWSAWaitForMultipleEventsでイベント駆動にする
ソケットごとにイベントオブジェクトを割り当て、WSAEventSelectでFD_READやFD_WRITEを紐づける設計は、Windowsネイティブらしいイベント駆動I/Oの代表例です。WSAWaitForMultipleEventsにタイムアウト値を渡せば、いずれかのソケットがI/O可能になるまでスレッドを待機させ、falseで返ったケースを期限切れとして検出できます。
FD_READがトリガーされたソケットはWSAResetEventのあとでrecvを実行し、戻り値ゼロを確認したら切断処理へ進めます。タイムアウト時は明示的にループの先頭へ戻り、次のリクエストを待ち受ける形が定石です。スレッドを占有せずに多数のソケットを捌ける反面、記述量は増えるため、ロジックのカプセル化を意識しておくと保守が楽になります。
さらに先を見据えるなら、より高機能なI/O完了ポートへ踏み込む価値があります。CreateIoCompletionPortでI/O完了ポートを実装するでは、ソケットとオーバーラップド構造体を関連付けてキューで待機する方法を扱っており、本記事の内容を足がかりに読み進めると理解が深まります。
ノンブロッキングソケットを使ったrecv制御
ioctlsocketにFIONBIOを渡してノンブロッキングモードへ切り替えると、recvはデータがない場合に即座にSOCKET_ERRORで返り、WSAGetLastErrorからWSAEWOULDBLOCKを取得できます。タイムアウトをミリ秒精度に拘りたい場合に有効で、待機スレッドを起こす高頻度クライアントと相性が良いです。
実装では通常、RecvEx相当のラッパー関数を作ってループさせ、一定時間が経過したら強制的にSOCKET_ERRORとWSAETIMEDOUT相当の値を返します。selectと異なりfd_setの制限がなく個別に管理できるため、ゲームや金融系の低遅延プロトコルで採用されるケースが目立ちます。ただしCPUを回しっぱなしにしないためのスリープやWin32メッセージポンプとの協調に注意を払う必要があります。
この方式はI/O完了ポートや別スレッド構成と相補的に組み合わせることが多く、サブプロセス起動を伴う用途では設計の段差が見えてきます。CreateProcessで子プロセスを起動する方法で触れられているような、子プロセスとの通信にソケットを用いるパターンを扱う際は、本記事のrecvタイムアウト設計がそのまま転用できます。
タイムアウト実装で失敗しがちなポイント
戻り値の判定を怠ると、タイムアウトが起きたこと自体に気づかないバグになります。recvがSOCKET_ERRORで返った場合はWSAGetLastErrorを取得し、WSAETIMEDOUTであればユーザーへの通知や再送ロジックへ分岐させてください。selectの戻り値0も同じように扱う必要があります。
タイムアウト値の設定場所も検討項目です。acceptで詰まっているのか、その後のrecvで詰まっているのかで切り分けなければ、問題箇所を誤認したままになります。ConnectExなど専用APIではそれぞれ独自のタイムアウト指定があるため、Winsockのリファレンスで個別に確認することをおすすめします。
リソースリークへの配慮も忘れてはなりません。タイムアウト発生時にソケットやイベントオブジェクトを確実に解放し、次のリクエストでも同じクリーンアップ経路を通れるよう設計してください。デバッガのハンドルカウントを監視しながらテストを繰り返せば、実装の品質は大きく向上します。
今回紹介したように、タイムアウト制御はソケットプログラミングの根幹を支える仕組みです。プロトコルの性質と運用要件を踏まえて方式を選び、まずはSO_RCVTIMEOから試して、必要に応じてselectやI/O完了ポートへと段階的に進化させていくのが、実装と保守を両立させる堅実な進め方です。詳細や周辺技術についてはhozugawa.netの関連ドキュメントにもまとめられていますので、設計フェーズの参考資料として広く役立てられます。