GUIを使うクライアント

GUIクライアントの最初のアイディア

端末から引数に相手のホスト名とメッセージを入れて起動する代わりに、GUIにして枠内にホスト名とメッセージを入れてボタンで送信するようにしてみます。

このクライアントはまだまだ欠陥がありますが、動きます。どこに問題があるかわかるでしょうか。

どんな改良をすれば便利になるでしょうか。

使う時には、Server03,Server031,Server04,Server05 を相手サーバーとして使用できます。他の人とのメッセージ交換にはServer05が優れていますが、動作テストには、Server03やServer031も使えます。

プログラム名 Client05.java

/** GUIで送受信するクライアント */
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;

public class Client05 extends JFrame implements ActionListener {
    JTextField aite;
    JTextField sndmsg;
    JButton sndbttn;
    int PORT = 10001;
    public Client05() {
        setTitle("Client05 "+Thread.currentThread().getName());
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        aite     = new JTextField(20);
        sndmsg   = new JTextField(20);
        sndbttn  = new JButton("送信");
        setLayout(new GridLayout(3,1,0,0));
        add(aite);
        add(sndmsg);
        add(sndbttn);
        sndbttn.addActionListener(this);
        pack();
        setLocationRelativeTo(null);
        setVisible(true);
    }
    @Override
    public void actionPerformed(ActionEvent ev) {
        if (ev.getSource() == sndbttn) {
            try{
                Socket mysoc = new Socket( aite.getText(), PORT );
                InputStreamReader in 
                     = new InputStreamReader(mysoc.getInputStream());
                BufferedReader inb  = new BufferedReader(in);
                OutputStreamWriter out
                     = new OutputStreamWriter(mysoc.getOutputStream());
                BufferedWriter outb = new BufferedWriter(out);

                String msg = sndmsg.getText();
                outb.write( msg );
                outb.newLine();
                outb.flush();
                sndmsg.setText( "送信しました。返信を待っています..." );//見ることはない
                String line;
                line = inb.readLine();
                sndmsg.setText( line  );

                outb.close();
                out.close();
                inb.close();
                in.close();
                mysoc.close();
            }
            catch( UnknownHostException ex ) {
                System.err.println( "Host not found" );
            }
            catch( IOException ex ) {
                System.err.println("IO Error");
            }
        }
    }
    public static void main( String[] args ) {
        Client05 frm = new Client05();
    }
}

動作テスト

端末から起動しますが今回は引数は不要です。

(クライアント)$ java Client05

送信用のウィンドウが開きます。

送信用GUI

上のフィールドには相手のホスト名、下には通信文を入れて送信ボタンを押します。

送信用GUIの返信待ち

actionPerformed内でサーバーからの返信を待ちますから、ボタンは押された状態のままになります。

サーバーからの返信があると次のように真ん中の欄に表示されます。

送信用GUIの返信を受けた後

[×]ボタンで終了しますが、通信文を入れなおして再び送信することもできます。

イベントの処理に長い時間がかかるものは入れてはいけないと言われていますが、今回はボタンがひとつしかありませんから、相手の返信を待っているつもりならこれでも問題はないでしょう。

ただ、フレーム内の書き換えはイベント処理が終わってからになります。「送信しました。返信を待っています...」という表示をとようとしていますが、これは返信文で上書きされてしまい表示されることはありません。

スレッドを使うクライアント

Client05b.javaは次のRecvMessageByTh.javaのインスタンスを作り、スレッドとしてスタートさせます。

プログラム名 Client05b.java

/** GUIで送受信するクライアント */
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;

public class Client05b extends JFrame implements ActionListener {
    JTextField aite;
    JTextField sndmsg;
    JButton sndbttn;
    int PORT = 10001;
    public Client05b(){
        setTitle("Client05b "+Thread.currentThread().getName());
        setDefaultCloseOperation(EXIT_ON_CLOSE); //終了処理を追加
        aite   = new JTextField(20);
        sndmsg = new JTextField(20);
        sndbttn = new JButton("送信");
        setLayout(new GridLayout(3,1,0,0));
        add(aite);
        add(sndmsg);
        add(sndbttn);
        sndbttn.addActionListener(this);
        pack();
        setLocationRelativeTo(null);
        setVisible(true);
    }
    public static void main( String[] args ) {
        new Client05b();
    }
    @Override
    public void actionPerformed(ActionEvent ev) {
        try{
            if (ev.getSource() == sndbttn) {
                Socket mysoc = new Socket( aite.getText(), PORT );
                OutputStreamWriter out
                  = new OutputStreamWriter(mysoc.getOutputStream());
                BufferedWriter outb = new BufferedWriter(out);
                String msg = sndmsg.getText();
                outb.write( msg );
                outb.newLine();
                outb.flush();
                  // ここでout,outb をcloseすると支障あり
                RecvMessageByTh th = new RecvMessageByTh(mysoc);
                th.setLocationRelativeTo(this);
                new Thread(th).start();
                  //ここでもout,outb をcloseすると支障あり
                //dispose();
            }
        }
        catch( UnknownHostException ex ) {
                System.err.println( "Host not found" );
        }
        catch( IOException ex ) {
                System.err.println("IO Error");
        }
    }
}

RecvMessageByTh.javaはJFrameを継承している(extends JFrame)ので、Threadの継承(extends Thread)をしたMoveDiskとは違ったスレッド化をしています。JFrameの継承をやめてThreadの継承とする方法もありますがいままでのやり方にあわせてスレッド化のもうひとつの方法として記載しておきます。

dispose()をコメントアウトしています。このままだと送信後もこのウィンドウは画面に残り、返信が来る前でもさらにメッセージを送ることができます。

dispose()を有効にすると送信は1度きりであとは返信を待つだけになります。

プログラム名 RecvMessageByTh.java

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;

public class RecvMessageByTh extends JFrame implements Runnable {
    Socket mysoc;
    JLabel rcvmsg;
    public RecvMessageByTh(Socket socket){
        mysoc = socket;
        setTitle("RecvMessageByTh "+Thread.currentThread().getName());
        JLabel aite = new JLabel("相手機: "+mysoc.getInetAddress().getHostName());
        rcvmsg  = new JLabel("返信を待っています...");
        setDefaultCloseOperation(DISPOSE_ON_CLOSE); //EXITでは親も死ぬ
        setLayout(new GridLayout(2,1,0,0));
        add(aite);
        add(rcvmsg);
        Dimension ps = rcvmsg.getPreferredSize();
        ps.width = 224;
        rcvmsg.setPreferredSize(ps);
        pack();
        setVisible(true);
     }
     @Override
     public void run(){
        try{
            InputStreamReader in 
               = new InputStreamReader(mysoc.getInputStream());
            BufferedReader inb = new BufferedReader(in);
            String line;
            line = inb.readLine();
            rcvmsg.setText("受信文: "+line);
            //pack();
            //setVisible(true);
            inb.close();
            in.close();
            mysoc.close();
        }
        catch( IOException ex ) {
            System.err.println("IO Error");
        }
    }
}

RecvMessageByTh.javaはJLabel2つだけからできていて、サーバーからの返信が来たらラベルに表示します。クローズボタンが押されるまで画面に出たままになります。

ps.width = 224;の前後の部分はJLabelの幅をJTextField(20)に合わせているものです。高さを自動にするためにgetPreferredSize()で高さを取得し、幅だけを指定しています。

setVisible(true);の位置により 返信を表示するウインドウを前もって表示しておくか返信が着いたときに表示をするかが変わります。現在は前もって表示します。

スレッドを使うクライアントの動作テスト

端末から起動します。引数は不要です。

(クライアント)$ java Client05b

送信用のウィンドウが開きます。

送信用GUI

上のフィールドには相手のホスト名、下には通信文を入れて送信ボタンを押します。

送信用GUIにホスト名と通信文を記入

この送信用ウィンドウは送信後、返信を待ちながらも更に通信文を送信できます。

setVisible(true);の位置により 「待っています」の表示が出ます。

返信を待つという表示

返信がサーバーから来ると表示します。

返信文の表示


Javaプログラミング
聖愛中学高等学校
http://www.seiai.ed.jp/
Dec.2008
Nov.2012