Java
JavaFX
Socket通信
ワンドロ開発
privateMessenger

LAN内通信アプリを作ろう その2JavaFXを利用してウィンドウを表示させる/ソケットの受信を待ち受ける

生活リズムの改善を試みたのに失敗して1日つぶした結果、作業時間もガッツリ減る大ガバ

さて、今回はUI部分について、JavaFXを利用して作っていきます。
それから通信の待ち受け部分も作っちゃいましょう。
時間が無いんで!(自業自得)

JavaFXを使ってUIを作る。

JavaFXとは?

一言で言うと、"Java向けのGUI描画ライブラリ"です。

特徴はXMLとCSSでデザインを記述する点。
また、ドラッグアンドドロップでデザインできるSceneBuilderというアプリケーションが無償で使えることもあって大変お手軽。
RIAプラットフォームでも使えるとかなんとか。

歴史とか経緯についてはこちらのwiki参照

メイン画面を作る

上記のSceneBuilderを利用してメインになるwindowを作ってしまいましょう。

IPMessanger先生をリスペクトしつつこんな感じに

2018y06m13d_193810674.jpg

これを出力するとFXML形式のファイルが出力される。
今回はメインとなる画面(Stage)ということでMainStageと命名。
早速格納用のディレクトリを作成し、ソースに入れ込んでやろう。

2018y06m13d_215409682.jpg

上の画像ではMainStage.javaも格納されてるけど、これは別途作ったやつ。
FXMLはC#のFormと異なり、構造(とイベントハンドラ情報)を記録しているファイルなので、
処理を行うjavaソースは別途作ってやる必要がある。

ここら辺はMVCモデル的な分割がなされていると考えた方がよさそう。

ファイルの分け方もコントロールとViewで分けたほうがきれいかも…

とりあえず呼び出してみたい。

作ったFXMLをとりあえず呼び出してみたい。

色々調べてみたんだけど、どうにもJavaFXProjectの雛形みたいなものしか出てこない。
具体的に言うと、main()メソッドからそのまま画面呼び出す方法しか書いてない。

今回は気分的に他のMainメソッドから呼び出したいので、色々調べてみた。
参考になったのはここら辺のページ

ざっくりな理解だと、Applicationが主になって、Stage(windowと同じ扱いでもいいかも)を管理する。
StageはSceneを描画する。
Sceneを構成するのはParentに格納された各種ノード。って感じだろうか?

具体的な手順としてはこんな感じ。

  1. javafx.application.Applicationを継承したクラスを用意する。
  2. javafx.application.Application.launchメソッドに用意したApplication継承メソッドを食わせる。

具体的にやってみよう。

1. javafx.application.Applicationを継承したクラスを用意する。

これは簡単。

MainStage.java
package kugui.owd.privateMessenger.stage;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class MainStage extends Application {

    @Override
    public void start(Stage stage) throws Exception{

        Parent root = FXMLLoader.load(getClass().getResource(
                getClass().getSimpleName() + ".fxml" )
                );

        Scene scene = new Scene(root);
        stage.setScene(scene);

        stage.show();

        return;
    }


}

まず気をつけるべきなのはstart()メソッドをoverrideしてるところ。
なんでもApplication.launch()に渡したApplication継承メソッドは独自のライフサイクルに沿って実行されるらしい。

簡単に書くとこんな感じ

呼び出し時
public void init()
public void start()
終了時
public void stop()

init()は元々実装されてるんだけど、start()は抽象クラスとして宣言されてるので、Overrideしないとビルド通りません。
基本的に初期化処理はstart()のタイミングでいいんじゃないかな。

上のコードの処理の流れは以下の通り。

  1. Parentインスタンス(parent)をfxmlファイルを元にFXMKLoader.load()メソッドを利用して生成。
  2. Sceneインスタンス(scene)を作成したParentインスタンスを元に生成。
  3. Applicationから渡されるStage型パラメータ(stage)に作成したSceneインスタンス(scene)を食わせる。
  4. stage.show()メソッドを用いて描画させる。

ここら辺は同一クラス内の処理を記述したサンプルがたくさんあるのでそんなに躓かないかも。

2. javafx.application.Application.launchメソッドに用意したApplication継承メソッドを食わせる。

Applicationの継承クラスはとりあえず用意できたので、次に呼び出し元を設定。

前回の記事で作りっぱなしにしてたppa.java君にちょろっと追記。

ppa.java
public class Ppa
{
    public static void main( String[] args) {
        System.out.println( "Good night World");

        Application.launch(MainStage.class, args);

        return;
    }
}

Applivation.launch()は以下の制限がある模様。

  • 複数回呼び出せない。
  • 継承したクラスからは呼び出せない(RuntimeExceptionが投げられる)

となると実装方法もちょい考えなきゃいけませんね。

ちなみにこのlaunch()は同期メソッド。
この後に処理を書くとwindow閉じた後に実行されます。

ここまできたらとりあえず表示してみます。

2018y06m13d_234251791.jpg

うん。ここまでは問題なさそう。

待ち受けスレッドを作成する。

正直あんまマルチスレッドなプログラムって組んだ事無いんですよね。
少なくとも組込機器だと見た事無いです。だってマルチプロセスで組んでいいわけだし、IPCよりIRQで情報処理する方が多いし。
あ、フレームウェアでも見た事無いかな。

ゲームとかだとモリモリ使ってる印象ですね。
せっかくなのでここで慣れて行きましょうか。

やったことないことに日和るのは当然。でもやっちまえばなんてこと無い。
世の中はそんなもんで出来ている。

スレッドとして動かすクラスを用意する

スレッドとして動作させるには?

スレッドの使い方はざっくりまとめると次の通り

  1. スレッド化に対応したクラスを作成する。
  2. スレッドを呼び出す処理を実装する。

シンプルですね。
じゃ、つくりましょ。

1. スレッド化に対応したクラスを作成する。

Javaに於ける"スレッド化に対応したクラス"の作り方は次の2つ

  • Threadクラスを継承してクラスを作成する。
  • Runnableインターフェースを実装してクラスを作成する。

違いはなんぞや?と調べてみると動作的には同じらしい。
多重継承が出来ないJavaの言語仕様上、他のクラスを継承してスレッド作りたいときにRunnableインターフェース使ってね!
ということらしい。

さて、新しく待ち受けスレッドクラス RxThread を Java.lang.Threadクラスを継承して作成。
ちなみにThreadクラスはimportなしで使えます。

RxThread.java
package kugui.owd.privateMessenger;


public class RxThread extends Thread{

    public void run() {

        int countPray = 0;

        try {
            while(true) {

                System.out.println(String.format("%2d : please let me see the good dream...", countPray));
                Thread.sleep(1000);

                countPray++;
            }
        }catch(InterruptedException ie ) {

            /* no operation */

        }catch(Exception ex) {

            ex.printStackTrace();

        }

        return;
    }

}

動作確認のため、ループ処理添え。

Threadもライフサイクル…みたいなものがあるらしく、スレッドを実行したときにvoid run()メソッドが呼び出されるらしい。
ので、run()メソッドを実装して処理を記述。
他のメソッドが呼び出されるような記述は探したけど見つからなかったので、どうやら初期化から終了処理までrun()から呼び出すように書いてやる必要がありそう。

2.スレッドを呼び出す処理を実装する。

ppa.java君のmain()メソッドに以下の処理を追加

  1. 作成したスレッド化クラスのインスタンスを生成する。
  2. Threadクラスを生成したスレッド化クラスを食わせて生成する。
  3. thread.start()メソッドを呼んでスレッドを開始する。
  4. (送信ウィンドウ消去後)スレッドに割り込みキューを送る。
ppa.java
    public static void main( String[] args) {

        RxThread rxThread = new RxThread();
        Thread thread = new Thread(rxThread);

        System.out.println( "Good night World");

        thread.start();
        Application.launch(MainStage.class, args);
        thread.interrupt();

        System.out.println( "r u still awake?");

        return;
                }

動作を確認するとこんな感じに。

2018y06m14d_012812748.jpg

↓ window閉じるとこうなる

2018y06m14d_012833983.jpg

ここまででスレッド化についてひっかかったところとかを以下にちょっと追記。

  • スレッドで無限ループを実行中にメインスレッド終了するとどうなるの?
  • Thread継承してるのにThreadインスタンス作らなきゃだめなの?
スレッドで無限ループを実行中にメインスレッド終了するとどうなるの?

正直に言うと、「親スレッド消えたら子スレッドも一緒に消えるっしょ」とか思ってました。
そう、「親は死んでも子は生きる」と、字面にするとなんだか少したくましい感じに。

動かしてみるとこんな感じ。

2018y06m14d_013214000.jpg

よく考えると「親は死んで子供はゾンビ化」みたいな状態なんでお先真っ暗もいいとこなのでは。
なので、「親プロセスが終わる前に子プロセスは確実に終了」させてやる必要がある、と。

だからね、調べたんですよ。スレッドを終了させる方法。
そしたらThread.stop()ってメソッドが実装されてるのを発見。

これを書いてみると今度はEclipse君がおむずがり。

thread.stop()みたいに打ち消し線まで出されちゃってね。
なんでや?と思ったらこのメソッド、非推奨メソッド。
詳細:Oracle - ドキュメンテーション クラスThread (日本語) - stop()の項参照

待機を取り消すにはThread.interrupt()メソッド使って、スレッドの終了処理自体はスレッド側でやらせろ、と。
ちなみにThreadの動作を一時停止するsuspend()と再開するresume()も非推奨。理由は「デッドロックを発生させやすいから!」とのこと。
基本的には他のスレッドへの干渉はthread.interrupt()を利用した割り込みキューだけにしとけってことっぽい。

まあ、変に制御系バラけさせると後々自分の首絞めるものね。

Thread継承してるのにThreadインスタンス作らなきゃだめなの?

これ、試してみたら問題なく動作します。
Runnableインターフェースを実装するパターンのクラスだとこっちでやる必要があるんでしょうね。(start()メソッドないし)

待ち受け処理を作る

今回はSocket通信なので、webSocketを利用した通信。

んー、Socket通信をわかりやすく説明ってどうすんだろ?
データを送受信するのに使うポートっていう兼用のデータ格納領域があるので、
待ち受け時に使うポートを指定して、そこを監視するイメージ。

調べてみると、javaのsocket通信は大変簡単に作れる様子。

早速実践。

上でこねくりまわしてたRxThread.run()の処理を一回削除して待ち受け処理に作り変え

RxThrea.java
package kugui.owd.privateMessenger;

import java.io.IOException;
import java.net.*;

public class RxThread extends Thread{

    public void run() {

        int port = 80; // test用。
        ServerSocket sSocket;

        try {

            sSocket = new ServerSocket(port);
            sSocket.accept();

            System.out.println( String.format("port %d : socket accept", port));

        }catch(IOException ioE) {

            ioE.printStackTrace();

        }

        if( sSocket != null ){
            sSocket.Close();
        }

        System.out.println( "finished");

        return;
    }
}

組んだら早速起動。
2018y06m14d_025627764.jpg

80番ポートにデータ送るため、ブラウザでlocalhostにアクセス。

localhostとは?自分自身にアクセスするときに使う名前。具体的には127.0.0.1のipアドレスにアクセスするためのドメイン名。

なんでブラウザ?ポートの数は有限(65536個)で、よく使うポートはwell-knownポートとして知られている。"80番ポートはHTTPでアクセスするポート"なので、localhostをブラウザで指定するだけで情報の取得要求を80番ポートに送ることが出来る。ブラウザで他のポートにアクセスしたい場合はアドレス/ドメイン名の後に:<>と指定してやればよい。普通はじかれるけど。

結果はこれ。
2018y06m14d_030827575.jpg


待ち受けまでは問題なく出来たので今日はここまで。
本日の稼動は・・・途中でwindowsの再起動入ってわかんなくなっちゃったけど2時間くらいと仮定して・・・
06:45:53とします。