- PR -

SocketChannel で Connection reset by peerが起きる

1
投稿者投稿内容
エイジ
会議室デビュー日: 2005/11/22
投稿数: 6
投稿日時: 2008-01-24 15:16
こんにちは、エイジです。
Java NIOを使ってTCPの通信を中継するプログラムを作っています。
負荷テストをしていると、SocketChannel#read において時々 IOException: Connection reset by peer というエラーが出てしまいます。原因と対策についてご教示ください。

[やりたいこと]
以下のように、クライアントとサーバで通信させたいのですが、種々の事情から
間に中継マシンをはさみ、クライアントとサーバの通信をそれぞれ相手に流す
処理を行いたいとかんがえています(delegatedのように)。

(CLIENT) <---> [中継マシン] <---> (SERVER:SMTP)

このため、中継マシンではJava NIOを用いて、クライアントおよびサーバと
Channelを開き、Selecterによって一方から書き込みがあったらもう一方へ
その内容を書き込む、ということを繰り返すようにしています。

[問題]
後に添付するソースにより上記の処理を行っていますが、Selectorにより
選択されたChannelからデータを読み込む時に IOException が発生します。
このため、それ以上中継が出来ず双方のChannelを閉じるしかありません。

(1) Channel#readの直前の状態ですが、
key.isValid() -> true
key.isReadable() -> true
channel.isConnected() -> true
となっており、読めないような兆候がみえません。

(2) 中継マシンを置かずクライアントとサーバが直接通信した場合は
何ら問題が起きません。よって、クライアントが切断したわけでは
ないように思われます。

このような問題がなぜ発生するのか、回避方法がないか、をご教示頂ければ
幸いです。よろしくお願い致します。

[プログラム(抜粋)]
** 見通しをよくするため例外処理を省いています
コード:

Selector selector;
ByteBuffer buffer;
Map peer;
:
(省略)
:
public void mainLoop() {
while (in_transit) {
int n = selector.select();
if (n == 0) {
continue;
}

Iterator it = selector.selectedKeys().iterator();
while (it.hasNext()) {
SelectionKey key = (SelectionKey)it.next();
if (key.isValid() == false) {
;
} else if (key.isAcceptable()) {
/* クライアントおよびサーバとの接続処理 */
} else if (key.isReadable()) {
relay(key);
}
it.remove();
}
}
}

private void relay(SelectionKey key) throws IOException {
try {
SocketChannel channel = (SocketChannel)key.channel();
/* 中継相手のchannelを取り出す */
SocketChannel peer = (SocketChannel)pair.get(channel);
int cnt_in = 0;
int cnt_out = 0;

if (peer != null) {
buffer.clear();
LOOP1:
while (true) {
try {
cnt_in = channel.read(buffer); <== ここでエラー!
} catch (IOException e) {
cnt_in = 0; <= 無視して続行したいが次回で必ずcnt_in=-1となる
break LOOP1;
}
buffer.flip();
switch (cnt_in) {
case -1:
break LOOP1;
case 0:
return;
default:
int i = 0;
while (buffer.hasRemaining()) {
cnt_out = peer.write(buffer);
i = buffer.remaining();
}
break;
}
buffer.clear();
}
} else {
cnt_in = -1;
}

if (cnt_in < 0) {
/* Channelのcloseなどの処理 */
}
}




[ メッセージ編集済み 編集者: エイジ 編集日時 2008-01-24 16:34 ]
ranco
大ベテラン
会議室デビュー日: 2007/11/02
投稿数: 112
投稿日時: 2008-01-24 20:25
実サーバがノンブロッキングNIOでなければ、この設計は無意味だし、実サーバがnbNIOなら、この設計は不要です(中継マシンは要らない)。現状では、中継器のクライアント部分は実クライアントと同一コードでよいし、チャネルはサーバ部分でのみ必要。チャネルは実クライアントとI/Oするのみです。サーバ部分とクライアント部分のデータのやりとりは、伝統的なプロキシのコードになる。それだけです。全貌は見えないが、なんか基本認識と基本設計がおかしい気がする。
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2008-01-24 20:50
引用:

エイジさんの書き込み (2008-01-24 15:16) より:
Java NIOを使ってTCPの通信を中継するプログラムを作っています。
負荷テストをしていると、SocketChannel#read において時々 IOException: Connection reset by peer というエラーが出てしまいます。原因と対策についてご教示ください。


私はネットワーク関係はあまり知らないほうなのですが、"Connection reset by peer" は、相手から勝手に切られたときには出て当然の例外だと思いますので、その IOException に適切に対処さえすれば良いだけだと思います。
負荷テストをされているそうですが、通信相手方のほうに問題があるのではないでしょうか。マルチスレッドなどの問題ということはないでしょうか。

引用:

エイジさんの書き込み (2008-01-24 15:16) より:
(2) 中継マシンを置かずクライアントとサーバが直接通信した場合は
何ら問題が起きません。よって、クライアントが切断したわけでは
ないように思われます。


「中継マシン」を置いた場合は、クライアントとこのプログラムとの通信になるので、そのときはクライアントがなぜか切断したくなって切断してくるのではないでしょうか?

IOException を throw している箇所の Java のソースコードも Sun のサイトから入手できますので、どんなところで例外が発生しているのかや発生した時点での状態を見てみてはどうでしょうか。
エイジ
会議室デビュー日: 2005/11/22
投稿数: 6
投稿日時: 2008-01-25 09:17
こんにちは、エイジです。
ranco様、unibon様、ご教示ありがとうございます。
なにぶん、ネットワークプログラミングに詳しくないためとんちんかんな
設計をしていたり、はずれた質問をしている可能性があります。
どうぞご容赦ください。

ranco様:

本件、実際にはサーバが何台もあり、プロトコルもSMTP以外に種々存在
します。このため、クライアントから中継マシンへ接続させ、そこで
サーバ側の負荷やプロトコルその他を考慮して適切なサーバへ振り分け
ようとしています。要するにプロキシ+ロードバランサなのですが、
その振り分けがシステムの他の部分と密接に連携するため今回Javaで
自作という対応をしております。

このため中継マシンが必要であり、特定のプロトコルに限定できないため
その振りをすることも出来ず、クライアント・サーバ双方からどの順序で
どのぐらいの量のデータが来るか判断できません。そこでノンブロッキング
NIOなら可能かと思った次第です。
そこで、

・実サーバがノンブロッキングNIOでなければ設計は無意味
・伝統的なプロキシのコードになる

についてもう少しご教示頂けないでしょうか?
(よく分かっておらず恐縮です)

unibon様:

ご指摘を受け、試しにクライアントを中継マシンと同一サブネットに置いて
テストしてみたところ、エラーが発生しませんでした。また、テストでは
SMTPおよびPOP3の通信を試していますが、今回のエラーはSMTPの通信中のみ
発生しております(現在までのところ)。これらのことから、ルータを始め
ネットワーク環境のどこかに要因がある可能性も考えられます。
そうであれば、おっしゃるように「本当にクライアントから切断された」ので
エラーになったということでプログラムの修正とは別の対応になるかも
しれません。さらにいろいろなパターンで試してみたいと思います。

よろしくお願い致します。
ranco
大ベテラン
会議室デビュー日: 2007/11/02
投稿数: 112
投稿日時: 2008-01-25 20:01
> ・実サーバがノンブロッキングNIOでなければ設計は無意味
最初の投稿の「図」では、サーバが単機でしたから、そう判断できます。

> ・伝統的なプロキシのコードになる
ただし、プロキシのサーバ部とクライアント部の間の質的量的同期は、実サーバがマルチノード&マルチプロトコルなら、相当精密な設計が必要ですね。不幸なことに、そういう超豪華幕の内弁当のような設計は私は未経験です。バグ源が、いたるところにありそう。パスワードのモンダイとか…。SMTPなら当然、ドメイン名とIPの整合性とか…。どうも、IPやDNSまわりアヤシイですね。
エイジ
会議室デビュー日: 2005/11/22
投稿数: 6
投稿日時: 2008-01-28 10:22
エイジです。ranco様ありがとうございます。

マルチノードでマルチプロトコルで設計していましたのでその辺が根本なのかも
知れません。もう少し見直し、よりシンプルな形を検討してみようと思います。
ネットワーク関連の仕組みを作るのはかなり綿密にしなければ解決しにくいバグが
混入しやすいということですね・・・。

どうもありがとうございました。
1

スキルアップ/キャリアアップ(JOB@IT)