Servlet日記(2000/03/12)

本日の成果

 今回は、ちょっと気分を変えて、Socketを直接用いてApplet-Servlet間で通信してみます。

 Appletの主要部分は、こんな感じ。Servlet日記(1999/12/27)に比べて、Socketを直接使うので幾分簡単になっていますが、あまり変わり映えしません。Socketはポート12345を決めうちで使っています。

    String out = null;
    try {
        Socket sock = new Socket(getCodeBase().getHost(), 12345);

        ObjectOutputStream oos = new ObjectOutputStream(sock.getOutputStream());
        oos.writeObject(inString.getText());
        oos.flush();

        ObjectInputStream ois = new ObjectInputStream(sock.getInputStream());
        out = (String)ois.readObject();

        oos.close();
        ois.close();
        outString.setText(out);
        sock.close();
    } catch (Exception e) {
        e.printStackTrace();
    }

 Servletの方は、こんな感じ。これまでとは違って、Socketサーバを作らないといけないので、Webブラウザからの指示に応じてServletからサーバのスレッドの起動・終了を行っています。サーバでは、Appletからの要求をaccept()で待ち、要求が来ればそれを処理するスレッドを起動します。最終的に起動されるスレッドは、Appletからの要求を処理して(この部分は前回と同じ)、完了すればスレッド自身も終了します。もはや、これをServletと呼ぶのか(形式的にはそうなんですけど)、という気もします。

public class SocketServlet extends HttpServlet {
    private SocketServletServer mainThread = null;
    private boolean serverRunning = false;

    public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        res.setContentType("text/html; charset=Shift_JIS");
        PrintWriter out = res.getWriter();

        out.println("<html><head><title>SocketServlet</title></head>");
        if ("start".equals(req.getParameter("cmd"))) {
            startServer();
            out.println("<body>SocketServletを起動しました。</body>");
        } else if ("stop".equals(req.getParameter("cmd"))) {
            stopServer();
            out.println("<body>SocketServletを停止しました。</body>");
        } else {
            if (serverRunning)
                out.println("<body>SocketServletは実行中です。</body>");
            else
                out.println("<body>SocketServletは停止中です。</body>");
        }
        out.println("</html>");
        out.close();
    }

    synchronized private void startServer() {
        if (null == mainThread) {
            mainThread = new SocketServletServer();
            mainThread.start();
        }
        serverRunning = true;
    }

    synchronized private void stopServer() {
        if (mainThread != null) {
            mainThread.loopFlag = false;
            mainThread.interrupt();
            mainThread = null;
        }
        serverRunning = false;
    }
}

class SocketServletServer extends Thread {
    private ServerSocket socket;
    public boolean loopFlag;

    public void run() {
        Socket con = null;

        try {
            socket = new ServerSocket(12345);
            socket.setSoTimeout(10000);
            System.out.println("Socket opened");
        } catch (IOException e) {
            e.printStackTrace();
            return;
        }

        for (loopFlag = true; loopFlag;) {
            try {
                con = socket.accept();
                System.out.println("Socket accepted");
                new ProcessRequest(con).start();
            } catch (Exception e) {
            }
        }
        try {
            socket.close();
            System.out.println("Socket closed");
        } catch (Exception e) {
        }
    }
}

class ProcessRequest extends Thread {
    Socket socket;

    ProcessRequest(Socket con) {
        this.socket = con;
    }

    public void run() {
        try {
            ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
            String s = (String)in.readObject();
            ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
            out.writeObject(s.toUpperCase());
            out.flush();
            in.close();
            out.close();
            socket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 問題は、サーバがaccept()で待っているときに、Webブラウザからサーバの終了が要求された場合です。APIドキュメントによれば、スレッドのstop()メソッドは使うな、ということなので、interrupt()メソッドでブロックされているaccept()に割り込もうとしています。が、なぜか割り込めません。調べてみると、Bug ParadeにWindows上では、Socket操作でブロックされている時はinterrupt()では割り込めないとありました(が、その番号がわからなくなってしまいました^^;)が、これがそうなんでしょうか。結局どうすればよいかよく分からないので、ここではsetSoTimeout()メソッドを呼び出して、accept()が10秒でタイムアウトするようにしました。不細工ですが。

 使い方は、まずWebブラウザからServletにアクセスします。その時のURLに、?cmd=startを付けるとサーバが起動し、?cmd=stopを付けるとサーバは終了します。サーバが起動した状態で、Appletを立ち上げて文字列を入力して変換ボタンを押すと、大文字に変換します。サーバが終了した状態では、もちろん正しく動作しません。

本日の教訓

 Socketを使ってAppletとServletが通信できました。しかし、こういう実装(Servletが直接通信する訳ではない)でも、Applet-Servlet間通信というんでしょうか?

おまけ

 来場者数がのべ4000人を超えた記念に、ちょっとプレゼントを。手違いで、Professional Java Server Programmingが2冊あるので、1冊差し上げます。送料着払いで送るか、大阪付近なら手渡しでどうでしょう。(←終了しました)。いい本のようですが、英語で1000ページ以上あるので、全部読むのは大変かも(私は、まだ1%くらいしか読んでません ^^;)。


Servlet日記(2000/03/05) Servlet日記の目次 Servlet日記(2000/03/19)