高度な検索
Developer Connection
Member Login ログイン | ご入会 ADC連絡先


Technote 1157

Don't println to a Socket


目次

お心当たりは

println は要注意

簡単な解決法

MMac OS 上で動作するクライアントサーバ Java アプリケーションが、デッドロックを起こす共通の原因として、Socket に接続された OutputStream への書き込みで println メソッドを誤用する場合があります。これから、その理由と解決策を説明します。



お心当たりは

HTTP や FTP などのインターネットの標準プロトコルや、同じように行を基本として交信を行う専用プロトコルを使って、サーバとネットワーク通信を行うクライアントアプリケーションまたはアプレットを作成したとします。このアプリケーションは Windows と UNIX では支障なく動作するのに、Mac で実行すると、サーバからの応答を受け取るところでクライアントが止まったままになります。サーバ側を調べてみると、読者のアプリケーションと通信するサーバ側のスレッドも、クライアントからのコマンドの読み込みで同じようにブロックしていることがわかります。

これはあまりよくは知られていませんが、Java ネットワーキングの定石なのです (われわれは多数のデベロッパからこの報告を受けています)。耳が痛いかもしれませんが、これは読者のアプリケーションのバグであって、MRJ のバグではありません。たださいわいにして、これは簡単に回避できます。

先頭ページに戻る


println は要注意

原因となっている問題は、行を区切る方法には複数の異なる方式があり、これが正しく区別されていないことです。行を基本とするインターネットプロトコルはほとんど例外なく行末コードとして CRLF、つまり、ASCII の復帰文字 (「\r」、16 進数の 0D) の後に改行文字 (「\n」、16 進数の 0A) を続けたものを使用しています。このようなプロトコルのコマンド送信には、Java のすばらしいストリームクラス群を用いて、PrintStreamPrintWriter オブジェクトから println メソッドでコマンドを送りたいと思うかもしれません。println は行末コードも送ってくれるからです。

問題は、println がファイルストリーム向けに設計されており、追加する行末コードはローカルプラットフォームのものだということです。それは、Windows なら CRLF、UNIX なら LF、Mac なら CR です。(具体的な行の区切り文字はシステムの line.separator プロパティから読み込まれます。) このやり方は、ローカルファイルに書き込む際には正しい方法ですが、サーバと通信する際には問題が起こります。

現象は次の通りです。クライアントが次の呼び出しを行います。

     out.println("HELO foo@bar.com");

MRJ で動作している場合、これは次のバイト列を送ります。

     HELO foo@bar.com\r

サーバは上記のテキストと CR を受け取り、CR の次に LF が来るのを期待して、次の 1 文字の受信を待ちます。たいていのサーバは洗練されており、CR の次に受け取った文字が LF でなくても、標準外の行末コードが使われていると判断し、その文字を次の行の最初の文字として処理します。しかし、とにかくまず、サーバは CR の次の文字が来ないとコマンドの処理へ進めません。

一方、クライアントはサーバからの応答行を待ち、これを受信しないと次のテキストは送信しません。両方が InputStream.read の呼び出しでブロックする典型的なデッドロックです。

先頭ページに戻る


簡単な解決法

正しい方法は、一定の行区切りのシーケンスを生成する必要がある際には println を使用しないことです。そうではなく自分で行います。上のサンプルを次のように修正しましょう。

     out.print("HELO foo@bar.com\r\n");

これは非常に簡単なことですが、残念ながら、コード中のすべての println 呼び出しを変更しなければなりません。

すばらしいがうまくはいかない解決策

非常に優れているのに残念ながら使えない解決策があります。java.awt.PrintStream および java.awt.PrintWriter クラスのソースを見ると、これらのクラスは newLine メソッドを使って行末コードの送信を行っていることがわかるでしょう。PrintStream または PrintWriter の独自サブクラスを作成して、newLine をオーバライドして明示的に CRLF を送信するのが非常に洗練されたやり方です。しかし、残念ながら、些細な設計ミスのため(newLine メソッドは private になっていて、protected ではありません)、オーバライドができないのです。本当に残念です。

サーバ側での修正

読者が同時にサーバの開発も行っており、サーバ側のコードも変更できる場合は、クライアント側を変更するより、サーバ側で行えるずっとよい修正方法があります。上記で、サーバが CR に続く文字を待って、LF かどうかを判断するためにブロックする方法を説明しました。標準外の行末コードを処理するもっと洗練度の高い方法は、サーバが CR を受け取ったら即座に入力行を処理し、もし次に受け取る文字が LF ならそれを無視することを指示するフラグを設定しておくのです。

この方法により、サーバは CR を受け取った後ブロックせず、コマンドを処理し応答できるようになります。クライアントコードは応答を受け取り実行を続けることができるため、デッドロックは起こりません。

先頭ページに戻る