JAVA 유용한 클래스

1:N 소켓 양뱡향 하기(Sever측) (chap gpt와의 대화)

mynote6676 2025. 6. 4. 17:18

 

package _server_socket.ch05;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Vector;

/**
 * 1:N 양방향 통신을 구현하는 서버 측 코드
 *  여러 클라이언트와 연결하여 메시지를 수신하고, 모든 클라이언트에게
 *  브로드캐스트 처리 한다.
 */
public class MultiNServer {

    private static final int PORT = 5000; // 포트 번호 5000번을 상수로 지정해 주었다.
    // List 계열 자료구조 - 사용방법 거의 동일(멀티 쓰레드 환경에서 안정적이다.)
    private static Vector<printWriter> ClientWriters = new Vector<>();
    public static int connectedCount = 0; // Socket이 생성될때마다 카운터를 새어주기 위한 변수

    // 내부 클래스
    public static class ClientHandler extends Thread {
        private Socket socket; // Socket socket = new serverSocket.accept();를 넣기위한 변수명선언
        private PrintWriter out; // 출력을 위한 out 변수명 선언
        private BufferedReader in; // 입력을 위한 in 변수명 선언

        public ClientHandler(Socket socket){
            this.socket = socket; 
        }
        /**
        * 클라이언트와 통신을 처리한다.
        * 메시지를 수신하여 모든 클라이언트에게 브로드캐스하고, 연결 종료시 
        * 자원을 정리한다.
        */
        @Override
        public void run(){
            try{
                // 스트림 준비 완료
                in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                out = new PrintWriter(socket.getOutputStream(),true);

                // 1. 자료구조에 서버-- 클라이언트 연결된 출력 스트림을 저장해 두자
                // 2. 클라이언트 측과 연결된 출력스트림(printWriter)을 자료구조에 저장
                clientWriters.add(out); // 이유를 알려줘 대충의 이유는 자료구조에 넣기 위함이란
                                        //것을 알고 있지만 더 자세히 알려줘 그리고 하필 이
                                        // 위치에 이 코드를 쓴 이유도 같이 알려줘

                // 클라이언트로 부터 메세지를 수신하고 방송할 예정
                String message; // 값을 잠시 담아두기 위한 변수명 선언
                while( ( message = in.readLine() ) != null ){
                    System.out.println("Recived : " + message) // 이 출력문안에 의미는 무엇을 
                                                             // 나타내고 왜 쓴거야?
                    for(PrintWriter writer : clientWriters){ // ()안에 코드를 해석해줘 향상된 
                                                            // for 구문의 이해도 같이 
                        writer.println("방송 : " + message);                   
                    }
                }
            } catch (IOException e){
                e.printStackTrace();
                System.out.println("소켓 연결 중 오류 발생");
            } finally{
                try{
                    if(socket != null) socket.close(); 마지막에 소켓을 닫아주기 위해서다.
                    // static 자료 구조에 저장된 나의 출력 스트림을 제거 하기
                    clientWriters.remove(out) // clientWriters가 자료 구조라서 remove로 해제
                        // 무엇이든 반대로 닫아주어야 한다.                        
                } catch(EXception e){
                    e.printStackTrace();
                    System.out.println("소켓 자원 해제 중 오류 발생") // 해제중 어떤 오류가 발생할 수 있지?
                }
            }
        }
    } // end of ClientHandler

    // 메인 함수
    public staitc void main(String[] args){
        System.out.println("Server started ....");

        try (SeverSocket serverSocket = new ServerSocket(PORT)){
            // 메인 쓰레는 무한 루프를 돌면서 클라이언트 연결 요청을 기다린다.
            while(true){
                //Socket socket = serverSocket.accept;
                // clientHandler <--- 작업가 1이 자신에 동작을 진행시킬 수 있다.
                new ClientHandler(serverSocket.accept()).start(); // 이것의 흐름은 대략으로 알고 있는데 정확하게 알려주고 
                                                                  // 이구조가 어떻게 성립되는지도 알려줘
                connectedCount++;
                System.out.println("클라이언 연결 됨 : " + connectedCount); // 연결될시 카운터 체크됨
            }
        } catch (Exception e) {
            e.printlnStackTrace();
        }
    } // end of main 
    
} // end of class
주석을 읽고 모르거나 의문점들을 지적,그리고 알려주면서 , 혹시 옆에 주석이 없는 코드들이 있다면 부가 설명해줘

 

chap gpt의 대답

 

아래는 당신이 제공한 MultiNServer 코드에 대해 주석의 의미, 오해가 있는 부분, 설명이 부족한 부분, 그리고 오류 및 개선 포인트를 하나씩 짚어가며 설명한 것입니다.

더보기

🔧 주요 오타 및 문법 오류 (먼저 정리)

  1. 클래스 이름 오류
    private static Vector<printWriter> ClientWriters = new Vector<>();
    • printWriter → ❌ 존재하지 않는 클래스입니다.
    • PrintWriter → ✅ 정확한 타입입니다. 대소문자 구분 주의!
      printWriter → ❌ 존재하지 않는 클래스입니다.
      
      PrintWriter → ✅ 정확한 타입입니다. 대소문자 구분 주의!
private static Vector<PrintWriter> clientWriters = new Vector<>();

2.오타

public staitc void main(String[] args){
System.out.println("Recived : " + message) // 세미콜론 빠짐
} catch(EXception e){
e.printlnStackTrace();
try (SeverSocket serverSocket = new ServerSocket(PORT)){

 

  • staitc → ❌
  • static → ✅
  • System.out.println("Received : " + message);로 수정
  • EXception → ❌
  • Exception → ✅
  • printlnStackTrace()는 존재하지 않음.
  • e.printStackTrace();가 올바른 호출입니다.
  • SeverSocket → ❌
  • ServerSocket → ✅

🎯 핵심 개념 설명 + 의문에 대한 답변

📌 1. clientWriters.add(out)의 정확한 이유와 위치

clientWriters.add(out);

이유:

  • 서버가 클라이언트로 메시지를 보낼 수 있으려면, 클라이언트에 연결된 PrintWriter 객체가 필요합니다.
  • 모든 클라이언트에게 브로드캐스트(모두에게 전송)를 하기 위해, PrintWriter들을 하나의 리스트 (Vector)에 저장해 둡니다.

왜 여기 위치에 있는가?

  • 이 시점(run() 메소드 안)에서는 클라이언트의 입출력 스트림이 이미 설정되어 있음.
  • 즉, out = new PrintWriter(...); 이후에 clientWriters.add(out);를 호출해야 정상적인 출력이 가능합니다.
  • 그보다 이전에 저장하면 null이 들어갈 수 있음.

📌 2. System.out.println("Received : " + message);의 의미

의미:

  • 클라이언트로부터 메시지를 수신했을 때, 서버 측 콘솔에 로그를 남기는 출력문입니다.
  • 즉, 서버가 메시지를 잘 수신하고 있다는 디버깅용 또는 운영 모니터링용 출력.

📌 3. 향상된 for문 해석

for(PrintWriter writer : clientWriters){
    writer.println("방송 : " + message);                   
}

해석:

  • clientWriters는 PrintWriter 객체들의 집합입니다 (Vector 구조).
  • writer는 그 집합에 있는 각 PrintWriter 객체를 순차적으로 가리킵니다.
  • 각 클라이언트에게 메시지를 전송하는 코드입니다.

향상된 for문 구조:

for(타입 변수명 : 반복할_컬렉션) {
    // 반복 처리
}

 

  • 이것은 Iterator를 내부적으로 사용하는 편리한 문법입니다.
  • 전통적인 for문보다 안전하고 코드가 간결합니다.

📌 4. new ClientHandler(serverSocket.accept()).start(); 흐름 설명

new ClientHandler(serverSocket.accept()).start();

이 코드는 다음 과정을 한 줄로 압축한 것입니다:

  1. serverSocket.accept()
    • 클라이언트가 접속할 때까지 블로킹됩니다.
    • 접속되면 Socket 객체가 반환됨.
  2. new ClientHandler(socket)
    • 그 소켓을 넘겨받아 해당 클라이언트를 처리할 쓰레드를 생성.
  3. .start()
    • Thread를 시작하여 run() 메소드 내부의 코드가 비동기로 실행됨.

왜 이렇게 구조화 되었는가?

  • 각 클라이언트는 독립적으로 처리되어야 하기 때문에, 각각을 별도의 쓰레드로 다룬 것입니다.

📌 5. finally 블록에서 자원 정리 이유와 발생 가능한 오류

finally {
    if (socket != null) socket.close();
    clientWriters.remove(out);
}

왜 자원 정리하는가?

  • 네트워크 자원 (Socket, Stream)은 OS 레벨에서 제한된 자원.
  • 사용이 끝나면 반드시 닫아야 메모리 누수 및 포트 고갈 방지 가능.

오류가 발생할 수 있는 이유?

  • socket.close() 중 이미 닫혀 있거나, 네트워크 오류가 생기면 예외 발생.
  • clientWriters.remove(out) 중에도 out이 null이면 NullPointerException.

💡 기타 설명이 없는 부분 보충

  • Vector는 동기화된 리스트 구조입니다. 여러 쓰레드가 접근하므로 ArrayList보다 안정적입니다.
  • connectedCount는 단순 카운터지만, 다중 쓰레드 환경에서는 AtomicInteger를 쓰는 것이 더 안전합니다.
  • 클래스/메서드/변수명은 Java 명명 규칙에 따라 clientWriters (소문자로 시작)로 수정하는 것이 좋습니다.

✅ 개선 제안 요약

  • Vector<PrintWriter>로 수정
  • PrintWriter out이 null일 수 있으므로 null 체크 후 remove()
  • connectedCount++ 는 AtomicInteger로 변경 고려
  • clientWriters 접근은 synchronized 블록 또는 CopyOnWriteArrayList 사용 고려
  • 에러 로그는 SLF4J 등의 로깅 라이브러리 사용 고려

 

✅ 정리된 MultiNServer.java 코드 + 라인별 주석

더보기

🧭 프로그램 흐름 설명 (main() 실행 기준)

🔄 main() 메서드 실행 시 흐름 요약:

  1. ServerSocket 생성
    • ServerSocket serverSocket = new ServerSocket(PORT);
    • 서버가 5000번 포트에서 연결 요청을 기다리기 시작.
  2. 무한 루프 실행 (while (true))
    • 서버는 클라이언트가 접속할 때까지 accept() 호출을 통해 대기.
    • 클라이언트가 연결되면, Socket 객체를 반환받음.
  3. 각 클라이언트를 처리하는 ClientHandler 쓰레드 생성
    • new ClientHandler(socket).start();
    • 새 쓰레드가 생성되어 run() 메서드가 자동 실행됨.
  4. run() 메서드의 실행
    • 클라이언트의 입출력 스트림 설정
    • 클라이언트로부터 메시지를 읽고, 모든 클라이언트에게 브로드캐스트
    • 클라이언트 연결이 종료되면 finally 블록에서 자원 정리 및 출력 스트림 제거
  5. 브로드캐스트 로직
    • 메시지가 수신되면 clientWriters 리스트를 돌며 각각의 PrintWriter를 통해 메시지를 전송
    • 모든 클라이언트가 동일한 메시지를 수신
  6. 클라이언트 수 카운팅
    • 새 클라이언트가 접속할 때마다 connectedCount++로 카운트 증가
    •  

 

 

package _server_socket.ch05;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Vector;

/**
 * 1:N 양방향 통신 서버
 * - 여러 클라이언트와 연결
 * - 클라이언트로부터 메시지를 수신하여 모든 클라이언트에 브로드캐스트
 */
public class MultiNServer {

    private static final int PORT = 5000; // 서버 포트 설정
    private static Vector<PrintWriter> clientWriters = new Vector<>(); // 클라이언트 출력 스트림 저장
    public static int connectedCount = 0; // 연결된 클라이언트 수 카운터

    // 클라이언트 처리용 내부 쓰레드 클래스
    public static class ClientHandler extends Thread {
        private Socket socket;             // 클라이언트 소켓
        private PrintWriter out;          // 클라이언트로 보내는 출력 스트림
        private BufferedReader in;        // 클라이언트로부터 받는 입력 스트림

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

        @Override
        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("Received: " + message); // 서버 콘솔에 로그 출력

                    // 모든 클라이언트에게 메시지 전송 (브로드캐스트)
                    for (PrintWriter writer : clientWriters) {
                        writer.println("방송: " + message);
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("클라이언트와 통신 중 오류 발생");
            } finally {
                try {
                    // 자원 정리
                    if (socket != null) socket.close(); // 소켓 닫기
                    if (out != null) clientWriters.remove(out); // 리스트에서 제거
                } catch (Exception e) {
                    e.printStackTrace();
                    System.out.println("소켓 자원 해제 중 오류 발생");
                }
            }
        }
    }

    // 메인 함수: 서버 실행 시작 지점
    public static void main(String[] args) {
        System.out.println("Server started ....");

        try (ServerSocket serverSocket = new ServerSocket(PORT)) {
            // 서버는 무한 루프로 클라이언트 접속을 기다림
            while (true) {
                // 클라이언트 연결 수락 (Blocking 호출)
                Socket socket = serverSocket.accept();

                // 클라이언트 처리용 쓰레드 생성 및 실행
                new ClientHandler(socket).start();

                // 연결된 클라이언트 수 출력
                connectedCount++;
                System.out.println("클라이언트 연결됨: " + connectedCount);
            }
        } catch (Exception e) {
            e.printStackTrace(); // 예외 정보 출력
        }
    }
}

 

 

728x90