2012年4月4日

TCP socketではwriteの後すぐにcloseしてはいけない

TCP socketではwriteの後すぐにcloseしてはいけない。 相手側に全てのデータが届いてからcloseする必要がある。 shutdown で書き込み側だけハーフクローズするとよい。 相手側がcloseしてから、こちらをcloseする。相手側がcloseしたことは、readを呼んでブロックさせておくと、読み込みバイト数==0 つまりEOFになったことでわかる。
12
koba @tetsu_koba

TCPのsocketでwriteの後にすぐcloseしていると、読み込み側が最後までデータを読めないことがある。sockoptのSO_LINGERは効果なし。closeの前に2秒sleepすればうまくいく。こんなんでいいのか??

2012-04-03 14:05:12
Yasuo Itabashi @yitabashi

@tetsu_koba キューの中身を送信前に、キューを破棄してしまうのが原因だと思うので、ioctlでSIOCOUTQを見てキューの送信残が0になるのを待つと良いかと。

2012-04-03 14:08:29
koba @tetsu_koba

TCPsocketのwriteの後にすぐcloseするのでなく、shutdown(sock, SHUT_WR); にして、readで読み込み側がcloseしてEOFになるのを確認してから、書き込み側をcloseするようにした。これでいいのかな??

2012-04-03 14:09:03
Tatsuo Nagamatsu @nagamatu

@tetsu_koba socketに対して shutdownを使ってみたら?

2012-04-03 14:18:40
koba @tetsu_koba

@yitabashi ありがとう。ぐぐるといろいろ違うことが書いてあるけど鉄板のやりかたは無いってことなのかな。ioctlでSIOCOUTQはLinux固有らしい。今は対象はLinuxだからいいけど。

2012-04-03 14:19:29
koba @tetsu_koba

@nagamatu ありがとう。とりあえずそれでうまくいってます。

2012-04-03 14:23:22
koba @tetsu_koba

TCPってエラーフリーのはずなのに!ってみんな一度は通る道なのかな。実際にやってみるまでは知らなかった。

2012-04-03 14:28:51
koba @tetsu_koba

TCP sokcetでwriteでデータ書き込みしても、相手側にそれが届くまでcloseしてはいけない。shutdown(sock, SHUT_WR)で、これ以上書き込まないことを相手側に通知できる。相手側がcloseしたことはreadで0が返ってくるのでわかる。

2012-04-03 14:36:24
Yasuo Itabashi @yitabashi

@tetsu_koba ノンブロックソケットにsendfile()で書き出すときも、先の方法でキューが空であることを確認してからでないとデータを上書きして壊してしまいます(2.6.18で確認。多分まだ直ってない)。バッファから吐き出した時点で書込完了通知が上がってるのが原因かと。

2012-04-03 14:39:21
koba @tetsu_koba

TCPで最後のデータを受信できないのを回避する方法。 / “The ultimate SO_LINGER page, or: why is my tcp not reliable” http://t.co/kdIlBAye

2012-04-03 14:45:01
koba @tetsu_koba

sockopt SO_LINGER が効果がなかったのが納得できないけど、shutdownでハーフクローズにするのは腑に落ちた。

2012-04-03 14:56:14
koba @tetsu_koba

popen(3)を使って実験したけど、その後にpipe(2), fork(2), dup2(2), execv(2) で書き直した。

2012-04-03 17:53:32
koba @tetsu_koba

今日はTCP socketのcloseとpipeの使い方を学んだ。経験値がアップした。

2012-04-03 18:07:39
koba @tetsu_koba

@AkiraTsukamoto ありがとうございます。その本さがしてみます。

2012-04-03 20:51:21
koba @tetsu_koba

もしかして、TCPのsocketをreadでブロックさせておくと相手側が切断したことを監視するのに使えるのか?別に何もデータは来ないけど切断されたらreadがEOFで戻ってくるから、そしたらこっちのプロセスも終了。

2012-04-04 10:41:36
鯉江 @koie

@tetsu_koba select()で待ってreadでもいいです。ただ相手が突然死した場合にはFINパケットが来ないのでこっちからなにか投げてやる必要があるかも。KEEPALIVE onにするか自分でpingするか。

2012-04-04 10:45:16
koba @tetsu_koba

TCPで接続したあと、何も通信するデータがない状態で、クライアント側が終了してもサーバ側がそれに気がつかずに、次にwriteするまでずっとプロセスが無駄に残ってしまうという問題があったけど、これで解決できるかも。

2012-04-04 10:45:47
koba @tetsu_koba

@tomo_watanabe HTTPのレベルじゃなくて、生のTCPソケットでやってる。

2012-04-04 10:48:06
koba @tetsu_koba

@koie 物理的にケーブルが抜けた場合とか、相手側のカーネルが後始末をしない場合はreadのEOFだけだと切れたことに気がつかないということですかね。

2012-04-04 10:49:50
鯉江 @koie

@tetsu_koba メッセージパッシングの限界ですね。これを超えるには物理レイヤーの情報がないと。

2012-04-04 11:00:37
koba @tetsu_koba

@koie 原理的には相手は地球の裏側にあって、途中に無数のノードを経由しているかもしれないですからね。:) 明示的に終了処理されなければ、切断されたのか、時間がかかっているだけなのか判断できないと。

2012-04-04 11:07:41

コメント

koba @tetsu_koba 2012年4月4日
@AkiraTsukamoto さんが紹介してくれた書籍。 "Unix Network Programming, Volume 1, W. Richard Stevens, second edition" これにSO_LINGERが効果が無いことが書かれているそうです。
0
koba @tetsu_koba 2012年4月4日
TCP/IPの通信の切断検知には sockoptのSO_KEEPALIVEなどを使用する。 http://d.hatena.ne.jp/iww/20081030/setsockopt
0
koba @tetsu_koba 2012年4月4日
「ソケットを正しく閉じるにはどうすればよいのですか?」 http://www.kt.rim.or.jp/~ksk/sock-faq/unix-socket-faq-ja-2.html#close
0
koba @tetsu_koba 2012年4月4日
writeしてshutdown(sock, SHUT_WR); の後にEOFをreadで待つところは、selectと組み合わせてtimeoutを指定するべきですね。
0
koba @tetsu_koba 2012年4月4日
サーバが書き込み側、クライアントが読み取り側のとき、サーバ側で(selectを使って)socketをreadしておくとクライアントのソフトを強制終了させたときに、readからECONNRESTが返ってきて、サーバ側のプロセスを終了させることができました。これでサーバ側のプロセスが無駄にたまってしまう問題も解決できました。
0
齊藤明紀 @a_saitoh 2012年4月5日
特定OSのTCPのバグじゃないの?write直後にcloseしてる既存ソフトはソケットが導入された4BSD時代からいっぱいあって、問題なく動いてきてる。
0
齊藤明紀 @a_saitoh 2012年4月5日
RFC793: In this case, a FIN segment can be constructed and placed on the outgoing segment queue. (略) All segments preceding and including FIN will be retransmitted until acknowledged.
0
Tsuyoshi CHO @tsuyoshi_cho 2012年5月5日
Linuxのioctlだし、この命令入れた時点で挙動が変ったのかな...?
0