SocketChannelが閉じれないことがある話 と 暫定対策
おもいっきりハマったので自メモ)
結論的にはこういう話)
http://twitter.com/kimukou2628/status/268656976647577601:twitter:detail:right
とりあえずAndroidでNanoHTTPDをnioベースに書き換えたことはあるけれど、まあそこまで詳しくはないな。
2012-11-14 20:25:35 via web
@alterakey そのサーバって@sakura_bird1 さんも呟かれていたような(汗。そこら辺の実装みればわかる感じだったりしますか?
2012-11-14 20:31:55 via hamoooooon to @alterakey
@kimukou2628 @alterakey その呟きはアルターさんに教えていただいたものです〜。ちなみにお二人の高度な話についていけてませんww
2012-11-15 00:45:33 via web to @kimukou2628
@sakura_bird1 いえいえ。そんな難しい話ではなく URL なクライアント通信処理を NIOという形式 URL に直すと通信が高速になるのですが開閉を繰り返すと上手く閉じない話 @alterakey
一度で閉じなければ再帰で閉じろって感じで・・。*1
そもそもソケット切断をトリガーにするサービスっていやん><。
NIOあたりの資料)
櫻庭さんの資料
- http://itpro.nikkeibp.co.jp/article/COLUMN/20060403/234326/
- http://itpro.nikkeibp.co.jp/article/COLUMN/20060410/234874/
- http://itpro.nikkeibp.co.jp/article/COLUMN/20060417/235453/
- http://itpro.nikkeibp.co.jp/article/COLUMN/20060424/236102/
- http://itpro.nikkeibp.co.jp/article/COLUMN/20060508/237029/
- http://itpro.nikkeibp.co.jp/article/COLUMN/20060515/237871/
追記)
上記で上手く言ったと思ってたんだけど、ダメやった。。。
- 普通に接続するだけなら
InetSocketAddress isa = new InetSocketAddress(”XXX.XXX.XXX.XXX”, 8000); java.lang.System.setProperty("java.net.preferIPv4Stack", "true"); java.lang.System.setProperty("java.net.preferIPv6Addresses", "false");
channel = SocketChannel.open(); //channel.configureBlocking(false); channel.socket().setSoTimeout(3000); if(!channel.connect(isa))return false; while(!channel.finishConnect());
- 切断は
if(channel==null)return channel.close() channel=null
をExceptionが出なくなるまで繰り返せば
切断接続の繰り返しでも再接続OKでしたが
一度でも読み書きの通信するとダメで、読込、書込バッファをフラッシュしてからじゃないとダメらしい。*2
close()自体はExceptionも出ずに通過してるんだけどな・・*3
if (channel == null || !channel.isOpen())return; Socket sc = channel.socket(); sc.shutdownInput(); //★ sc.shutdownOutput();//★ sc.close();
でもまあ
sakura_bird1さん興味をもつ幅がすごいよな(汗
参考にしたサイト1)
- Socketクラスを使用してソケット通信をする « Tech Booster
- J2SE, v1.4 の新機能 New I/O SocketChannel と Selector
- 5. ノンブロッキングチャネル | TECHSCORE(テックスコア)
- SocketChannel (Java Platform SE 6)
追記2)
更に補足コメントいただきました。ありがとうございます。
終了だ…
@alterakey 昨日は色々とありがとうございました。一応最終的な結果はまとめておきました URL @sakura_bird1
2012-11-15 21:46:12 via YoruFukurou to @alterakey
@kimukou2628 @sakura_bird1 詳しく拝読させていただきました。やはりFIN (shutdown) の問題でしたか… 自分の場合メッセージが小さかったので気づけなかった感じですね (汗) SO_LINGER=0にすれば強制closeできると思いますよ :-)
2012-11-16 00:18:19 via web to @kimukou2628
@alterakey @sakura_bird1 補足コメントありがとうございます。SO_LINGERの使い方調べてみて初めてわかりました URL そういう目的で使うものなのですね。。
2012-11-16 01:01:24 via YoruFukurou to @alterakey
@kimukou2628 @sakura_bird1 LINGER=0にしておくとどちらかがcloseした時に即座にRSTが飛ぶようになる(=Connection reset by peer)ので、shutdownしないでcloseするとFINするまでのデータがロストします…
2012-11-16 01:09:31 via web to @kimukou2628
@kimukou2628 @sakura_bird1 また、今回はcloseができない?ということなのでTIME_WAITがどうこうというのは別な気がしています。すみません。書かれていた通りのshutdownか、無理やりにでも閉じたいならLINGER=0でFAかと… ^^;
2012-11-16 01:13:27 via web to @kimukou2628
@kimukou2628 @sakura_bird1 この辺はかなり錆びついている知識なので間違っているかも (汗) 健闘をお祈りします :-)
2012-11-16 01:20:29 via web to @kimukou2628
@kimukou2628 @sakura_bird1 おお、それは良かったです ;-)
2012-11-16 01:56:52 via web to @kimukou2628
channel = SocketChannel.open(); Socket sc = channel.socket(); sc.setSoTimeout(3000); sc.setSoLinger(true, 0); //◯ sc.setReuseAddress(true); //◯ if(!channel.connect(isa))return false; channel.configureBlocking(false); while(!channel.finishConnect());
修正にあたっての参考サイト1ー2)
- Socket作成で「Address already in use: connect」が発生する件の続き - うなの日記
- Reading data from a SocketChannel (Sockets and Internet Protocols forum at Coderanch)
configureBlocking(false) に関しては connect後にするまで落ちてた><。
呼ぶタイミングもやはり大事。。
追記3)
で、殆どの機種では上記の対応でいくのだが。。
いつも苦しめられる ARROWS X LTE F-05D 様に苦しめられることに・・・。
まず
- timeoutが3秒ではなく30秒ぐらい設定しないとすぐtimeoutで自然に切れる><。
channel = SocketChannel.open(); Socket sc = channel.socket(); sc.setSoTimeout(30000); //△ sc.setSoLinger(true, 0); sc.setReuseAddress(true); if(!channel.connect(isa))return false; channel.configureBlocking(false); while(!channel.finishConnect());
- ソケットclose関数時 に★をtry-catchで囲まないと盛大に落ちる>< *4。
if (channel == null || !channel.isOpen())return; Socket sc = channel.socket(); try{ sc.shutdownInput(); //★ }catch(Exception ex){} try{ sc.shutdownOutput();//★ }catch(Exception ex){} sc.close();
- channel.read(buffer); なバッファで取得した文字列を判定処理するところに時間がかかりすぎるとtimeoutで自然に切れる。たまに落ちる
- 大量にデータ受信しまくると発生。。。
- とりあえず別スレッド(ExecutorService)に判定処理自体を逃した。。。
IS03やGNとかは全然問題ないので、なんだまたこの機種かよ。。な感じ
なん何でしょうかね・・・。
しかもこのtimeout起きた時に ソケットclose処理読んでますが 、
shutdownInput()
shutdownOutput()
がエラーでて通過なので、
ソケットは切れてるけど鯖側はクライアント切断認識してない
というよくわからん現象。。
まあ上には相談したので後は判断待ちかな・・*5
あと今回一定期間経過後に回線切断する処理を組んでみたんだけど
onPause,onStopに入れた処理
- 2系だと
- 電源ボタン押して画面暗くする
- =>指定時間後に切断通知
- 電源ボタン押して画面暗くする
- 4系だと
- ロック画面でも回線切れない
- Homeで最小化
- =>指定時間後に切断通知
イマイチ挙動遷移がわからない感じなのでしたorz
http://twitter.com/kimukou2628/status/269421280153972737:twitter:detail:right
@kimukou2628 @sakura_bird1 拝読しました。落ちるときのログなどあったりしますか?時間の空いている時に研究したいと思います。一番手っ取り早いのが実機で見分することなのですが… きっと誰かよちよちに(チラッ
2012-11-16 22:02:54 via twicca to @kimukou2628
http://twitter.com/kimukou2628/status/269428180559994881:twitter:detail:right
@kimukou2628 @sakura_bird1 fmfmfmfm。端末側のgdgdによるものととはいえshutdown時の例外の時点でクライアント的にはソケットが壊れているので、Keep-Aliveを効かせておくとかの方法でHBを飛ばしておくくらいしか解決策はないのではと。
2012-11-16 22:24:41 via twicca to @kimukou2628
@kimukou2628 @sakura_bird1 はい、なので普段からKeep-Aliveや独自プロトコルで定期的に疎通確認して置くのがいいのではと思うのですよね… サーバから送ったりするだけでもクライアントのTCPスタックが切断してくれたりしますし :-)
2012-11-16 22:39:47 via twicca to @kimukou2628
@kimukou2628 @sakura_bird1 回線切断のタイミングですが場合によるとしか… しかし、スリープと同時に回線が切断されたりするのはありがちなので…適切なロックが取れない状況であればonPauseあたりで一旦切るのがいいのかも、とは思います。
2012-11-16 22:42:42 via twicca to @kimukou2628
@kimukou2628 @sakura_bird1 WSでもその辺の完全な解決はできないと思います ^^; 例えば節電系のアプリがなくともWi-Fiはスロットルされて応答時間が不安定になったりします。積極的に切る機種もあるでしょうし。そうなればもはやソケットは (しろめ
2012-11-16 22:56:53 via twicca to @kimukou2628
やっぱり結論としてはこうなっちゃうんだよな。。
@alterakey 地下鉄とかでよく切れるからそうですよね(´`;)ゞ NWInfoで接続状態とれるようになったのは4系からですし。room-id等で再接続が現実的なのかなと
2012-11-16 23:08:53 via hamoooooon to @alterakey
@kimukou2628 ですよね?。普通の用途なら再接続が一番簡単だと思います :-)
2012-11-16 23:10:51 via twicca to @kimukou2628
どちらかと言うとプライベートチャットのほうが人気あるよねみたいな。
なかでも登録するデータができるだけ少ないやつ・・*6。
まあ情報凄く抜こうとする割には情報管理がずさんだからな。。。(汗
やり取り的なメモ)
検証コードさらっとかける @alterakey さん凄い!
[http://twitter.com/alterakey/status/268703967486279680:twitter:tree]
まあ本来 普通は繋げっぱで、やり取りメッセージで退室とかって実装になってればよかったんだけど
後半多分実装面倒になったのか、クライアント切断処理と退室処理を兼ねて横着みたいな鯖実装。
即興で作るとこんな感じになっちゃうんだよな(汗
参考にしたサイト2
最終的にはTLの詳しい人に聴きまくり。
いつもお世話になってます(汗
なんか変な仕様は治したいんだけど、運用中のサービスだと
騙し騙しでクライアント側でなんとかゴニョる話になるんだよな・・・。
正直ありえない実装なんだけど・・・
@skrb お忙しいところすみません。socketChannel.close がうまく動かないことがあって調べていたのですが、桜庭さんの昔の記事で URL finallyで遣るといいという記載は1回で閉じないことがあるための対策なのでしょうか?
2012-11-14 22:42:28 via YoruFukurou to @skrb
@kimukou2628 closeはfinallyで行なうというのは、よく使うイディオムというだけで、closeできないからというわけではないです。ソケットがcloseできないという事象は遭遇したことがないので、何が悪いのかはよく分からないのです... お役に立てず、すいません
2012-11-14 23:11:18 via HootSuite to @kimukou2628
@skrb @kimukou2628 AndroidはJavaじゃない(ボソッ
@yusuke @skrb わかってはいるんですけどね(苦笑。早くjavaFXが動くようになったら、それにかこつけて遊びたいなーとかいつも思ってます(今だとデータエディタ作るからJavaFXな話しか出来ず<IDEはecipseね な縛りがorz
2012-11-14 23:43:44 via YoruFukurou to @yusuke
@kimukou2628 @yusuke @skrb そうだったんですね。すいません。(^_^;)