소개
멀티스레드와 소켓 통신 기술을 활용하여 실시간 데이터 교환이 가능한 서버 아키텍처를 설계해보겠습니다. 텍스트 기반 메시지를 여러 클라이언트에게 동시 전송하는 기능을 중심으로 동작 원리를 설명합니다.
시스템 구조 설계
본 구현체의 핵심은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();
}
}