채팅 프로그램은 네트워크 프로그래밍의 기본기를 익히기에 좋은 프로젝트입니다. 자바(Java)는 표준 라이브러리에서 소켓(Socket)과 입출력 스트림을 제공해 TCP 기반 통신을 쉽게 구현할 수 있습니다. 여기에 멀티스레드를 활용하면 다수의 클라이언트를 동시에 처리할 수 있는 간단한 서버-클라이언트 채팅 시스템을 만들 수 있습니다. 이번 글에서는 TCP 소켓, 멀티스레드, 서버라는 세 가지 핵심 키워드를 중심으로 자바 채팅 프로그램을 단계별로 구현하는 방법을 살펴보겠습니다.
TCP 소켓을 이용한 통신 구조
채팅 프로그램은 기본적으로 서버와 클라이언트가 네트워크를 통해 메시지를 주고받는 구조입니다. 자바에서는 ServerSocket
과 Socket
클래스를 사용해 TCP 기반 통신을 구현할 수 있습니다. TCP는 연결 지향적이어서 데이터가 손실 없이 순서대로 전달되는 장점이 있어 채팅과 같은 실시간 메시징에 적합합니다.
서버 코드는 다음과 같이 시작할 수 있습니다.
import java.net.*;
import java.io.*;
public class ChatServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(12345);
System.out.println("서버 시작. 클라이언트 접속 대기 중...");
Socket clientSocket = serverSocket.accept();
System.out.println("클라이언트 접속 완료!");
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);
out.println("서버 응답: " + message);
}
serverSocket.close();
clientSocket.close();
}
}
위 코드는 단일 클라이언트와만 통신할 수 있는 구조입니다. 서버 소켓은 특정 포트에서 클라이언트 요청을 기다리다가, 접속이 들어오면 소켓을 생성해 입출력 스트림으로 데이터를 주고받습니다.
클라이언트 코드는 다음과 같이 작성할 수 있습니다.
import java.net.*;
import java.io.*;
public class ChatClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("localhost", 12345);
BufferedReader userInput = new BufferedReader(new InputStreamReader(System.in));
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
String message;
while ((message = userInput.readLine()) != null) {
out.println(message);
System.out.println("서버: " + in.readLine());
}
socket.close();
}
}
이 코드로 서버와 클라이언트가 서로 메시지를 주고받을 수 있습니다. 하지만 단일 접속만 가능하기 때문에 여러 사용자가 동시에 대화하려면 구조를 확장해야 합니다.
멀티스레드를 통한 동시 처리
채팅 프로그램의 핵심은 다수의 클라이언트를 동시에 처리하는 것입니다. 이를 위해 서버는 클라이언트가 접속할 때마다 새로운 스레드를 생성해 독립적으로 통신을 처리해야 합니다.
다음은 멀티스레드 기반 서버 코드의 예입니다.
import java.net.*;
import java.io.*;
import java.util.*;
public class MultiChatServer {
private static Set<PrintWriter> clientWriters = Collections.synchronizedSet(new HashSet<>());
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(12345);
System.out.println("멀티채팅 서버 시작!");
while (true) {
Socket socket = serverSocket.accept();
new ClientHandler(socket).start();
}
}
private static class ClientHandler extends Thread {
private Socket socket;
private PrintWriter out;
private BufferedReader in;
public ClientHandler(Socket socket) {
this.socket = socket;
}
public void run() {
try {
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(), true);
clientWriters.add(out);
String message;
while ((message = in.readLine()) != null) {
System.out.println("메시지 수신: " + message);
for (PrintWriter writer : clientWriters) {
writer.println(message);
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
socket.close();
} catch (IOException e) {}
clientWriters.remove(out);
}
}
}
}
이 구조에서 서버는 클라이언트가 접속할 때마다 ClientHandler
라는 스레드를 생성합니다. 각 스레드는 메시지를 읽어 서버 콘솔에 출력하고, 연결된 모든 클라이언트에게 메시지를 전달합니다. 이 방식은 채팅방처럼 모든 참가자가 같은 메시지를 공유하는 구조를 만들 수 있습니다.
클라이언트도 스레드를 이용해 입력 전용과 출력 전용 작업을 분리하면 사용자 경험이 좋아집니다. 하나의 스레드는 서버에 메시지를 보내고, 다른 스레드는 서버에서 메시지를 받아 출력하는 식입니다. 이렇게 하면 입력과 출력을 동시에 처리할 수 있습니다.
서버 관리와 확장 포인트
멀티스레드 채팅 서버는 기본적인 채팅 기능을 지원하지만, 실무에서는 다음과 같은 확장이 필요합니다.
- 닉네임 관리 접속한 클라이언트마다 고유한 닉네임을 부여하면 대화 내용을 구분할 수 있습니다.
- 메시지 포맷 정의 단순히 문자열만 주고받는 것이 아니라 JSON 형식으로 구조화하면, 메시지 종류(채팅, 공지, 시스템 메시지)를 구분할 수 있습니다.
- 클라이언트 종료 처리 사용자가 종료했을 때 다른 클라이언트에게 알리고, 리소스를 정리하는 과정이 필요합니다.
- 보안 고려 TCP는 기본적으로 암호화되지 않기 때문에 SSL 소켓을 적용하거나, 인증 기능을 추가하면 보안을 강화할 수 있습니다.
- GUI 클라이언트 확장 현재 코드는 콘솔 기반이지만, Swing이나 JavaFX를 활용하면 그래픽 인터페이스 채팅 프로그램으로 발전시킬 수 있습니다.
결론: 채팅 프로그램은 네트워크 학습의 지름길
자바 채팅 프로그램은 TCP 소켓, 멀티스레드, 서버 관리라는 세 가지 주제를 자연스럽게 학습할 수 있는 훌륭한 프로젝트입니다. 단일 접속에서 시작해 멀티스레드 서버로 확장하면서 네트워크 프로그래밍의 기본 개념을 익힐 수 있습니다. 또한 닉네임 관리, 메시지 포맷, 보안, GUI 확장 같은 요소를 추가하면 실제 메신저와 유사한 기능으로 발전할 수 있습니다.
중요한 것은 처음부터 완벽한 프로그램을 만드는 것이 아니라, 작은 단계로 나누어 점진적으로 확장하는 학습 방법입니다. 이렇게 하면 네트워크 프로그래밍의 원리를 체득하면서 실전 감각을 키울 수 있고, 동시에 자바 언어의 활용 능력도 크게 향상됩니다.