トップ回答者
ブロッキングソケット使用でrecv()がERROR_IO_PENDINGを返す

質問
-
お世話になっております。
Windows2012R2+VisualStudio2013+VC(MFC)を使用して
DLL(ソケットサーバー機能)を開発しております。
その中で使用しているWinsock2.0のrecv()関数が、ERROR_IO_PENDING(997)を稀に返す時があり
対応に苦心しております。
このエラーはブロッキングモードでは発生しないと考えておりましたが、このエラーについて
考えられる原因をご存知の方はいらっしゃいますでしょうか?
具体的には、DLLの公開している関数(OpenSocket()、仮名)で接続待ちスレッド(ListenThread)を起動し、
ListenThread内でbind、listen、acceptを行い、接続が確立されたら受信スレッド(ServerThread)を起動します。
recv()はそのServerThreadの中でポーリングして待ち受けています。(recvのタイムアウトは100ms)
以上、よろしくお願いいたします。
回答
-
細かいですが、Windows 95辺りからWinsockのバージョンは2.2が使用できますので2.0は稀有な存在です。バージョンを確認することをお勧めします。
BSDソケットはUNIX / Winsockに関わらずブロッキングモード/ノンブロッキングモードを持ちます。しかしWinsockに於いてはこれとは別にWindows独自のOverlapped I/O(重複IO、非同期IOとも言います)の概念があります。
両者の違いを軽く説明すると、ノンブロッキングモードとはAPI呼び出しに対して即座に応答することしか保証されていません。recv()で読み取りを行っても受信済みのデータのみが返されるのはこのためです。対してOverlapped I/OはAPI呼び出しを受けたらバックグラウンドで処理を行い完了を通知する機能を持ちます。この場合、指定されたバイト数を読み取れた時点で完了通知されます。その上で、Winsock2のsocket()関数はWinsock1.1互換のために既定でブロッキングモードでかつOverlapped I/Oが有効となっています(Default State for a Socket's Overlapped Attribute)。この場合、Overlapped I/Oに関連するエラーERROR_IO_PENDINGが発生することは正常な動作です。Overlapped I/Oを無効化したい場合はsocket()関数ではなくWSASocket()関数でソケットを作成する必要があります。
- 回答としてマーク 星 睦美 2016年11月8日 4:31
- 回答としてマークされていない piroshi164 2016年11月9日 8:51
- 回答の候補に設定 佐祐理 2016年11月10日 0:50
- 回答としてマーク 星 睦美 2016年11月17日 1:37
すべての返信
-
細かいですが、Windows 95辺りからWinsockのバージョンは2.2が使用できますので2.0は稀有な存在です。バージョンを確認することをお勧めします。
BSDソケットはUNIX / Winsockに関わらずブロッキングモード/ノンブロッキングモードを持ちます。しかしWinsockに於いてはこれとは別にWindows独自のOverlapped I/O(重複IO、非同期IOとも言います)の概念があります。
両者の違いを軽く説明すると、ノンブロッキングモードとはAPI呼び出しに対して即座に応答することしか保証されていません。recv()で読み取りを行っても受信済みのデータのみが返されるのはこのためです。対してOverlapped I/OはAPI呼び出しを受けたらバックグラウンドで処理を行い完了を通知する機能を持ちます。この場合、指定されたバイト数を読み取れた時点で完了通知されます。その上で、Winsock2のsocket()関数はWinsock1.1互換のために既定でブロッキングモードでかつOverlapped I/Oが有効となっています(Default State for a Socket's Overlapped Attribute)。この場合、Overlapped I/Oに関連するエラーERROR_IO_PENDINGが発生することは正常な動作です。Overlapped I/Oを無効化したい場合はsocket()関数ではなくWSASocket()関数でソケットを作成する必要があります。
- 回答としてマーク 星 睦美 2016年11月8日 4:31
- 回答としてマークされていない piroshi164 2016年11月9日 8:51
- 回答の候補に設定 佐祐理 2016年11月10日 0:50
- 回答としてマーク 星 睦美 2016年11月17日 1:37
-
佐祐理さん
返信が遅れ、申し訳ありません。ご回答ありがとうございました。
winsockのバージョンですがWSAStartupで2.0を指定していましたので
そちらは2.2に修正いたします。ご指摘ありがとうございます。
さて、ERROR_IO_PENDINGの件ですが、BSDソケットを使用していても
このエラーに対するハンドリングも行わないといけないという認識でよろしいでしょうか?
WSARecv()関数ではWSAOVERLAPPED構造体を指定して非同期処理の終了を監視できますが、
recv()関数にはその手段が存在しません。
この場合、どのようなエラー処理が望ましいのでしょうか?(成功するまでリトライ?)
WinSockのカーネルの中にはブロッキングソケットを使用する場合に重複I/Oが利用されていても、
OVERLAPPED構造体を指定できない関数の呼び出しに対して、
重複I/Oに対するエラーコードが返っていることになりますので、
もしかしてWinSockの中に不具合があるのではないかと思ってしまいました。
そのため、同様な経験を得られた方々とのご意見を伺えればと思っています。
上記の現象にはご説明、ご解説をいただければ幸いです。
宜しくお願いします。
-
WSAStartup()のRemarksセクションでも説明されていますが、WSAStartup()にバージョン2.0を要求した場合、指示通りに2.0で初期化されます。現行バージョンの2.2とは異なる挙動を示すかもしれません。20年以上も前の話となるためこの辺りはよくわかりません。
まずは2.2に修正しても同様の現象は発生しますでしょうか?
その上で対策は既にコメント済みです。再度引用しておきます。
Overlapped I/Oを無効化したい場合はsocket()関数ではなくWSASocket()関数でソケットを作成する必要があります。
- 編集済み 佐祐理 2016年11月10日 0:52 リンク追加
-
佐祐理さん
ご返答ありがとうございます。
当現象での再現性ですが、ばらつきこそあるものの常時通信で3週間ほど連続稼働させて発生するかどうか程度の頻度です。
2.2で再現するかどうかの確認には、長い時間を必要とする可能性がありますが検証を進めてまいります。
対策法については、最初にいただいたレスで承知しております。
そちらに対してのお礼が遅れてしまい、申し訳ありませんでした。
当スレッドの主旨を、BSDソケット関数で、重複I/Oに起因するエラーが返って来ることの是非や、
そのエラーハンドリングについてということにさせていただきます。
以上、よろしくお願いいたします。
-
フォーラム オペレーターの星 睦美です。piroshi164 さん、こんにちは。
質問の参考にしていただいた佐祐理 さんの返信に私から[回答としてマーク] させていただきました。
少し時間が経ってしまいましたので既に解決されているかも知れませんが、「BSDソケット関数で、重複I/Oに起因するエラーが返って来ることの是非」に関してフォーラム ユーザーからのアドバイスが必要な場合には新しい質問として投稿いただければと思います。
今後ともフォーラムをよろしくお願いします。フォーラム オペレーター 星 睦美 - MSDN Community Support