FMS(JAVA PRESS Vol.11より)

JAVA Press VOL.11のp.92から始まる「Java2で作るネットワークプログラム」という記事のコピーが手元にあった。いつかJavaの勉強のために読もうと思っていた、約9年前に発行された雑誌のコピーである。UDPを使って相手を探し、TCPを使ってメッセージやファイルをやり取りする、簡単なチャットアプリのTCP/UDP通信の部分のソースコードが掲載され、解説されている。画面イメージも載っている。"File&Message Send"の頭文字を取って"FMS"という名前のアプリだそうだ。当時はGUIの部分と合わせてフルセットコードがJAVA PRESSのWebサイトにあったらしいが、今は入手できないと思う。

掲載されているのは8つのクラスの内5つのクラスのソースコードで、合わせて300行程度である。画面イメージから読み取れるGUIはシンプルなものである。似たようなGUIさえ作れば動くんだろう、と思って作ってみたら、4倍近いコードに膨れ上がってしまった。記事に載っていたコードも、1台のマシンでもテストできるよう、動的にポートを切り替えるように改造したりしたので、結局大半のコードに手が入ってしまった。
折角なので、ここで公開することにする。

FMSアプリ(改)のソースコードとクラスファイル一式(JARファイル)

・画面イメージ

●起動方法
まず、FMSアプリを動かすそれぞれのマシンにfms.jarをダウンロードして、以下のコマンドで展開してください。(JREはそれぞれのマシンにインストール済だとします。)

jar xvf fms.jar

次に、接続先として検索するIPアドレスのリスト(seekips.txt)とユーザ名のリスト(uesrs.txt)を編集してください。ブロードキャストアドレスも指定可能です。ユーザ名は、名前とキーワード(2つ合わせて1つのユーザ名となる)をコロンで区切ってください。
・seekips.txtの例
192.168.0.255
172.1.1.1

・users.txtの例
John:America
Yamada:Hanako
Penguin:Antarctica
Whitebear:Arctica

次に、コマンドラインから下記のjavaコマンドで起動してください。
java fms.Run (自分の名前) (自分のキーワード)

1つのマシンで複数起動してもOKです。

●使用方法
アプリを起動すると、seekips.txtに書かれたIPアドレスとローカルマシンの別ポートに対してUDPのパケットが送信され、相手から返信があると、ウィンドウの左上の領域に相手が表示されます。但し、相手の名前が自分のusers.txtに無い場合は、表示されません(自分の名前が相手のusers.txtにある場合は、相手のウィンドウには自分の名前が表示されます)。
相手から検索があるとユーザ名の後ろが[COMING]となり、相手から検索の応答があると[ONLINE]となります。

[COMING]または[ONLINE]の相手が見つかったら、リスト上の接続相手を選択して、メニューバーの"Users"→"Connect"で接続します。接続に成功すると、相手の名前の後ろが[CONNECTED]になります。

接続に成功したら、メッセージまたはファイルを送る相手を選択し、ウィンドウ下段の入力フォームにメッセージまたはローカルマシン上のファイル名を入力して、"Msg Send"ボタンまたは"File Send"ボタンを押してください。送信に成功すると、相手側のウィンドウにメッセージが表示され、ファイルの場合は相手側のローカルマシンに同じファイル名で保存されます。

●ソースコードの説明
Seeker.java
相手を探したり自分の状態を伝えたりするためのUDPパケットを発信するクラス。
オリジナルのコードに動的ポート対応を加えている。
Watcher.java
UDPパケットを受信するクラス。
オリジナルのコードではユーザ情報の管理を別クラス(Users)にしていたらしいが、想像がつかなかったので、ここで管理するようにした。
SendPack.java
TCPで送信する1回分の情報を持つクラス。
オリジナルのコードから変更無し。
Connector.java
1つのTCP接続のソケットと入出力ストリームを保持するクラス。
オリジナルのコードに動的ポート対応を加えた。
Accepter.java
サーバーとしてTCPの接続要求を待つ(acceptする)クラス。接続要求が来る度にConnectorのインスタンスを作成する。
オリジナルのコードから変更無し。
Run.java
FMSアプリを起動するクラス。Seeker, Watcher, Accepter,MainPanelのインスタンスを作る。
GUIとしては、SwingのJFrameのインスタンスを用意して、メニューバー付きのウィンドウを作る。
全て自作。
MainPanel.java
ウィンドウ内の各部品を作り、それぞれに対する操作や要求を処理するクラス。実質のメイン処理。
全て自作。

より詳しく書くとJAVA PRESSの記事と被るので、説明はここまでとする。
なお、Run.JavaとMainPanel.JavaはEclipse+VisualEditor(GUIエディタ)で作ったので、他の環境のEclipse+VEでもGUIが読み込めるはず。


正月休みに雑誌のコードを読み込んだので、短時間でできるだろうと高を括っていたが、通信系の5つのコードが想定する処理シーケンスが理解できていなかったようで、延々と試行錯誤を繰り返す羽目になった。マルチスレッドなのでprintfデバッグではどこまで行っておかしくなったのかがわからず、Eclipseのデバッガに大変助けられた。

Eclipseは便利だ。特に、入力補完機能が賢く、importとtry/catchブロックを自動補完してくれるのが良い。Javaのコードを手書きしていて私が最も面倒に思うのがimport文の記述とtry/catchブロックの記述である。常にimport *.*.*としたい気持ちだがそれは通らないので、いちいちAPI仕様書で探さないといけないし、tryしてcatchするのはExceptionに決まってるのにいちいちExceptionと書かされる。IOExceptionとFileNotFoundExceptionを分けて処理するコードを手で書こうという気にはならない。それに対して、Eclipseがtry〜catch(IOException)〜catch(FileNotFoundException)〜…を自動的に挿入してくれると、大変気持ちが良い。Eclipseの"Surround With try/catch block"コマンドは病み付きになる。

折角VEを使ったので、SwingのGridBagLayoutを初めて使ってみたが、部品の絶対的なサイズ指定をせず、全て相対的なspanやweightの指定で配置しようとすると、決して思い通りにはならないことがわかった。ウィンドウの大きさを変えられると配置が崩れるし、部品にテキストを表示すると部品の大きさが変わって配置が崩れる。特に、表示するテキストが動的に変わる部品が直線方向に並ぶ場合は、1つを除いて絶対的なサイズ指定をする必要があるようだ。
HTMLのようにウィンドウの何%とかで指定できると便利だと思うのだが、残念だ。