【Java】socketを用いたクライアント／サーバ間通信の基礎

# はじめに
表記の件の備忘録です。ネットワークと、スレッドについて学習したかったのでまとめました。

# ソケットを用いた通信の手順
1. サーバーでServerSocketを作成し、クライアントからの接続を待ち受ける
1. クライアントがソケットを作成すると、サーバーとクライアントの間で接続が確立される
1. サーバーが文字列を書き出し、それをクライアントが読み込む
1. ソケットをクローズする

# プログラム例
サーバ側を先に起動すること。
```java:サーバ側プログラム
package com.example;

import java.io.BufferedWriter;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

public final class Server {
    private Server() {
    }

    public static final int PORT = 10000;

    public static void main(String[] args) {
        try (ServerSocket serverSocket = new ServerSocket(PORT)) {
            System.out.println("待機します。");

            while (true) {
                Socket socket = serverSocket.accept(); // 接続を受け付ける
                System.out.println("ようこそ。");

                PrintWriter printWriter = new PrintWriter(
                        new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())));
                printWriter.println("こちらはサーバです。");
                printWriter.flush();
                printWriter.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
```
```java:クライアント側プログラム
package com.example;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.Socket;

public class Client {
    public static final String HOST = "localhost"; // 接続先のホスト名
    public static final int PORT = 10000; // 接続先のポート番号

    public static void main(String[] args) {
        try (Socket socket = new Socket(HOST, PORT)) {
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));

            String str = bufferedReader.readLine();
            System.out.println(str);

            bufferedReader.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
```
```sh:サーバ側出力
$ java <プログラム実行のコマンド>
待機します。
ようこそ。
ようこそ。
```
```sh:クライアント側出力
$ java <プログラム実行のコマンド>
こちらはサーバです。
$ java <プログラム実行のコマンド>
こちらはサーバです。
```

# スレッドも利用した通信
## 概要
Javaでは、スレッドはjava.lang.Threadクラスを使って作成する。スレッドを利用する方法は主に2つある。
1. Threadクラスを拡張する
    Threadクラスを拡張して、自分のスレッドクラスを作成する。`run()`メソッドをオーバライドし、`start()`でスレッドを起動する。
1. Runnableインターフェースを実装する
    Runnableインターフェースを実装して、Threadクラスに渡す。
## プログラム例
```java:サーバ側メインスレッド
public class Server {

    public static void main(String[] args) {
        ServerThread serverThread = new ServerThread();
        serverThread.start();

        while (true) {
            try {
                Thread.sleep(5000); // 5秒間スリープ
                System.out.println("サーバ側メインスレッドも起動中です。");
            } catch (InterruptedException e) {
                System.out.println("スリープが中断されました: " + e.getMessage());
            }
        }
    }
}
```
```java:サーバ側通信用スレッド
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerThread extends Thread {

    public static final int PORT = 10000;

    public ServerThread() {

    }

    public void run() {

        try (ServerSocket serverSocket = new ServerSocket(PORT)) {
            Socket socket = serverSocket.accept();

            try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                    PrintWriter printWriter = new PrintWriter(
                            new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())))) {

                String message;
                while ((message = bufferedReader.readLine()) != null) {
                    printWriter.println("サーバ：" + message + "受け付けました。");
                    printWriter.flush();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}
```
```java:クライアント側メインスレッド
public class Client {

    // メインスレッドは、コンソールから入力を受け付け、サーバーに送信する
    public static void main(String[] args) {

        // サーバから受信を行うスレッドを立ち上げる
        Thread thread = new Thread(new ClientThread());
        thread.start();

        while (true) {
            try {
                Thread.sleep(3000);
                System.out.println("クライアント側メインスレッドも起動中です");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

}
```
```java:クライアント側通信用スレッド
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;

public class ClientThread implements Runnable {

    public static final String HOST = "localhost";
    public static final int PORT = 10000;

    // サーバーからの文字列を受信し続け、コンソールに出力する
    public void run() {
        try (Socket socket = new Socket(HOST, PORT);
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
                BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in))) {

            System.out.println("サーバに接続しました。");
            String userInput;
            while (true) {
                System.out.print("サーバに送信するメッセージを入力してください。>");
                userInput = consoleReader.readLine();

                printWriter.println(userInput);
                printWriter.flush();

                String serverResponse = bufferedReader.readLine();
                System.out.println(serverResponse);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
```