ソケット通信を行う際に高水準なファイル入出力関数(fgets/fprintf等のFILE構造体を渡す関数)を利用する サンプル用 C プログラムを書きました。

https://gist.github.com/kosh04/3772420/d2192618765898413f15c9c08107ece3172711c8

上記リンクのソース一式を git clone で落としてきて make コマンドを叩けば Echoサーバ(echo-server) と HTTPサーバ(http-server) という2つのプログラムが作成されます。Ubuntu/macOS/Win32(mingw-w64) など複数環境で動作確認済。

ここからが問題で、server.c 148-150行目 では Winsock2 のソケット記述子をファイル記述子と関連付けする処理を行っています。

int h = _open_osfhandle(accept_socket, _O_RDONLY|_O_BINARY);
r = _fdopen(h, "rb");
w = _fdopen(h, "wb");

このように読み込み用の変数 r と書き込み用の変数 w を分けた場合は後続のサーバ処理は期待どおりに動作するのですが、この部分をコメントアウトしてある 151 行目のように読み書き用の変数を一つにまとめた書き方にすると入出力の挙動がおかしくなってしまいました。

r = w = _fdopen(_open_osfhandle(accept_socket, _O_RDWR|_O_BINARY), "r+b");

具体的には以下のような動作になります。クライアント側の出力がぐちゃぐちゃで、サーバ側でも書き込みエラーが発生していることがわかります。(シェルは MSYS2-mingw64 を使用)

(サーバ側) $ make echo-server
(サーバ側) $ ./echo-server.exe
listening :::1234.
accept ::1:7103.
write 2 byte, but expect 3 byte: '12\n'
write 2 byte, but expect 3 byte: '23\n'
write 6 byte, but expect 7 byte: '123456\n'
write 6 byte, but expect 7 byte: '234567\n'

(クライアント側) $ cat testdata.txt (改行は'\n'のみ)
1
12
123
1234
12345
123456
1234567
12345678
123456789
(クライアント側) $ ncat localhost 1234 < testdata.txt
1
1223234
12345
1234562345672345678
123456789

Linux (unix) 環境では server.c 153 行目 r = w = fdopen(accept_socket, "r+b"); のように「ストリームの読み書きを行うフラグ r+b」を指定すれば問題なく動作している(ように見えるだけ?)こともあって、Windows 環境でも多分動作するだろうと考えていたのですが、結果は上記の通りダメでした。

質問

タイトルの通り、Windows/Winsock2 においてソケットを高水準ファイル入出力関数から扱いたい場合に、読み書き用の FILE * 変数を一つだけにすることは可能でしょうか?それとも素直に読み書きの変数を分けて書くべき?