diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml
new file mode 100644
index 0000000..2b63946
--- /dev/null
+++ b/.idea/uiDesigner.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..71e59f6
--- /dev/null
+++ b/README.md
@@ -0,0 +1,119 @@
+# Java 소켓 기반 멀티 클라이언트 채팅 프로그램
+
+## 📖 프로젝트 소개
+이 프로젝트는 **Java 표준 라이브러리(`java.net`, `java.io`)만**을 사용하여 개발된 경량 텍스트 기반 채팅 애플리케이션입니다.
+동아리나 수업 환경에서 **별도의 외부 라이브러리 설치 없이**, 같은 네트워크 내 여러 사용자가 동시에 대화할 수 있는 간단한 채팅 도구를 목표로 합니다.
+서버-클라이언트 모델을 기반으로 하며, **스레드 풀**을 이용해 다수의 클라이언트를 효율적으로 처리합니다.
+
+---
+
+## 주요 기능
+- **다중 클라이언트 접속**: `ExecutorService`(스레드 풀)를 활용하여 안정적인 동시 접속 처리
+- **닉네임 시스템**
+ - 서버 접속 시 고유한 닉네임을 설정 (`NICK <이름>`)
+ - 중복 또는 공백이 포함된 닉네임 거절
+- **실시간 채팅**: 메시지를 모든 클라이언트에게 브로드캐스트
+- **입장/퇴장 알림**: 사용자 입장/퇴장 시 전체 알림
+- **기본 명령어**
+ - `/who` : 현재 접속자 목록 출력
+ - `/quit` : 정상 종료 및 연결 해제
+- **안정적인 종료 처리**: 서버가 `Ctrl+C`로 종료될 경우, 클라이언트에게 알림 후 안전 종료
+
+---
+
+## 사용 기술
+- **언어**: Java (JDK 11 이상 권장)
+- **핵심 라이브러리**
+ - `java.net.Socket`, `java.net.ServerSocket` : TCP/IP 기반 소켓 통신
+ - `java.io.*` : 데이터 입출력 스트림
+ - `java.util.concurrent.*` : 멀티스레딩 및 동시성 제어 (`ExecutorService`, `ConcurrentHashMap`)
+- **인코딩**: UTF-8 (한글 입출력 지원)
+
+---
+
+## 빌드 및 실행 방법
+
+### 1. 사전 준비
+- JDK가 설치되어 있어야 합니다.
+
+### 2. 컴파일
+`src` 폴더에 `ChatServer.java`, `ChatClient.java` 파일이 있다고 가정합니다.
+```bash
+javac -d out src/ChatServer.java src/ChatClient.java
+```
+
+→ 컴파일된 .class 파일은 out 디렉토리에 생성됩니다.
+
+### 3. 서버 실행
+```bash
+# 기본 포트(5000)
+java -cp out ChatServer
+
+# 특정 포트(예: 5001)
+java -cp out ChatServer 5001
+```
+
+```bash
+실행 시:
+
+[서버] 채팅 서버를 포트 5001에서 시작합니다.
+```
+
+### 4. 클라이언트 실행
+```bash
+# 형식
+java -cp out ChatClient <서버 IP> <포트> <닉네임>
+
+# 예시 1
+java -cp out ChatClient 127.0.0.1 5001 Yumi
+
+# 예시 2
+java -cp out ChatClient 127.0.0.1 5001 ShinSaegae
+```
+
+테스트 시나리오 (실행 예시)
+```bash
+1. 서버 실행
+[서버] 채팅 서버를 포트 5001에서 시작합니다.
+
+2. 클라이언트 1 (Yumi) 접속
+채팅 서버에 연결되었습니다 (127.0.0.1:5001)
+OK 채팅 서버에 오신 것을 환영합니다. 'NICK <닉네임>' 형식으로 닉네임을 설정해주세요.
+OK 닉네임이 'Yumi'으로 설정되었습니다.
+Yumi님이 채팅에 참여했습니다.
+
+
+서버 로그:
+
+[서버] /127.0.0.1:xxxxx 님이 'Yumi' 닉네임으로 접속했습니다.
+
+3. 클라이언트 2 (Bob) 접속
+OK 닉네임이 'Bob'으로 설정되었습니다.
+Yumi님이 채팅에 참여했습니다.
+Bob님이 채팅에 참여했습니다.
+
+
+(Yumi 클라이언트 창에도 "Bob님이 채팅에 참여했습니다." 출력)
+
+4. 대화 및 명령어
+
+(Yumi) 안녕하세요!
+→ 모든 클라이언트: [Yumi] 안녕하세요!
+
+(Bob) /who
+→ 현재 접속자: Yumi, Bob
+
+5. 클라이언트 종료
+
+(Yumi) /quit
+→ 모든 클라이언트: Yumi님이 채팅을 떠났습니다.
+
+6. 서버 강제 종료 (Ctrl+C)
+[서버] 종료 절차를 시작합니다...
+```
+```bash
+남은 클라이언트:
+
+SYSTEM: 서버가 곧 종료됩니다.
+[클라이언트] 서버와의 연결이 끊어졌습니다. (Disconnected)
+```
\ No newline at end of file
diff --git a/src/ChatClient.java b/src/ChatClient.java
new file mode 100644
index 0000000..7e10d38
--- /dev/null
+++ b/src/ChatClient.java
@@ -0,0 +1,78 @@
+import java.io.*;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.nio.charset.StandardCharsets;
+
+public class ChatClient {
+ public static void main(String[] args) throws IOException{
+ if (args.length != 3) {
+ System.out.println("사용법: java -cp out ChatClient <서버_IP> <포트> <닉네임>");
+ return;
+ }
+
+ String host = args[0];
+ int port = Integer.parseInt(args[1]);
+ String nickname = args[2];
+
+ try (Socket socket = new Socket(host, port)) {
+ System.out.println("채팅 서버에 연결되었습니다 (" + host + ":" + port + ")");
+
+ PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8), true);
+
+ Thread readerThread = new Thread(new ServerMessageReader(socket));
+ readerThread.start();
+
+ out.println("NICK " + nickname);
+
+ BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8));
+ String userInput;
+ while ((userInput = consoleReader.readLine()) != null) {
+ out.println(userInput);
+ if ("/quit".equalsIgnoreCase(userInput.trim())) {
+ break;
+ }
+ }
+ readerThread.join();
+
+ } catch (UnknownHostException e) {
+ System.err.println("호스트 " + host + "를 찾을 수 없습니다.");
+ } catch (IOException e) {
+ System.err.println(host + "에 연결할 수 없습니다. 서버가 실행 중인지 확인해주세요.");
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ System.err.println("클라이언트가 중단되었습니다.");
+ } finally {
+ System.out.println("서버와의 연결이 종료되었습니다.");
+ }
+ }
+
+ private static class ServerMessageReader implements Runnable {
+ private final Socket socket;
+ private BufferedReader in;
+
+ public ServerMessageReader(Socket socket) {
+ this.socket = socket;
+ }
+
+ @Override
+ public void run() {
+ try {
+ in = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
+ String serverMessage;
+ while ((serverMessage = in.readLine()) != null) {
+ System.out.println(serverMessage);
+ }
+ } catch (IOException e) {
+ System.out.println("서버와의 연결이 끊어졌습니다.");
+ } finally {
+ try {
+ if (!socket.isClosed()) {
+ socket.close();
+ }
+ } catch (IOException e) {
+ System.out.println(e.getMessage());
+ }
+ }
+ }
+ }
+}
diff --git a/src/ChatServer.java b/src/ChatServer.java
new file mode 100644
index 0000000..5402921
--- /dev/null
+++ b/src/ChatServer.java
@@ -0,0 +1,161 @@
+import java.io.*;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+
+public class ChatServer {
+ private static final int PORT = 5001;
+
+ private static final ExecutorService pool = Executors.newCachedThreadPool();
+
+ private static final Map clients = new ConcurrentHashMap<>();
+
+ public static void main(String[] args) {
+ System.out.println("[Server] port:" + PORT + " 에서 서버 실행");
+
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+ System.out.println("\n[서버] 종료를 시작합니다...");
+ broadcast("SYSTEM: 서버가 곧 종료됩니다.");
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ pool.shutdownNow();
+ System.out.println("[서버] 서버 강제 종료.");
+ }));
+
+ try (ServerSocket serverSocket = new ServerSocket(PORT)) {
+ while (!Thread.currentThread().isInterrupted()) {
+ try {
+ Socket socket = serverSocket.accept();
+ pool.submit(new CLientHandler(socket));
+ } catch (IOException e) {
+ System.out.println("[Server] client연결 오류" + e.getMessage());
+ break;
+ }
+ }
+ } catch (IOException e) {
+ System.out.println("[Server] 포트" + PORT + " not listen");
+ } finally {
+ pool.shutdown();
+ }
+
+ }
+
+ private static void broadcast(String message) {
+ for(CLientHandler client : clients.values()){
+ client.sendMessage(message);
+ }
+ System.out.println("[전체 메세지]" + message);
+ }
+
+ private static class CLientHandler implements Runnable {
+ private final Socket socket;
+ private PrintWriter out;
+ private BufferedReader in;
+ private String nickname;
+
+ public CLientHandler(Socket socket) {
+ this.socket = socket;
+ }
+
+ @Override
+ public void run() {
+ try{
+ in = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
+ out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8), true);
+
+ // 닉네임 설정
+ setupNickname();
+ if(nickname == null) return;
+
+ String message;
+ while ((message = in.readLine()) != null) {
+ if (message.startsWith("/")) {
+ handleCommand(message);
+ } else {
+ broadcast("[" + nickname + "] " + message);
+ }
+ }
+
+ } catch(IOException e){
+ System.out.println(e.getMessage());
+ } finally {
+ cleanup();
+ }
+ }
+
+ private void setupNickname() throws IOException{
+ sendMessage("NICK <이름> ");
+ String nickCommand = in.readLine();
+
+ if (nickCommand == null || !nickCommand.toUpperCase().startsWith("NICK ")) {
+ sendMessage("ERR 잘못된 명령어입니다. 'NICK <닉네임>'으로 시작해야 합니다. 연결을 종료합니다.");
+ return;
+ }
+
+ String potentialNickname = nickCommand.substring(5).trim();
+ // 닉네임 유효성 검사 (공백, 중복)
+ if (potentialNickname.isEmpty() || potentialNickname.contains(" ")) {
+ sendMessage("ERR 닉네임은 비어있거나 공백을 포함할 수 없습니다. 연결을 종료합니다.");
+ return;
+ }
+
+ synchronized (clients) {
+ if (clients.containsKey(potentialNickname)) {
+ sendMessage("ERR 이미 사용 중인 닉네임입니다. 연결을 종료합니다.");
+ return;
+ }
+ this.nickname = potentialNickname;
+ clients.put(nickname, this);
+ }
+ sendMessage("OK 닉네임이 '" + nickname + "'으로 설정되었습니다.");
+ broadcast(nickname + "님이 채팅에 참여했습니다.");
+ System.out.println("[서버] " + socket.getRemoteSocketAddress() + " 님이 '" + nickname + "' 닉네임으로 접속했습니다.");
+
+ }
+
+ private void handleCommand(String command) {
+ if ("/quit".equalsIgnoreCase(command)) {
+ try {
+ socket.close();
+ } catch (IOException e) {
+ e.getMessage();
+ }
+ } else if ("/who".equalsIgnoreCase(command)) {
+ // /who 명령어 처리
+ String userList = "현재 접속자: " + String.join(", ", clients.keySet());
+ sendMessage(userList);
+ } else {
+ sendMessage("ERR 알 수 없는 명령어: " + command);
+ }
+ }
+
+ public void sendMessage(String message) {
+ if (out != null) {
+ out.println(message);
+ }
+ }
+
+ private void cleanup() {
+ if (nickname != null) {
+ clients.remove(nickname);
+ broadcast(nickname + "님이 채팅을 떠났습니다.");
+ System.out.println("[서버] " + nickname + "님의 연결이 끊어졌습니다.");
+ }
+ try {
+ socket.close();
+ } catch (IOException e) {
+ // 무시
+ }
+ }
+
+ }
+
+}
diff --git a/src/Main.java b/src/Main.java
deleted file mode 100644
index 8265e58..0000000
--- a/src/Main.java
+++ /dev/null
@@ -1,17 +0,0 @@
-// Press Shift twice to open the Search Everywhere dialog and type `show whitespaces`,
-// then press Enter. You can now see whitespace characters in your code.
-public class Main {
- public static void main(String[] args) {
- // Press Opt+Enter with your caret at the highlighted text to see how
- // IntelliJ IDEA suggests fixing it.
- System.out.printf("Hello and welcome!");
-
- // Press Ctrl+R or click the green arrow button in the gutter to run the code.
- for (int i = 1; i <= 5; i++) {
-
- // Press Ctrl+D to start debugging your code. We have set one breakpoint
- // for you, but you can always add more by pressing Cmd+F8.
- System.out.println("i = " + i);
- }
- }
-}
\ No newline at end of file