Java Network 〜 Socket通信で受付を行う〜

Socket通信処理の受付

Socket通信処理の実装を行いました。Githubに下のようなコードをコミットしてあります。

今回の内容について

  • まずはコードを見る
  • 処理の概要について
  • 通信処理について
  • 受信処理について
  • 感想

以上のような内容を記載します。

まずはコードを見る

下のように実装しました。
メインメソッドは一番下にあります。色々と試していたら長いコードになってしまいました。

/**
 * 低レベルSocketサーバー。
 * サーバーとクライアントの関係を理解するためのサンプルコード。
 * 単体アプリとする
 * @author 作成者の名前
 */
public class NaturalBornServlet extends Thread {
    private static final boolean DEBUG = true;
    /** 受け付けるおポート番号 */
    public static final int PORT_NO = 8081;
    /** 自分自身のインスタンス */
    private static NaturalBornServlet main;

    /** リクエスト */
    private BufferedReader request;
    /** レスポンス */
    private PrintWriter response;

    /** サーバーソケット */
    private ServerSocket server;
    /** 改行コード */
    private final char SEP = (char) 10;

    /**
     * 外部からの起動を禁止する。プライベートコンストラクタ。
     */
    private NaturalBornServlet() {
        try {
            server = new ServerSocket(PORT_NO);
        } catch (IOException e) {
            System.out.println("*** サーバーソケットの起動に失敗しました。 ***");
            e.printStackTrace();
            System.exit(-1);
        }
    }

    /**
     * デストラクタ。
     * サーバーソケットのクローズ、メモリの開放を行う。
     */
    public void finalize() {
        try {
            server.close();
        } catch (IOException e) {
            System.out.println("*** サーバーソケットの終了に失敗しました。 ***");
            e.printStackTrace();
        } finally {
            server = null;
            main = null;
            System.exit(-1);
        }
    }

    /**
     * 起動した、サーバーソケットで待機を行う。
     */
    public void run()  {
        try {
            System.out.println("*** サーバーソケット起動 ***");
            Socket sock = server.accept();
            // リクエスト
            request = new BufferedReader(new InputStreamReader(sock.getInputStream()));
            // レスポンス
            response = new PrintWriter(new OutputStreamWriter(sock.getOutputStream()));
            // リクエストの読み込み
            while(true) {
                String inputTxt = readRequest(request);

                if (inputTxt.contains("HTTP/1.1")) {
                    System.out.println("HTTP: " + inputTxt);
                    inputTxt = getHttpResponse(inputTxt);
                } else if ("".equals(inputTxt) ) {
                    System.out.println("***> 空リクエスト: " + inputTxt);
                    inputTxt = "<html><body>Hello World!</body></html>";
                } else {
                    System.out.println("***> else: " + inputTxt);
                    if (isBye(inputTxt)) {
                        break;
                    }
                }
                if (DEBUG) System.out.println("*** サーバーソケット: リクエスト受信 " + inputTxt + "***");

                // レスポンスを返す
                response.write(inputTxt + SEP);
                response.flush();
                System.out.println("*** サーバーソケット: レスポンス送信 " + inputTxt + "***");
                inputTxt = null;
//              request.close();
//              response.close();
//              sock.close();
//              request = null;
//              response = null;
//              break;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private boolean isBye(String inputTxt) {
        if (inputTxt != null && inputTxt.startsWith("bye")) {
            System.out.println("*** サーバーを終了します。 ***");
            return true;
        }
        return false;
    }

    private String getHttpResponse(String req) {
        StringBuilder response = new StringBuilder();

        response.append("200 OK" + SEP);
        response.append("Access-Control-Allow-Origin: *" + SEP);
        response.append("Connection: Keep-Alive" + SEP);
        response.append("Content-Encoding: gzip" + SEP);
        response.append("Content-Type: text/html; charset=utf-8" + SEP);
        response.append("Date: Mon, 18 Jul 2016 16:06:00 GMT" + SEP);
        response.append("Etag: \"c561c68d0ba92bbeb8b0f612a9199f722e3a621a\"" + SEP);
        response.append("Keep-Alive: timeout=5, max=997" + SEP);
        response.append("Last-Modified: Mon, 18 Jul 2016 02:36:04 GMT" + SEP);
        response.append("Server: NaturalBorn" + SEP);
        response.append("Set-Cookie: mykey=myvalue; expires=Mon, 17-Jul-2017 16:06:00 GMT; Max-Age=31449600; Path=/; secure" + SEP);
        response.append("Transfer-Encoding: chunked" + SEP);
        response.append("Vary: Cookie, Accept-Encoding" + SEP);
        response.append("X-Backend-Server: developer2.webapp.scl3.mozilla.com" + SEP);
        response.append("X-Cache-Info: not cacheable; meta data too large" + SEP);
        response.append("X-kuma-revision: 1085259" + SEP);
        response.append("x-frame-options: DENY" + SEP);

        return response.toString();
    }

    /**
     * 受信した、メッセージを読み込む。
     *
     * @param in Socketより取得した入力ストリーム
     * @return
     * @throws IOException
     */
    private String readRequest(BufferedReader in) throws IOException {
        if (DEBUG) System.out.println("*** サーバーソケット: readRequest() ***");
        int read = 0;
        StringBuilder inputTxt = new StringBuilder();
//      System.out.println("**** read() : " + in.read());
//      System.out.println("**** ready() : " + in.ready());
//      System.out.println("**** readLine() : " + in.readLine());

        // CRとCRLFの場合で入力が終了している時がある
        while((read = in.read()) > 0) {
            // 空の場合
            if (read == 10 || read == 13) {
                System.out.println("*** read break ***");
                break;
            }
            char ch = (char) read;
            inputTxt.append(ch);
        }
        if (DEBUG) System.out.println("*** サーバーソケット: 完了:readRequest(): " + inputTxt + " ***");
        return inputTxt.toString();
    }
    /**
     * メインメソッド、ここから始まる。
     * @param args
     */
    public static void main(String[] args) {
        main = new NaturalBornServlet();

        try {
            main.start();
        } catch (Exception e) {
            System.out.println("*** 例外が発生しました。 " + e.getMessage() + "***");
            e.printStackTrace();
        }

    }
}

処理の概要と受信処理

上記のコードは大まかに下のような処理を行っています。

  1. メインメソッドでメイン処理を実行します。このクラスはThreadクラスを継承しているので、マルチスレッド処理を行うことができます。

  2. メインメソッドでstart()`が呼び出されているので、`run()メソッドが起動します。

  3. run()`メソッドでは、ServerSocketで`accept()を呼び出し、リクエストの受付を行います。
    つまりは、リクエストを受け付けるまで処理を行わず待っている状態になります。

  4. リクエストをつけ付けたら次のコードに処理が進みます。Socketクラスを受け取り、さらに入力ストリームと出力ストリームを取得します。入力は受け付けたリクエストで、出力は返却するレスポンスになります。具体的には下のようなコードです。

    // リクエスト
    request = new BufferedReader(new InputStreamReader(sock.getInputStream()));
    // レスポンス
    response = new PrintWriter(new OutputStreamWriter(sock.getOutputStream()));
  5. リクエストを読み込み、受信した内容に応じてレスポンスを返します。

    // リクエストの読み込み
    while(true) {
    String inputTxt = readRequest(request);
    
    if (inputTxt.contains("HTTP/1.1")) {
        System.out.println("HTTP: " + inputTxt);
        inputTxt = getHttpResponse(inputTxt);
    } else if ("".equals(inputTxt) ) {
        System.out.println("***> 空リクエスト: " + inputTxt);
        inputTxt = "<html><body>Hello World!</body></html>";
    } else {
        System.out.println("***> else: " + inputTxt);
        if (isBye(inputTxt)) {
            break;
        }
    }
    if (DEBUG) System.out.println("*** サーバーソケット: リクエスト受信 " + inputTxt + "***");
    
    // レスポンスを返す
    response.write(inputTxt + SEP);
    response.flush();
    System.out.println("*** サーバーソケット: レスポンス送信 " + inputTxt + "***");
    inputTxt = null;
    }
  6. リクエストの読み込みを行っているのは下のコードです。

    /**
    * 受信した、メッセージを読み込む。
    *
    * @param in Socketより取得した入力ストリーム
    * @return
    * @throws IOException
    */
    private String readRequest(BufferedReader in) throws IOException {
    if (DEBUG) System.out.println("*** サーバーソケット: readRequest() ***");
    int read = 0;
    StringBuilder inputTxt = new StringBuilder();
    System.out.println("**** read() : " + in.read());
    System.out.println("**** ready() : " + in.ready());
    System.out.println("**** readLine() : " + in.readLine());
    
    // CRとCRLFの場合で入力が終了している時がある
    while((read = in.read()) > 0) {
        // 空の場合
        if (read == 10 || read == 13) {
            System.out.println("*** read break ***");
            break;
        }
        char ch = (char) read;
        inputTxt.append(ch);
    }
    if (DEBUG) System.out.println("*** サーバーソケット: 完了:readRequest(): " + inputTxt + " ***");
    return inputTxt.toString();
    }

    ポイントになるのは、文字列を読み込をするときにに読み込む文字がないことを確認するために「改行コード」の数値型の値を使用しているところです。改行コードのchar型の値は整数型(int)で表現すると「13」もしくは「10」になります。CRLFCRの存在があるため2つになります。

通信処理について

通信処理=レスポンスの送信処理部分に関しては出力ストリームを使用して、送信します。

// レスポンスを返す
response.write(inputTxt + SEP);
response.flush();
System.out.println("*** サーバーソケット: レスポンス送信 " + inputTxt + "***");

「inputTxt」はString型の変数で、リクエストの値を取得しています。
つまりはリクエストで受けた文字列をそのまま返却しているというわけです。

感想

この実装は、Socket通信の最も簡単な処理になります。いわゆる「Socket通信のハローワールド」を実装したようなものでなので、通信処理の土台になります、逆に言うとこれを基本にどのような処理も実装することが可能ということです。

それなりに大変なことがありますが。。。

まとめると、通信の基本というところです。これを理解すれば、いろいろな通信処理の実装を行うことも可能になります。
なぜかというと、通信の基本を学んだからです。

でわでわ。。。

関連ページ

Java Basic

  1. Java Basic Level 1 Hello Java
  2. Java Basic Level2 Arithmetic Calculate
  3. Java Basic Level3 About String class
  4. Java Basic Level 4Boolean
  5. Java Basic Level 5If Statement
  6. Java Basic Summary from Level1 to 5
  7. Java Basic Level 6 Traning of If statement
  8. Java Basic Level8 How to use for statement
  9. Java Basic Level 8.5 Array
  10. Java Basic Level 9Training of for statement
  11. Java Basic Level 10 While statement 
  12. Java Basic Swing〜オブジェクト指向〜
  13. Java Basic Swing Level 2〜オブジェクト指向2
  14. サンプル実装〜コンソールゲーム〜
  15. Java Basic インターフェース・抽象クラスの作り方
  16. Java Basic クラスとは〜Step2_1
  17. Java Basic JUnit 〜テストスイートの作り方〜