リアルタイムチャットシステムの実装

リアルタイム通信の仕組みと設計

リアルタイム通信とは、あるクライアントが送信したメッセージを、他のすべての接続中のクライアントが即座に受信できる仕組みです。従来のモデルではクライアントからのデータはサーバーにのみ届く形でしたが、リアルタイム通信では、サーバーが受信したメッセージを他のすべてのオンラインクライアントに配信する必要があります。

この機能を実現するためには、ポート転送(または双方向接続)の設計思想が不可欠です。サーバー側では、各クライアントとの接続状態(Socket)を保持し、受信したメッセージをそのリストに含まれるすべてのソケットにブロードキャストします。

クライアント実装:メッセージ送信と受信の並行処理

クライアントは、ユーザー入力を受け付けながら、同時にサーバーから送られてくるメッセージも受信する必要があります。そのため、送信処理と受信処理を別スレッドで行うのが基本的なアプローチです。

package com.example.chat;

import java.io.*;
import java.net.Socket;
import java.util.Scanner;

public class ChatClient {
    private final Socket connection;
    private final Scanner inputScanner;

    public ChatClient(String host, int port) throws IOException {
        this.connection = new Socket(host, port);
        this.inputScanner = new Scanner(System.in);

        // メッセージ受信用スレッドの起動
        new MessageReceiver(connection).start();
    }

    public void startChat() {
        try (PrintStream out = new PrintStream(connection.getOutputStream())) {
            while (true) {
                System.out.print("メッセージを入力してください: ");
                String message = inputScanner.nextLine();
                if (!message.isEmpty()) {
                    out.println(message);
                    out.flush();
                }
            }
        } catch (IOException e) {
            System.err.println("送信エラー: " + e.getMessage());
        }
    }

    public static void main(String[] args) {
        try {
            ChatClient client = new ChatClient("127.0.0.1", 6868);
            client.startChat();
        } catch (IOException e) {
            System.err.println("クライアント起動失敗: " + e.getMessage());
        }
    }
}

受信スレッドの実装

受信用スレッドは、接続されたソケットからデータを読み取り、画面に出力します。

package com.example.chat;

import java.io.*;
import java.net.Socket;

public class MessageReceiver extends Thread {
    private final Socket socket;

    public MessageReceiver(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println("受信: " + line);
            }
        } catch (IOException e) {
            System.out.println("接続が切断されました。");
        }
    }
}

サーバー側の管理とメッセージ配信

サーバーは複数のクライアント接続を管理し、それぞれの接続情報を保持するリストを維持します。新しい接続が来た際にはリストに追加し、受信メッセージを全員に送信します。

package com.example.chat;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

public class ChatServer {
    private static final List<Socket> activeClients = new ArrayList<>();

    public static void main(String[] args) {
        try (ServerSocket server = new ServerSocket(6868)) {
            System.out.println("【サーバー起動】ポート 6868 で待機中...");

            while (true) {
                Socket clientSocket = server.accept();
                System.out.println("クライアント接続: " + clientSocket.getRemoteSocketAddress());

                // 接続済みリストに追加
                activeClients.add(clientSocket);

                // クライアントごとのメッセージ処理スレッドを開始
                new ClientHandler(clientSocket).start();
            }
        } catch (IOException e) {
            System.err.println("サーバーエラー: " + e.getMessage());
        }
    }

    // 全体にメッセージを送信するメソッド
    public static void broadcastMessage(String message) {
        for (Socket client : activeClients) {
            try (PrintStream out = new PrintStream(client.getOutputStream())) {
                out.println(message);
                out.flush();
            } catch (IOException e) {
                System.err.println("送信失敗: " + client.getRemoteSocketAddress());
                activeClients.remove(client); // 失敗したら削除
            }
        }
    }
}

クライアント処理スレッドの実装

クライアントからのメッセージを受信し、それを全員に配信する役割を担います。

package com.example.chat;

import java.io.*;
import java.net.Socket;

public class ClientHandler extends Thread {
    private final Socket clientSocket;

    public ClientHandler(Socket clientSocket) {
        this.clientSocket = clientSocket;
    }

    @Override
    public void run() {
        try (BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
             PrintStream out = new PrintStream(clientSocket.getOutputStream())) {

            String message;
            while ((message = in.readLine()) != null) {
                System.out.println("受信: " + message);
                // 他の全クライアントにメッセージをブロードキャスト
                ChatServer.broadcastMessage(message);
            }

        } catch (IOException e) {
            System.err.println("クライアント接続終了: " + clientSocket.getRemoteSocketAddress());
            activeClients.remove(clientSocket);
        }
    }
}

この構成により、複数のクライアントが同時に会話を行うリアルタイムチャットシステムが実現可能です。接続の追加・削除、エラーハンドリング、並行処理の適切な管理が、安定性の鍵となります。

タグ: Java socket リアルタイム通信 チャットアプリ スレッド同期

9月18日 17:30 投稿