자바 네트워크 프로그래밍을 통한 기초 채팅 서버 개발

소개

멀티스레드와 소켓 통신 기술을 활용하여 실시간 데이터 교환이 가능한 서버 아키텍처를 설계해보겠습니다. 텍스트 기반 메시지를 여러 클라이언트에게 동시 전송하는 기능을 중심으로 동작 원리를 설명합니다.

시스템 구조 설계

본 구현체의 핵심은incoming connection 을 지속적으로 감지하고, 연결된 각 세션에 독립적인 실행 흐름을 부여하는 것입니다. 주요 클래스 구성은 다음과 같습니다.

  • ChatterServer: 메인 엔트리 포인트 및 포트 리스너 관리
  • SessionProcessor: 개별 접속자의 입출력 스트림 처리 (Runnable 인터페이스 구현)

메인 스레드는 서버 소켓을 생성하여 특정 포트에서 대기하며, 새로운 접속 발생 시 별도의 스레드를 할당하여 처리합니다.

서버 초기화 및监听 로직

주요 로직은 애플리케이션 시작과 동시에 수행됩니다. 지정된 포트 번호로 바인딩된 서버 소켓이 외부 요청을 수용할 준비 상태가 됩니다.

public class ChatterServer {

    private static final int PORT = 9000;
    // 접속 중인 모든 클라이언트의 출력 스트림을 저장
    private List<PrintWriter> activeOutputStreams = new ArrayList<>();

    public static void main(String[] args) {
        new ChatterServer().execute();
    }

    public void execute() {
        try {
            System.out.println("서버 시작: " + PORT);
            ServerSocket listener = new ServerSocket(PORT);
            
            while (true) {
                Socket incomingSocket = listener.accept();
                
                // 해당 클라이언트를 위한 출력 스트림 확보 후 목록에 등록
                PrintWriter outWriter = new PrintWriter(incomingSocket.getOutputStream(), true);
                activeOutputStreams.add(outWriter);
                
                // 수신 메시지 처리용 전용 스레드 생성
                Thread clientWorker = new Thread(new SessionProcessor(incomingSocket));
                clientWorker.start();
            }
        } catch (IOException e) {
            System.err.println("소켓 오류 발생: " + e.getMessage());
        }
    }
}

클라이언트 세션 처리기

내부 클래스 또는 별도 클래스로 분리되어 각 클라이언트의 입력을 읽습니다. 읽어들인 내용은 전체 그룹 채팅 메시지로 간주되어 다른 참여자들에게 전달됩니다.

class SessionProcessor implements Runnable {
    private final Socket currentSocket;

    public SessionProcessor(Socket socket) {
        this.currentSocket = socket;
    }

    @Override
    public void run() {
        String receivedLine;
        try (BufferedReader reader = new BufferedReader(
                     new InputStreamReader(currentSocket.getInputStream()))) {
            
            while ((receivedLine = reader.readLine()) != null) {
                System.out.println("수신: " + receivedLine);
                // 수신된 내용을 전파하는 메서드 호출
                propagateToAllPeers(receivedLine);
            }
        } catch (IOException exception) {
            System.out.println("연결 끊김: " + currentSocket.getInetAddress());
        }
    }
}

메시지 브로드캐스팅 로직

모든 활성 커넥션에 대해 순회하면서 동일한 데이터를 씁니다. 이 과정에서 스트림 버퍼를 강제로 비우는_flush_ 작업이 중요합니다.

private void propagateToAllPeers(String msg) {
    Iterator<PrintWriter> streamIterator = activeOutputStreams.iterator();
    
    while (streamIterator.hasNext()) {
        PrintWriter targetWriter = streamIterator.next();
        targetWriter.println(msg);
        targetWriter.flush();
    }
}

태그: java socket-communication Multithreading Networking tcp-ip

8월 14일 17:00에 게시됨