Learn business/Network

Java Socket Server/Client (TCP 통신)


HTTP 스터디 과제로 Client로부터 요청 받은 내용을 그대로 출력하는 Echo Server를 만드는 과제를 진행하였다. 인터넷 검색을 하면 자료가 워낙 많아서 쉽게 찾아할 수 있었지만, 역시나 내 것으로 만드려면 포스팅이 제맛이다.

 

HTTP는 OSI에서 Application 계층에서 동작하는 프로토콜이다. 이미 학부 시절 네트워크 수업에서 배웠겠지만 Client와 Server의 네트워크 통신은 OSI 계층 구조를 가지고 있으며 Client 프로세스와 Server 프로세스의 통신이라고 보면 된다. 데이터를 보내는 경우 상위 계층에서 하위 계층으로 데이터는 멀티플렉싱되며, 데이터를 받는 입장에서는 하위 계층에서 상위 계층으로 디멀티플렉싱된다. java.net.* 패키지에서 제공해주는 인터페이스를 통해서 멀티플렉싱디멀티플렉싱되는 현상을 코드를 통해서 보도록 하자.

 

ServerSocket 동작

이미 학부시절 네트워크 수업에서 배웠겠지만, 서버 프로그래밍은 전형적으로 아래 순서대로 만들어 진다.

 1. 소캣 생성 (Socket)

 2. 특정 포트에 바인드 (Bind)

 3. 연결 수신 (Listen)

 4. 요청 대기 (accept)

java.net.* 패키지에서 제공해주는 ServerSocket 클래스는 객체 생성을 통해서 1번부터 3번까지의 동작을 내부에서 진행한다. 그리고 생성된 ServerSocket 객체를 통해서 accept() 메소드를 실행시면, 클라이언트로부터 요청을 받을 수 있는 대기 상태가 된다.

 

Java Echo Socket Server 샘플 코드

아마 구글링을 하면 많은 자료가 나오기 때문에 위 코드에 대해서 다로 설명은 하지 않겠다. 다만, 주석에 BufferedReader Blocking 으로 된 부분은 아래 Socket Client 예제 코드를 보고 좀 더 자세히 보도록 하자.

 

Java Socket Client 샘플 코드

 

BufferedReader Bloking 현상

위 코드를 돌려보면 코드를 보면 서버 로그에 클라이언트에서 요청한 HTTP 메시지가 찍힐 것이고, 클라이언트 로그에는 서버에서 응답받은 HTTP 메시지가 찍힐 것이다. 사실 JavaSocketServer 코드에서 주석친 부분처럼 구현하기 전에 원래는 아래와 같이 단순하게 bufferedReader.lines() 메소드를 실행시켜서 foreach 문으로 로그를 남겼었다.

bufferedReader.lines().forEach(LOGGER::info);

그러나 이상하게 bufferedReader에서 Blocking이 걸려서 그 아래 클라이언트에게 응답 메시지를 보내주는 부분이 실행되지 않는 것이다. 구글링을 통해서 bufferedReader에 Blocking이 걸리는 이유를 찾아봤는데, 그 이유는 아래와 같았다.

 

 1. bufferedReader는 CR("\r") 혹은 LF("\n") 혹은 CRLF("\r\n") 문자를 기준으로 "한 줄"임을 판단하기 때문에 CRLF 정보가 없으면 버퍼에 담아두고 다음 입력이 들어올때까지 기다린다. 즉, Echo Socket Server에 들어온 요청을 로그에 남기기 위해서는 클라이언트에서 CRLF 값은 필수적이었다.

 2. 그러나 CRLF로 클라이언트로 들어온 모든 요청을 로그에 남겼지만, 여전히 Block된 상태로 입력을 대기하고 있었다. 왜냐하면 BufferedReader 객체는 결국 clientSocket 객체의 getInputStream() 메소드를 통해서 만들어졌기 때문이다. 결국 clientSocket이 close되지 않는 이상 bufferedReader는 계속 무한정 Block된 상태로 다음 입력을 기다리고 있는 것이다. (참고로, JavaSocketClient 클래스에서 bufferedReader.lines().forEach(LOGGER::info) 코드를 실행시켜도 Block이 걸리지 않는 이유는 clientSocket이 close되었기 때문이다.)

 

사실 클리이언트에서 bufferedReader에게 더 이상 전송할 입력 데이터가 없다고 노티를 주는 방법이 없을까 구글링 및 삽질을 통해서 노력해봤지만, 원하는 결과는 나오지 않았다. 그래서 우회한 방법이 BufferedReader 클래스의 ready()라는 메소드이다. ready() 메소드를 통해서 Block된 상태를 벗어날 수 있으며, 클라이언트에게 응답 메시지를 보낼 수 있었다. 이렇게 우회하는 방법이 마음에 들지는 않았다. 그래서 클라이언트 소켓으로부터 입력을 받는 부분을 쓰레드로 빼서 따로 대기시켜 놓고 메인 쓰레드를 실행하도록 할까도 고민해보고, while 문을 돌려서 readLine()으로 클라이언트로부터 온 문자열을 받는 String 변수를 만들어서 널체크하여 while문을 빠져가는 방법도 있었지만 삽질하다가 많이 지쳐서 ready()를 통해서 Block되는 현상을 해결하기로 하였다.

 

혹시 더 좋은 방법이 있으면 댓글로 해결책을 제시해주면 감사하겠습니다.


[참고자료]

https://stackoverflow.com/questions/15521352/bufferedreader-readline-blocks

http://www.java-gaming.org/index.php?topic=37191.0

https://www.tek-tips.com/viewthread.cfm?qid=471685

https://mommoo.tistory.com/28

https://sdr1982.tistory.com/168

https://stackoverflow.com/questions/7872846/how-to-read-from-standard-input-non-blocking

'Learn business > Network' 카테고리의 다른 글

Packet Switching을 사용하면 왜 Loss나 Delay가 발생할까?  (0) 2020.06.03
CORS 이슈 해결 :: Spring Security  (0) 2020.02.24
CORS 이슈 해결  (3) 2020.02.16
CORS 정책  (3) 2020.02.09