인프런 영문 브랜드 로고
인프런 영문 브랜드 로고

인프런 커뮤니티 질문&답변

지민님의 프로필 이미지

작성한 질문수

김영한의 실전 자바 - 고급 2편, I/O, 네트워크, 리플렉션

채팅 프로그램 - 클라이언트

채팅 프로그램 콘솔출력 겹침

작성

·

82

0

 학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문 전에 다음을 꼭 확인해주세요.


1. 강의 내용과 관련된 질문을 남겨주세요.
2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.
(자주 하는 질문 링크: https://bit.ly/3fX6ygx)
3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.
(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)

질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.
=========================================
[질문 템플릿]
1. 강의 내용과 관련된 질문인가요? (예/아니오)
2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)
3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)

[질문 내용]
안녕하세요 채팅 프로그램 작성 도중 여쭤볼 게 있어 남기게되었습니다

문제 상황

image.png

위 사진은 제 콘솔출력창입니다 왼쪽사용자는 kim : hi라는 메시지를 보냈고, 오른쪽 사용자는 han: hello라는 메시지를 보냈는데요, 문제는 메시지를 보내달라는 문장과 다른 사용자가 보낸 메시지 내용이 겹친다는 것입니다

 

질문

image.png

 

위 사진은 제가 해결해보려는 시도로 static메소드를 lock을 사용해서 만들었습니다. 하지만 결과가 똑같았습니다. 해결방법이 궁금합니다! 그리고 lock을 걸어서 사용했는데 왜 결과가 똑같은지도 궁금합니다...답변해주시면 감사하겠습니다

 

 

아래는 제 코드를 보여드리겠습니다!

1.Client 메세지 보내는 부분 클래스입니다

public class ClientSecond {
    private static final int PORT = 12345;
    static Socket socket;
    static DataOutputStream output;
    static DataInputStream input;


    public static void main(String[] args) throws IOException {
        try {
            socket = new Socket("localhost", PORT);
            output = new DataOutputStream(socket.getOutputStream());
            ClientReceiverSecond clientReceiverSecond = new ClientReceiverSecond(socket);
            Thread thread = new Thread(clientReceiverSecond);
            thread.start();

            System.out.println("Chatting Program");
            System.out.print("사용자의 이름을 입력하세요 : ");
            Scanner scanner = new Scanner(System.in);
            String name = scanner.nextLine();
            output.writeUTF(name); //1.이름전송

            while (true) {
                //System.out.print("메세지를 입력해주세요 : ");
                ClientOutput.printMessage();

                String message = scanner.nextLine();
                output.writeUTF(message); //2.메세지전송

                if (message.equals("exit")) {
                    System.out.println("채팅 프로그램을 종료합니다. ");
                    break;
                }
            }
        }finally {
            output.close();
            socket.close();
        }
    }

}

 

 

  1. 각 클라이언트에게 다른 사용자의 메세지를 보내는 클래스입니다

public class ClientReceiverSecond implements Runnable {
    private static Socket socket;
    static DataInputStream input;

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

    @Override
    public void run() {//다른 사용자들의 메세지 확인
        try {
            while (true) {
                input = new DataInputStream(socket.getInputStream());
                String other_messages = input.readUTF(); //사용자의 이름과 메시지 분리 필요

                //System.out.println(other_messages);
                ClientOutput.printMessage(other_messages);

                //todo
                if (other_messages.equals("exit")) {
                    System.out.println("채팅을 종료합니다. ");
                    break;
                }
            }

        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            try {
                input.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

    }
}

 

 

 

  1. 클라이언트와 통신하는 세션 클래스인데요 혹시 몰라서 추가로 올려보겠습니다

public class SessionSecond implements Runnable{
    private Socket socket;
    private SessionManagerSecond sessionManagerSecond;
    DataInputStream input = null;
    DataOutputStream output = null;


    public SessionSecond(Socket soket, SessionManagerSecond sessionManagerSecond) {
    this.socket = soket;
    this.sessionManagerSecond = sessionManagerSecond;
    sessionManagerSecond.add(this);
    }

    public void send(String message) throws IOException {
        output = new DataOutputStream(socket.getOutputStream());
        String messages[] = message.split(",");
        String name = messages[0];
        String messagemain = messages[1];
        output.writeUTF(name+" : "+messagemain);

    }

    @Override
    public void run() {

        try {
            input = new DataInputStream(socket.getInputStream());
            //output = new DataOutputStream(socket.getOutputStream());

            String name = input.readUTF();

            while (true) {
                String message = input.readUTF();
                if (message.equals("exit")) {
                    break;
                }
                String allmesaage = name+","+message;

                sessionManagerSecond.call(allmesaage);
            }


        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            try {
                output.close();
                input.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

        }

    }
}

답변 1

0

인프런 AI 인턴님의 프로필 이미지

안녕하세요, 인프런 AI 인턴입니다.
지민님, 채팅 프로그램 작성 중 겪고 계신 콘솔 출력 문제에 대해 설명드리겠습니다.

현재 문제가 발생하는 원인은 여러 쓰레드가 동시에 콘솔에 출력을 시도하면서 메시지와 입력을 대기하는 문장이 겹치기 때문입니다. synchronized 블록을 사용하여 출력할 때 쓰레드 간의 충돌을 줄이려는 시도는 좋지만, 현재 코드 구조에서는 모든 출력과 입력이 적절하게 동기화되지 않아 문제가 발생할 가능성이 높습니다.

제안하는 해결책

  1. 공통적인 lock 사용: ClientOutput의 각 printMessage 메소드에서 사용하는 lock 객체는 제대로 공유되고 있어야 합니다. 모든 쓰레드가 동일한 lock 객체를 참조할 수 있도록 ClientOutput이 기본적으로 저장하는 lock 객체가 모든 사용자의 콘솔 출력을 담당할 수 있도록 확인해야 합니다.

  2. 입력과 출력의 동기화 분리: 입력(prompt)를 받는 부분과 다른 사용자의 메시지를 화면에 출력하는 부분을 잘 분리하여 처리할 필요가 있습니다. 예를 들어, 사용자로부터 입력을 받을 때는 다른 사용자의 메시지를 출력하지 못하도록 동기화 구문을 변경해야 합니다.

  3. 버퍼 사용: 만약 위 두 가지 방법으로도 문제가 해결되지 않는다면, 사용자 입력을 받을 때와 메시지를 출력할 때 두 개의 큐나 버퍼를 사용하여 데이터를 처리하는 방법을 고려해보세요. 즉, 수신된 메시지는 출력 버퍼에 저장하고, 사용자 입력이 완료되면 한 번에 큐에 저장된 메시지를 출력하는 식입니다.

문제 해결에 도움이 되기를 바라며, 궁금한 점이 있으면 언제든지 추가 질문 남겨주세요. 작업에 행운을 빕니다!

Spring Boot Documentation에서의 콘솔 로그와 관련된 정보는 여기를 참고하세요.

지민님의 프로필 이미지

작성한 질문수

질문하기