- PR -

TCP通信を中継するJavaプログラムを作りたい

1
投稿者投稿内容
エイジ
会議室デビュー日: 2005/11/22
投稿数: 6
投稿日時: 2005-11-22 22:01
はじめまして、エイジです。
JavaでTCPの通信を中継するプログラムを作っていますが、うまくいかず困っています。
アドバイスをいただければ幸いです。
実現したいのは、delegateのようにクライアントとサーバの間に1台マシンが挟まり、
クライアントとサーバとの間のTCP通信を中継することです。

CLIENT <-----------> 中継マシン <-----------> SERVER

CLIENTからは中継マシンがサーバのように見え、SERVERからは中継マシンがCLIENTの
ように見え、それぞれ送信した内容が中継マシンを経由して相手に受信されるようなものです。これを実現しようとして以下のようなルーチンを作成しました(部分)。


ByteBuffer buffer = ByteBuffer.allocateDirect(4096);
SocketChannel channel_src;
SocketChannel channel_dst;

int count = 0;
int written = 0;
while ((count = channel_src.read(buffer)) > 0) { <------ (1)
buffer.flip();

int i;
while (buffer.hasRemaining()) { <------ (2)
written = channel_dst.write(buffer); <------ (3)
i = buffer.remaining(); <-------(4)
}
buffer.clear();
}

ここで、channel_src, channel_dst はそれぞれCLIENTもしくはSERVERとの
接続されたSocketChannelです(上でははしょってますが接続済みで、
Selectorにより読み書き可能な状態になっています)。
channel_srcからbufferへデータを読み込み、そのデータをchannel_dstへ
書き込むことで、CLIENTとSERVERとの間の通信を中継しようと考えました。

このプログラムを実行したところ、確かにCLIENTとSERVER間の通信を中継
できるので問題なさそうに見えたのですが、時折CPUを非常に消費するので
調べたところ(2)のwhileループで無限ループに近い状態が時々発生することが
わかりました。そこで読み書きのバイト数を(1),(3),(4)のところで確認したところ、
(1)で読み込んだバイトを(3)のところで1バイトも書き込めず、何回もループを
繰り返した後、突然書き込みが成功して(2)のループを抜ける、という
状態とわかりました。つまり、(1), (3), (4) で採取できる値が次のようになります。

(1) = 4096
(3) = 0, (4) = 4096
(3) = 0, (4) = 4096
(3) = 0, (4) = 4096
:
(3) = 0, (4) = 4096
(3) = 4096, (4) = 0
-> ループ抜ける

channel_srcはisReadableで、channel_dstはisWritableなのは
確認しています。少しずつ書き込めるのではなく、全く書き込めずに
ループを何度もまわり、最後に一括で書き込まれるということの原因が
わかりません。これでは書き込めるまでビジーウェイト状態になり非効率です。
このような状況を回避するにはどうすればよいか知りたいので、
どなたがご教示願います。



_________________


[ メッセージ編集済み 編集者: エイジ 編集日時 2005-11-22 22:03 ]

[ メッセージ編集済み 編集者: エイジ 編集日時 2005-11-22 22:04 ]
シュン
ぬし
会議室デビュー日: 2004/01/06
投稿数: 328
お住まい・勤務地: 東京都
投稿日時: 2005-11-22 23:14
TCP SocketのSNDBUF(送信バッファ)が一時的に満杯になるんじゃないですか?

SocketChannel#write()がノンブロッキングだとすれば、その状態になった時に
ビジーウェイト状態になるのはごく自然のことだと思います。

バッファ全体をソケットの送信バッファに書き込むまでブロックするタイプの
メソッドを呼ぶか、または書き込みバイト数が0だったら一定時間自分で待機すべき
ではないでしょうか。
山本 裕介
ぬし
会議室デビュー日: 2003/05/22
投稿数: 2415
お住まい・勤務地: 恵比寿
投稿日時: 2005-11-22 23:48
AXISのtcpmonなんて参考にしてみてはいかがでしょう。
http://ws.apache.org/axis/java/user-guide.html#AppendixUsingTheAxisTCPMonitorTcpmon
http://cvs.apache.org/viewcvs.cgi/ws-axis/java/src/org/apache/axis/utils/tcpmon.java?rev=1.59&view=log
エイジ
会議室デビュー日: 2005/11/22
投稿数: 6
投稿日時: 2005-11-24 09:16
エイジです。
シュン様、インギ様、アドバイス頂きどうもありがとうございます。
ご発言を参考に確認してみました。

まず、tcpmonについては、ソースをざっと見た限りではSocketChannelを使っていない
ように見えました。今回の案件ではnon-blockingモードで処理したいと考えているため
SocketChannelを使用したく、残念ながら解を見つけることができませんでした。

次に、SO_SNDBUFが溢れるためではないか、とのご指摘でしたので、
デフォルトのバッファサイズの確認とサイズを拡大しての挙動の確認をしてみました。
使用している環境では既定値はSO_SNDBUF=25296, SO_RCVBUF=43944でした。
これをSND,RCV共に25000, 50000, 100000, 111616(MAX)にして、
bufferのサイズも2048, 4096,8192,16384と変えてテストしました。
結果は、多少のばらつきはあるもののビジーウェイトはやはり同程度に発生し、
通信時間も取り立てて大きな違いは見られませんでした。

一つわかったのは、bufferサイズに関係なく、16000バイト強をwriteする((3)のところ)
たびにビジーウェイトが発生していることです。例えばサイズが4096ならwrite4回に
1回、16384なら毎回待ちになります。きっかり16384バイトかどうかまでは確認
できていません。ともかく、SNDBUF以前(以後)のどこかでこのサイズの制約
もしくは既定値による壁があるように思えました。このあたりについてはもう少し
確認してみようと思います。

一方で書き込めなければ待機するという手についてはこれから試してみようと
思っております。
どうもありがとうございました。

_________________
nil
会議室デビュー日: 2003/06/17
投稿数: 14
投稿日時: 2005-11-25 14:03
2度目以降の書き込みの前にisWritableを確認していますか?
コード:
if(buffer.hasRemaining()){
  buffer.compact();
  break; // Selector#select() へ
}


などとして、書き込めなかったものは再びisWritableになるまで持ち越しては如何でしょうか。
(bufferに空きがないときにSelectorを操作して読み込みを一時的に止める、などといった処理も必要でしょうね)
エイジ
会議室デビュー日: 2005/11/22
投稿数: 6
投稿日時: 2005-11-28 12:04
エイジです。
いろいろアドバイス頂きありがとうございます。
whileループの中で、書き込めなかった場合にsleepする手を試してみました。
待ち時間は5msecにして実行しましたが、CPUの負荷は全く上がりませんでした。
(topコマンドでload avg.を監視した限りでは)
また、処理時間も対処しない場合とほぼ同じでしたので、固定の待ち時間にした
ことによる無駄な待ちもあまり影響していないようです。

一方で、書き込めなかった時は再度isWritableのチェックをする、という対処については
無駄な待ちを生じないという点で魅力的ですので試してみたいと思います。

どうもありがとうございました。
シュン
ぬし
会議室デビュー日: 2004/01/06
投稿数: 328
お住まい・勤務地: 東京都
投稿日時: 2005-11-28 23:16
アプリケーションレイヤーでの「待ち状態」であることが時間の無駄で
あるならば、それを発生しないブロッキングI/Oでの送信が最適だとお
もいますが…
1

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