Java 양방향 소켓 통신중 일어난일(readLine 무한)

HYK·2022년 7월 9일
1

Java

목록 보기
3/4

문제

개요

Client에서 Server로 접속 후에 Socket 객체의 OutputStream를 통해서 서버에게 메시지를 전달하는데 이상하게 PrintWriter의 println 을 사용할 때는 데이터가 잘 전송됐는데 BufferedWriter ,PrintWriter, OutputStreamWriter의 write 메서드를 이용하면 서버에서 BufferedReader의 readLine 메서드로 읽어지지가 않고 무한 대기 상태가 됐다.(나중에 시도해 본 결과 read(char[]) 메서드로는 읽어졌음) 심지어 예외도 일어나지 않아서 이유를 알 수 없었다.


문제의 코드

client

    public static void startClient(String data) {
        Socket socket = null;
        try {
            socket = new Socket("127.0.0.1", 1234); // 소켓 서버에 접속
            System.out.println("접속 성공!");

            //입출력 Stream return
            OutputStream out = socket.getOutputStream();
            InputStream in = socket.getInputStream();

            //체이닝
            //PrintWriter pw = new PrintWriter(new OutputStreamWriter(out), true);
            BufferedReader br = new BufferedReader(new InputStreamReader(in));
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(out));

            //message 전송
            bw.write(data);
            bw.flush();
			
            Thread.sleep(1000);
            //server응답 메세지
            System.out.println(br.readLine());
        } .... catch ~
    }

server


    private static void startSocket() {
        ServerSocket server = null;
        Socket sc = null;
        try {
            ss = new ServerSocket(1234);
            while (true) {
                System.out.println("클라이언트 접속대기중");
                //대기
                sc = server.accept();
                
                //입출력 Stream return
                Thread.sleep(1000);
                InputStream in = sc.getInputStream();
                OutputStream out = sc.getOutputStream();

                //체이닝
				//PrintWriter pw = new PrintWriter(new OutputStreamWriter(out), true);
                BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(out));
                BufferedReader br = new BufferedReader(new InputStreamReader(in));

             
                //client에서 보낸 데이터 출력
                String message = null;
                while (br.ready()) {        // readLine은 \n \r 을 만날때 까지 무한히 실행된다.
                    message = br.readLine();
                }
                
                System.out.println("전송받은 데이터 : " + message);
                //client에 다시 return
                
                bw.write("return : "+message);
                bw.flush();


                bw.close();
                br.close();
                out.close();
                in.close();
                sc.close();
                
                if (message != null && "exit".equals(message)) {
                    System.out.println("서버종료");
                    break;
                }
                
            }
        } ...catch ~
    } 

처음에 봤을 때는 눈을 씻고 찾아봐도 문제는 보이지 않았다. 주석에 힌트가 나와있지만 디버깅하기 전까지는 이 문제의 답을 찾기는 쉽지 않았다.

해결

디버깅시도

서버의 데이터를 받는 br.readLine에서 step in을 해서 들어가 보았다. 그런데 BufferedReader의 readLine 메 서드가 무한히 실행되고 있었던 것이었다. 이유가 뭘까 해서 종료 조건을 봤는데... '\n','\r' 이었던 것이다.
혹시나 하는 마음에 char[] 버퍼를 이용해서 BufferedReader의 readLine이 아닌 read를 이용해 받으니 잘 실행됐다.
PrintWriter의 println 을 사용하면 잘 됐던 이유는 println에서 '\n'을 자동으로 추가해서 데이터를 보내줬기 때문이다.
write 메 서드는 줄바꿈 문자를 보내주지 않아서 계속 무한히 read를 하고 있었던 것.. write를 쓰고 메시지를 startClient("message.\n"); 이렇게 보내니 잘 작동됐다.
몇 시간 동안 고생해서 허무하긴 했지만 다음부터 이런 실수와 사용하는 라이브러리의 내부 동작 코드를 좀 더 유심히 봐야 할 필요가 있다고 생각이 들었다.

참고 : readLine메서드

    /**
     * Reads a line of text.  A line is considered to be terminated by any one
     * of a line feed ('\n'), a carriage return ('\r'), or a carriage return
     * 종료조건은 \n \r 이라고 명시. 되어있음
     * followed immediately by a linefeed.
     *
     * @param      ignoreLF  If true, the next '\n' will be skipped
     *
     * @return     A String containing the contents of the line, not including
     *             any line-termination characters, or null if the end of the
     *             stream has been reached
     *
     * @see        java.io.LineNumberReader#readLine()
     *
     * @exception  IOException  If an I/O error occurs
     */
    String readLine(boolean ignoreLF) throws IOException {
        StringBuilder s = null;
        int startChar;

        synchronized (lock) {
            ensureOpen();
            boolean omitLF = ignoreLF || skipLF;

        bufferLoop:
            for (;;) {

                if (nextChar >= nChars)
                    fill();
                if (nextChar >= nChars) { /* EOF */
                    if (s != null && s.length() > 0)
                        return s.toString();
                    else
                        return null;
                }
                boolean eol = false;
                char c = 0;
                int i;

                /* Skip a leftover '\n', if necessary */
                if (omitLF && (cb[nextChar] == '\n'))
                    nextChar++;
                skipLF = false;
                omitLF = false;

            charLoop:
                for (i = nextChar; i < nChars; i++) {
                    c = cb[i];
                    if ((c == '\n') || (c == '\r')) {
                        eol = true;
                        break charLoop;
                    }
                }

                startChar = nextChar;
                nextChar = i;

                if (eol) {
                    String str;
                    if (s == null) {
                        str = new String(cb, startChar, i - startChar);
                    } else {
                        s.append(cb, startChar, i - startChar);
                        str = s.toString();
                    }
                    nextChar++;
                    if (c == '\r') {
                        skipLF = true;
                    }
                    return str;
                }

                if (s == null)
                    s = new StringBuilder(defaultExpectedLineLength);
                s.append(cb, startChar, i - startChar);
            }
        }
    }
profile
Test로 학습 하기

0개의 댓글