카테고리 없음

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

mynote6676 2025. 6. 4. 19:34
package _client_socket.ch05;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

/**
 * 서버와 양방향 통신하는 클라이언트 측 코드
 */
public class MulitNClient {

    private final String name; // Socket생성시 사용자 이름 넣기 위한 값이다.
                               // final상수인데도 오류 없이 사용가능한 이유는 아래에서 확인하자.
    private Socket socket; // 소켓을 생성했을 시 변수명을 설정해주었다.
    private PrintWriter writerStream; // 서버로 메시지를 전송하는 스트림
    private BufferedReader readerStream; // 서버로 메시지를 읽어오는 스트림
    private BufferedReader keyboardReader; // 키보드값을 읽기위한 스트림

    public MulitNClient(String name){
        this.name = name;     // 생성자 생성시 이것 하나만으로 생성되고 name값을 
                              // 입력해야 하기에 위 final상수에 값을 넣지 않아도 오류가 나지 않았다.
    }

    

    // 서버에 5000번 코트로 연결 처리
    private void connectToServer() throw IOException{ // new Socket생성시 나는 오류를 throw IOException 던져 주었다. 
        socket = new Socket("localhost",5000); // Socket에 연결할 컴퓨터의 ip주소와 포트번호를 입력해주어야 연결가능하다.
        System.out.println("connected to the server"); // 서버에 연결되었다고 보여주는 출력문이다.
    }

     // 입출력 스트림을 설정하는 처리
    private void setupStreams() throws IOException{ 
        writerStream = new PrintWriter(socket.getOutputStream(),true); // 소켓에 메세지를 보내는 스트림이다
        readerStream = new BufferedReader(new InputStreamReader(socket.getInputStream())); // 소켓에서 메세지를 읽어주는 스트림이다.
        keyboardReader = new BufferedReader(new InputStreamReader(System.in)); // 키보드 값을 받아 오는 메세지다.
    }

    
    // 서버로 부터 온 메세지를 실제로 읽는 행위
    public void createdReadThread() {
        return new Thread(() -> {   // 람다식을 활용하였다.
            try {
                String serverMessage; // 값을 잠시 담아주기 위한 변수명 설정
                while( ( serverMessage = readerStream.readLine() ) != null ){
                    // while문을 돌려 readerStream의 값이 들어올때마다 serverMessage에 넣어준다.
                   System.out.println("Server Message : " + serverMessage);
                      // sout은 서버로 부터 받아온 값을 내 콘솔에 보여주기 위한 출력이다.
                }
            } catch (Exception e) {
                e.printStackTrace(); // 정확히 무엇을 캐치하는지 알려줘
            }
        });
    }

    // 키보드에서 입력을 받아 서버로 메세지를 전송하는 행위
    private Thread createWriteThread() {
        return new Thread(new Runnable() { 
            @override
            public void run(){

                try {
                    String keyboardMessage; // 값을 담아주기위한 변수
                    while( (keyboardMessage = keyboardReader.readLine()) != null  ){
                        // 키보드에서 쓴값을 변수에 담아주고
                        WriterStream.println("[" + name + "]" + keyboardMessage);
                        // 키보드에서 쓴값을 출력한다.
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    // 자원을 정리하고 소켓을 닫는 행위
    private void cleanup(){
        try {
            if (socket != null) { // 소켓에 연결된것이 없을때
                socket.close(); 소켓을 닫아준다.
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 읽기 /쓰기 스레드를 생성 및 스타트 처리 
    private void startCommuication () throws InterruptedException{
        Thread readThread = createdReadThread();
        Thread writerStream = createWriteThread();

        readThread.start(); 실행하는 메서드 
        writerStream.start();실행하는 메서드

        // 내가 종료 될때 까지 메인 쓰레드 종료 하지말고 기다려
        readThread.join();
        writeThread.join();
    }

    public void chartRun(){
        try {
            connectToServer();  // 서버에 5000번 코트로 연결 처리 
            setupStreams();// 입출력 스트림을 설정하는 처리 도구를 갖춤
            startCommuication();// 읽기 /쓰기 스레드를 생성 및 스타트 처리  실질적 행동의 주체
        } catch (IOException | InterruptedException e) {    // 이 코드에 대해서 해석해줘 | 이거는 어떤 의미야?      
        } finally {
            cleanup(); // 자원을 정리하고 소켓을 닫는 행위
        }
    }
        // 메인 함수
    public static void main(String[] args) {
        System.out.println("클라이언트 프로그램 시작");
        MulitNClient nClient = new MulitNClient("장승원"); // Socket을 생성
        nClient.chartRun(); // 모든행위를 하게 해준다.
    }
    }

 

 

chap gpt 의 대답

좋아요! 당신이 제공한 MulitNClient 코드를 기반으로, 오타와 구조 문제를 교정하면서, 주석이 없는 부분의 설명, 의문을 가질 수 있는 부분에 대한 해설, 그리고 마지막에 전체 실행 흐름 요약까지 해드릴게요.

✅ 오타 및 주요 수정 사항

원래 코드문제수정
MulitNClient 클래스 이름 오타 MultiNClient
throw IOException 메서드 선언 오류 throws IOException
@override 오타 @Override
WriterStream.println(...) 변수명 대소문자 오타 writerStream.println(...)
writerStream 이름 충돌 변수명이 Thread 이름으로 재사용됨 writeThread 등 다른 이름 사용 권장
readThread.start(); 실행하는 메서드 주석이 코드 라인에 섞임 분리
socket.close(); 소켓을 닫아준다. 코드와 주석이 붙음 분리 필요

✅ 정리된 전체 코드 + 설명이 빠졌던 부분 주석 추가

package _client_socket.ch05;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

/**
 * 서버와 양방향 통신하는 클라이언트 측 코드
 */
public class MultiNClient {

    private final String name; // 사용자 이름 (final: 생성자에서 초기화하면 OK)
    private Socket socket; // 서버와 연결되는 소켓
    private PrintWriter writerStream; // 서버로 보내는 출력 스트림
    private BufferedReader readerStream; // 서버로부터 메시지를 받는 입력 스트림
    private BufferedReader keyboardReader; // 키보드 입력을 받는 입력 스트림

    public MultiNClient(String name) {
        this.name = name;
    }

    // 서버에 5000번 포트로 연결 시도
    private void connectToServer() throws IOException {
        socket = new Socket("localhost", 5000);
        System.out.println("Connected to the server");
    }

    // 입출력 스트림 초기화
    private void setupStreams() throws IOException {
        writerStream = new PrintWriter(socket.getOutputStream(), true); // autoFlush = true
        readerStream = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        keyboardReader = new BufferedReader(new InputStreamReader(System.in));
    }

    // 서버로부터 받은 메시지를 출력하는 읽기 쓰레드 생성
    public Thread createdReadThread() {
        return new Thread(() -> {
            try {
                String serverMessage;
                while ((serverMessage = readerStream.readLine()) != null) {
                    System.out.println("Server Message: " + serverMessage);
                }
            } catch (Exception e) {
                e.printStackTrace(); 
                // 예: 서버 연결이 끊긴 경우 IOException, null 포인터 등
            }
        });
    }

    // 키보드 입력을 서버로 전송하는 쓰기 쓰레드 생성
    private Thread createWriteThread() {
        return new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    String keyboardMessage;
                    while ((keyboardMessage = keyboardReader.readLine()) != null) {
                        writerStream.println("[" + name + "] " + keyboardMessage);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    // 소켓 및 스트림 종료
    private void cleanup() {
        try {
            if (socket != null) {
                socket.close(); // 소켓 닫기
            }
        } catch (Exception e) {
            e.printStackTrace(); // 소켓이 이미 닫혀있을 경우 예외 발생 가능
        }
    }

    // 읽기/쓰기 쓰레드 시작 및 동기화
    private void startCommunication() throws InterruptedException {
        Thread readThread = createdReadThread(); // 서버로부터 메시지를 읽는 쓰레드
        Thread writeThread = createWriteThread(); // 사용자 입력을 서버로 보내는 쓰레드

        readThread.start(); // 각각 비동기 쓰레드로 실행
        writeThread.start();

        // 메인 쓰레드는 둘이 끝날 때까지 대기 (클라이언트가 종료되지 않게)
        readThread.join();
        writeThread.join();
    }

    // 클라이언트 실행 전체 흐름을 관리
    public void chatRun() {
        try {
            connectToServer();    // 서버에 연결
            setupStreams();       // 스트림 설정
            startCommunication(); // 읽기/쓰기 쓰레드 시작
        } catch (IOException | InterruptedException e) {
            e.printStackTrace(); 
            // IOException: 서버 연결 또는 스트림 오류
            // InterruptedException: join() 대기 중 인터럽트
        } finally {
            cleanup(); // 예외 발생 여부 관계없이 자원 해제
        }
    }

    // 실행 시작 지점
    public static void main(String[] args) {
        System.out.println("클라이언트 프로그램 시작");
        MultiNClient client = new MultiNClient("장승원"); // 이름 지정
        client.chatRun(); // 클라이언트 전체 실행 흐름
    }
}

❓ 질문하신 부분 설명

catch (Exception e)

❓ 정확히 무엇을 캐치하는지 알려줘

  • Exception은 모든 예외의 최상위 타입입니다.
  • 여기서 주로 발생 가능한 예외는:
    • IOException: 스트림 읽기/쓰기 중 발생
    • NullPointerException: 스트림이 null일 경우
    • SocketException: 연결이 비정상적으로 종료되었을 때
    • EOFException: 연결된 소켓이 강제로 끊길 때

catch (IOException | InterruptedException e)

❓ | 이건 어떤 의미야?

  • Java 7부터 도입된 멀티 캐치 구문입니다.
  • IOException 또는 InterruptedException이 발생하면 하나의 블록에서 처리합니다.
  • 이 구문은 코드 중복을 줄여줍니다.

🔄 실행 흐름 요약

main()
 └─> chatRun()
       ├─> connectToServer() ← 서버 연결 시도 (localhost:5000)
       ├─> setupStreams() ← 스트림 준비
       ├─> startCommunication()
       │     ├─> createReadThread().start() ← 서버로부터 메시지 수신
       │     ├─> createWriteThread().start() ← 키보드 입력을 서버로 전송
       │     └─> join() ← 두 쓰레드 종료까지 대기
       └─> cleanup() ← 소켓 정리

 

📌 추가 팁

  • Scanner 대신 BufferedReader를 쓴 이유: 한 줄 단위로 빠르게 읽기 위함 (네트워크 기반에서 더 일반적).
  • 키보드 입력을 쓰레드로 처리하는 이유: UI 입력 차단 없이 수신을 병렬로 처리하기 위함.
  • readThread.join()은 현재 메인 쓰레드가 읽기 쓰레드 종료까지 기다리게 함 → 클라이언트가 강제 종료되지 않도록 유지.
728x90