応用アプリ例(チャット)

GUI や Network 処理との協調

皆さんが普段よく目にするアプリでは、GUI 画面がついていたり、Network と繋がっていたり、多分に「外界」とのやり取りがあります。 外とのやり取りをする場合、「低遅延で反応できるか?」というのは重要な指標です。画面や通信が固まるのは嫌ですよね。

GUI の場合、その処理は単一スレッドでおこなうことが多いです。GUIとスレッドで述べたように、GUI スレッドへの仕事の依頼は、特別な API を介して処理することになります。API 内部では、共有データアクセスが行われることになりますが、ちゃんと作られているはずです。

一方で、ネットワーク処理について。 まず、受信処理ですが、いままで教えてきたように blocking I/O をつかった場合、受信命令を発行したらデータが受信できるまで待つことになります。なので、受信処理には特別のスレッドを設けたいところです。受信内容に応じて GUI に仕事を依頼したい場合は、前述の API を通じて仕事を依頼してお任せ(待たない)のが基本でしょうね。 Retrofit などのライブラリをつかった場合は、複数の受信処理を Retrofit が処理してくれます。callback 関数も、それ用のスレッドで実行してくれます。こちらも、GUI への仕事の依頼は、依頼してお任せというのがスタイルでしょう。

一方で、送信処理については、複数のスレッドが排他制御して行ってもらっても構わないかと思います。

synchronized void sendMessage(Msg msg) {
    out.writeObject(msg);
    out.flush();
}

なるメソッドを準備して、GUI スレッドなどから呼ばせるとよいかも知れません。

gui_net.png

GUI マルチチャットの例

thread, GUI, synchronized などの用例詳解ということで、複数人でチャット可能な GUI アプリプログラムを見てみましょう。ソースは kobeU.cs.guiChatにあります。

  • Server program CommunicationHub
    • 複数のクライアントが接続(TCP/IP socket)するためのサーバプログラム
    • 状態確認用の GUI もついている。
    • 複数クライアントに対応するため、マルチスレッド化されている。
    • kobeU.cs.guiChat.comToolsの4つのクラスを利用
  • Client program GUIChat
    • サーバに接続
    • GUI とnetwork 処理のため、マルチスレッド化されている。
    • kobeU.cs.guiChat.appの4つのクラスを利用

とりあえず、動かしてみましょう。

  1. まずは、Server (comtools package の communicationHub)を立ち上げる。port 番号を指定してください。
  2. その後、Client (app package の GUIChat)をいくつか立ち上げる。(Server mode は OFF で、port 番号を指定してください)
    • Client が接続するたび、Server にメッセージが現れる
    • Client がメッセージを投げると、Server および他の Client に通知される。
    • 注:GUIChat 起動時の Server mode は、1対1通信をするときに、一方を Server 扱いするためのものです。

で、こんな感じのプログラムですが、内部がどうなっているか、簡単に説明です。

まずは、Client 側ですが、まずは GUI thread に GUI 画面(Board クラス)を作成依頼し、その初期設定の際に GUI thread 側でネットワーク接続を開始しています。 GUI thread による初期設定終了を待ってから、main thread は、メッセージ受信待ち状態に入ります。(該当method: exec())

その後は、しばらくユーザ(GUI thread)もしくは到着メッセージ(main thread)に応じた処理をすすめ、 GUI による終了操作もしくはサーバからの接続停止命令が来たら、接続停止処理をおこなうといった感じです。 main thread はメッセージを受信すると、GUI thread に invokeLater を用いてメッセージ到着を知らせるといった手順になっています。

guichatClient.png

一方で、サーバ側ですが、server socket で受信待ちし、クライアントが接続するたび、メンバリストに加えていきます。 このプログラムでは、各クライアントとの接続管理をおこなうためスレッド(ClientProxy)が割り当てられます。 クライアントの数が多いとスレッドが沢山生成されることになりますが、ほとんどは I/O 待ち状態ですので、CPU が常時割り当てられている訳ではなく、 メッセージが来た時だけ動き出すということになります。

クライアントのリストは List データ構造に格納され、新規クライアントが接続するたびに追加され、クライアントが接続を切ると削除されます。 一方で、各クライアントがメッセージ送信する場合は、当該リストを介して各クライアントへのメッセージ送信処理が行われます。 リストへの要素追加・削除中に、他のスレッドによってリストへのアクセスが行われると困るので、排他制御処理が必要で、 このプログラムではリストへのアクセスについては、synchronized method を用いておこなう形をとっています。

guichatServer.png

さて、このプログラムですが、通信データ構造はObject Serialization を使っています。 なので、ObjMessage2 クラスを改変すれば、各種データ構造の送受信に使えます(同じオブジェクトを更新して再送したい場合は、reset()をわすれずに)。 必要に応じて、改造して遊んでもらって構いません。