【プログラミングしてみよう65】Javaのネットワークプログラミング入門(Socket通信)

Java

今回は「Socket通信」について解説します。 SocketはWebやチャットアプリの裏側で使われている低レベルなネットワーク通信の仕組みです。標準ライブラリだけで、サーバーとクライアントが直接やり取りするプログラムを作れます。

Socket通信の基本

「Socket(ソケット)」とはネットワーク通信の接続口のことです。サーバーとクライアントがそれぞれSocketを持ち、その間でデータを送受信します。

サーバー側:
1. ServerSocketで指定したポートで待ち受ける
2. クライアントが接続してきたらSocketを受け取る
3. InputStream/OutputStreamでデータを送受信する

クライアント側:
1. Socketでサーバーのアドレスとポートを指定して接続する
2. InputStream/OutputStreamでデータを送受信する

HTTPやWebSocketなど多くのプロトコルがSocketの上に構築されています。今回は生のSocket通信を学ぶことで、ネットワーク通信の仕組みを理解しましょう。

サンプルコード(シンプルなサーバー・クライアント)

ファイル作成

「Java53」フォルダに SimpleServer.java、SimpleClient.java という名前でファイルを作成します

コード入力

SimpleServer.java に以下のコードを入力します。

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

public class SimpleServer {
    public static void main(String[] args) throws Exception {
        int port = 8888;
        System.out.println("サーバー起動: ポート " + port + " で待ち受け中...");

        try (ServerSocket serverSocket = new ServerSocket(port)) {
            // クライアントからの接続を待つ
            Socket clientSocket = serverSocket.accept();
            System.out.println("クライアントが接続しました: " + clientSocket.getInetAddress());

            // 受信用と送信用のストリームを用意
            BufferedReader in  = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            PrintWriter    out = new PrintWriter(clientSocket.getOutputStream(), true);

            // クライアントからのメッセージを受け取り、返信する
            String message;
            while ((message = in.readLine()) != null) {
                System.out.println("受信: " + message);
                if (message.equals("bye")) {
                    out.println("さようなら!");
                    break;
                }
                out.println("サーバーより: " + message.toUpperCase());
            }

            System.out.println("接続を終了しました");
        }
    }
}

SimpleClient.java に以下のコードを入力します。

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

public class SimpleClient {
    public static void main(String[] args) throws Exception {
        String host = "localhost";
        int    port = 8888;

        System.out.println("サーバーに接続中: " + host + ":" + port);

        try (Socket socket = new Socket(host, port)) {
            System.out.println("接続しました");

            BufferedReader in  = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            PrintWriter    out = new PrintWriter(socket.getOutputStream(), true);

            // メッセージを送信して返信を受け取る
            String[] messages = {"hello", "java", "socket", "bye"};
            for (String msg : messages) {
                System.out.println("送信: " + msg);
                out.println(msg);
                String response = in.readLine();
                System.out.println("受信: " + response);
                if (msg.equals("bye")) break;
            }
        }

        System.out.println("接続を終了しました");
    }
}

コンパイル

javac SimpleServer.java SimpleClient.java

実行(ターミナルを2つ開いて試します)

ターミナル1(サーバー起動):

java SimpleServer

ターミナル2(クライアント起動):

java SimpleClient

サーバー側の出力:

サーバー起動: ポート 8888 で待ち受け中...
クライアントが接続しました: /127.0.0.1
受信: hello
受信: java
受信: socket
受信: bye
接続を終了しました

クライアント側の出力:

サーバーに接続中: localhost:8888
接続しました
送信: hello
受信: サーバーより: HELLO
送信: java
受信: サーバーより: JAVA
送信: socket
受信: サーバーより: SOCKET
送信: bye
受信: さようなら!
接続を終了しました

サンプルコード(マルチクライアント対応のチャットサーバー)

シンプルなサーバーは1つのクライアントしか受け付けられません。以前学んだマルチスレッドとExecutorServiceを組み合わせて、複数のクライアントが同時に接続できるチャットサーバーを作りましょう。

ファイル作成

「Java53」フォルダに ChatServer.java、ChatClient.java という名前でファイルを作成します

コード入力

ChatServer.java に以下のコードを入力します。

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ChatServer {
    // スレッドセーフなSetで全クライアントを管理
    private static final Set<PrintWriter> clients = new CopyOnWriteArraySet<>();

    public static void main(String[] args) throws Exception {
        int port = 9999;
        System.out.println("チャットサーバー起動: ポート " + port);

        ExecutorService pool = Executors.newCachedThreadPool();

        try (ServerSocket serverSocket = new ServerSocket(port)) {
            while (true) {
                Socket clientSocket = serverSocket.accept();
                pool.execute(() -> handleClient(clientSocket));
            }
        }
    }

    static void handleClient(Socket socket) {
        PrintWriter out = null;
        try {
            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            out = new PrintWriter(socket.getOutputStream(), true);
            clients.add(out);

            // 最初の行はユーザー名
            String userName = in.readLine();
            System.out.println(userName + " が参加しました");
            broadcast(userName + " が参加しました", null);

            String message;
            while ((message = in.readLine()) != null) {
                if (message.equals("/quit")) break;
                System.out.println("[" + userName + "]: " + message);
                broadcast("[" + userName + "]: " + message, null);
            }

            broadcast(userName + " が退出しました", null);
            System.out.println(userName + " が退出しました");
        } catch (Exception e) {
            System.out.println("クライアントエラー: " + e.getMessage());
        } finally {
            if (out != null) clients.remove(out);
            try { socket.close(); } catch (Exception ignored) {}
        }
    }

    // 全クライアントにメッセージを送信
    static void broadcast(String message, PrintWriter sender) {
        for (PrintWriter client : clients) {
            if (client != sender) {
                client.println(message);
            }
        }
    }
}

ChatClient.java に以下のコードを入力します。

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

public class ChatClient {
    public static void main(String[] args) throws Exception {
        String host = "localhost";
        int    port = 9999;

        Scanner scanner = new Scanner(System.in);
        System.out.print("ユーザー名を入力してください: ");
        String userName = scanner.nextLine();

        try (Socket socket = new Socket(host, port)) {
            BufferedReader in  = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            PrintWriter    out = new PrintWriter(socket.getOutputStream(), true);

            // ユーザー名を送信
            out.println(userName);

            // 受信スレッド: サーバーからのメッセージを表示
            Thread receiver = new Thread(() -> {
                try {
                    String message;
                    while ((message = in.readLine()) != null) {
                        System.out.println(message);
                    }
                } catch (Exception e) {
                    System.out.println("切断されました");
                }
            });
            receiver.setDaemon(true);
            receiver.start();

            // メインスレッド: ユーザーの入力を送信
            System.out.println("チャット開始 (/quit で退出)");
            while (scanner.hasNextLine()) {
                String input = scanner.nextLine();
                out.println(input);
                if (input.equals("/quit")) break;
            }
        }
    }
}

コンパイル

javac ChatServer.java ChatClient.java

実行(ターミナルを3つ開いて試します)

ターミナル1(サーバー起動):

java ChatServer

ターミナル2(クライアント1):

java ChatClient

ターミナル3(クライアント2):

java ChatClient

サーバー側の出力例:

ターミナル2に「ぱんまる」を入力

ターミナル3に「たろう」を入力

チャットサーバー起動: ポート 9999
ぱんまる が参加しました
たろう が参加しました

ターミナル2に「こんにちは!」を入力

ターミナル3に「よろしく」を入力

[ぱんまる]: こんにちは!
[たろう]: よろしく

ターミナル2を閉じる

ぱんまる が退出しました

コードの解説

ServerSocket は指定したポートでクライアントからの接続を待ち受けます。serverSocket.accept() はクライアントが接続するまでブロック(待機)し、接続が来たら Socket を返します。try-with-resources で囲むことで、プログラム終了時に自動的に閉じられます。

シンプルなサーバーでは1つのクライアントしか処理できませんが、チャットサーバーでは ExecutorService のスレッドプールを使い、クライアントごとに別スレッドで handleClient を実行することで複数のクライアントを同時に処理できます。

CopyOnWriteArraySet はスレッドセーフなSetで、複数のスレッドから同時にアクセスされても安全に動作します。以前学んだスレッドセーフの知識がここで活きています。

ChatClient では受信と送信を別スレッドで行っています。receiver スレッドが setDaemon(true) に設定されているのは、メインスレッド(送信)が終了したときに受信スレッドも自動的に終了させるためです。

まとめ

Socket通信を使うことで、標準ライブラリだけでサーバーとクライアントがリアルタイムにデータをやり取りするプログラムが作れます。ServerSocket でサーバー側が待ち受け、Socket でクライアントが接続し、InputStream/OutputStream でデータを送受信するという基本の流れを覚えておきましょう。マルチスレッドと組み合わせることで複数クライアントへの対応も可能になります。これまで学んだマルチスレッド・ExecutorService・スレッドセーフの知識がすべて活きる実践的な内容です。

2026/08/15(金) ぱんまる

コメント

タイトルとURLをコピーしました