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 코드에 대해 주석의 의미, 오해가 있는 부분, 설명이 부족한 부분, 그리고 오류 및 개선 포인트를 하나씩 짚어가며 설명한 것입니다.
ServerSocket serverSocket = new ServerSocket(PORT);
서버가 5000번 포트에서 연결 요청을 기다리기 시작.
무한 루프 실행 (while (true))
서버는 클라이언트가 접속할 때까지 accept() 호출을 통해 대기.
클라이언트가 연결되면, Socket 객체를 반환받음.
각 클라이언트를 처리하는 ClientHandler 쓰레드 생성
new ClientHandler(socket).start();
새 쓰레드가 생성되어 run() 메서드가 자동 실행됨.
run() 메서드의 실행
클라이언트의 입출력 스트림 설정
클라이언트로부터 메시지를 읽고, 모든 클라이언트에게 브로드캐스트
클라이언트 연결이 종료되면 finally 블록에서 자원 정리 및 출력 스트림 제거
브로드캐스트 로직
메시지가 수신되면 clientWriters 리스트를 돌며 각각의 PrintWriter를 통해 메시지를 전송
모든 클라이언트가 동일한 메시지를 수신
클라이언트 수 카운팅
새 클라이언트가 접속할 때마다 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(); // 예외 정보 출력
}
}
}