GUIを使うサーバー

GUIを使えば入力中でも受信できる?

Server05は接続をうけるとMessageのインスタンスを作って処理を任せ,受信に戻ります。今回の設計は不完全です。動きますが問題があります。作成の過程の試作品と考えてください。

サーバー

このサーバーも 10001 ポートで待ちます。

プログラム名 Server05.java

/** 受信後返信ウィンドウを呼び出すサーバー */
import java.net.*;
import java.io.*;
public class Server05 {
    public static void main( String[] args ) {
        int PORT = 10001;
        try {
            ServerSocket mysvsoc = new ServerSocket( PORT );
            while( true ) {
                System.out.println( "Server05 受信のため待機中..." );
                Socket mysoc = mysvsoc.accept();
                String remotehost = mysoc.getInetAddress().getHostName();
                System.out.println(remotehost + " からメッセージです。:");
                System.out.println( "返信中..." );
                // 処理をGUIプログラムに委託
                new SendMessage(mysoc);
            }
        }
        catch( IOException e ) {
            System.err.println("IO Error (ServerSocket or Socket)");
        }
    }
}

委託されるGUIプログラム

プログラム名 SendMessage.java

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

public class SendMessage extends JFrame implements ActionListener {
    /*フィールド*/
    JButton sndbttn;
    JTextField sndmsg;
    Socket mysoc;
    /* コンストラクタ*/
    public SendMessage(Socket socket){
        mysoc = socket;
        setTitle("SendMessage "+Thread.currentThread().getName());
        JLabel aite = new JLabel("相手機: "+mysoc.getInetAddress().getHostName());
        JLabel rcvmsg  = new JLabel("受信文: ");
        setDefaultCloseOperation(DISPOSE_ON_CLOSE); //終了処理を追加

        sndmsg = new JTextField(20);
        sndbttn = new JButton("返信");
        setLayout(new GridLayout(4,1,0,0));
        add(aite);
        add(rcvmsg);
        add(sndmsg);
        add(sndbttn);
        
        sndbttn.addActionListener(this);

        try{
            InputStreamReader in 
               = new InputStreamReader(mysoc.getInputStream());
            BufferedReader inb = new BufferedReader(in);
            String line;
            line = inb.readLine();
            rcvmsg.setText("受信文: "+line);
            pack();
            setVisible(true);
            // ここでin,inb をcloseすると支障あり
        }
        catch( IOException e ) {
            System.err.println("IO Error");
        }
    }

    /* イベントがあったらここに来る */
    public void actionPerformed(ActionEvent ev) {
        try{
            if (ev.getSource() == sndbttn) {
                OutputStreamWriter out
                  = new OutputStreamWriter(mysoc.getOutputStream());
                BufferedWriter outb = new BufferedWriter(out);
                String henji = sndmsg.getText();
                outb.write( henji );
                outb.newLine();
                outb.close();
                out.close();
                mysoc.close();
                dispose();
            }
        }
        catch( IOException ex ) {
                System.err.println("IO Error");
        }
    }
}

クライアント

プログラム名 Client04.java

クライアントは同じものが使えます。

動作テスト

まずサーバーを起動します。実行すると次のようになって接続を待ちます

(サーバー)$ java Server05
Server05 受信のため待機中...

別の端末から、クライアントを実行します。

(クライアント)$ java Client04 vineXX Konnichihwa.
vineXX に接続しました 返信を待っています。

Server04に通信した時と同じです。引数として通信したいホストの名前と、通信内容を引数として入れます。まずは自分の使っているコンピュータのホスト名でテストしましょう。

サーバーでは端末は次のようになって再び接続を待つようになります。

(サーバー)$ java Server05
Server05 受信のため待機中...
vineXX からメッセージです。:
返信中...
Server05 受信のため待機中...

同時にサーバーでは返信用のウィンドウが開きます。

返信用のウィンドウ

入力して返信してみます

textfieldに記入

クライアントにそのメッセージが帰ります。

(クライアント)$ java Client04 vineXX Konnichihwa.
vineXX に接続しました 返信を待っています。
なんでしょう<-
(クライアント)$ 

返信前にも受信

Server05では返信が終わる前に受信できます。受信のたびにウィンドウが開きます。

このウィンドウを「閉じる」ボタンで閉じてしまうとクライアント側では返信を待ち続けます。通信文が空でも返信ボタンを押してあげましょう。

委託されるGUIプログラムSendMessage.javaの解説

いつものJFrameを使っていますが、setDefaultCloseOperation(DISPOSE_ON_CLOSE); となっていていつもの終了処理と違います。このウィンドウが開いたときにServer05は次の受信にむけて待機していますから、EXITしては困るので返信ウィンドウを破棄するだけにしました。(クライアントは待ち続けますのでまずいといえはまずい)

引数で与えられたSocketからInputStreamを得て1行受信してラベルに表示し、テキストフィールドに返信を書くようにしむけてボタンのクリックを待ちます。

[返信]ボタンを押されるとOutputStreamを得て送信をし、Socketをclose()して自らはdispose()します。

このdispose()は「これらの Component のリソースが破棄され、それらの消費するメモリーが OS に戻ります。 」とリファレンスにありますが、実際にはServer05が動いている限り何らかの情報が残っています。したがってこの設計ではサーバーとしては長期間稼働させるとメモリを食っていくので支障があります。

inb,inをクローズしていませんが、getOutputStream()の前にクローズするとSocketExceptionが起こります。inb,inの宣言をフィールドに移してoutb,outのクローズのときに一緒にクローズすることは出来ますが、dispose()が上記の状態なのでマナーは悪いですが放置しています。Socketのクローズで一緒に破棄されるはずです。

dispose()で情報が残ることはシステムモニタで所要メモリが増えていくことで確認できますが、次のコードの追加でも確認できます。Server05のnew SendMessage(mysoc)を次のように変更すると、getWindows()で得られるWindowの配列数が増えていくのが表示されます。

SendMessage SMF = new SendMessage(mysoc);
System.out.println( SMF.getWindows().length  );
(サーバー)$  java Server05 
Server05 受信のため待機中...
vineXX からメッセージです。:
返信中...
1
Server05 受信のため待機中...
vineXX からメッセージです。:
返信中...
2
Server05 受信のため待機中...

inb,inのclose

// ここでin,inb をcloseすると支障あり なのでcloseしないままになっていました。フィールドで宣言すれば、outb.outと一緒にcloseすることが可能です。ただ、mysoc.close();するときに問題がでなければ一緒にcloseされると考えても良いはずです。また、 dispose();されることでメモリの開放もされるはずですから、よりマナーの良いプログラムとするということでしかありません。

ここでは宣言して、

    /*フィールド*/
    JButton sndbttn;
    JTextField sndmsg;
    Socket mysoc;
    InputStreamReader in;
    BufferedReader inb;

ここでは宣言しません。

        sndbttn.addActionListener(this);
        try{
            in  = new InputStreamReader(mysoc.getInputStream());
            inb = new BufferedReader(in);
            String line;
            line = inb.readLine();
            rcvmsg.setText("受信文: "+line);
            pack();
            setVisible(true);
            // ここでin,inb をcloseすると支障あり
        }

outb,outのあとにcloseします。

                outb.write( henji );
                outb.newLine();
                outb.close();
                out.close();
                inb.close();
                in.close();
                mysoc.close();
                dispose();

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