Skip to content

2.2 블로킹과 논블로킹

Junggeun Lee edited this page Aug 10, 2019 · 1 revision

2.2 블로킹과 논블로킹

  • 블로킹 : 요청한 작입이 성공하거나 에러가 발생하기 전까지는 응답을 돌려주시 않는 것
  • 논블로킹 : 요청한 작업의 성공 여부와 상관없이 바로 결과를 돌려주는 것

2.2.1 블로킹 소켓

블로킹 소켓 클래스

  • ServerSocket
  • Socket

논블로킹 소켓 클래스

  • ServerSocketChannel
  • SocketChannel

블로킹 되는 위치

  • accept() : 연결수락 블로킹
  • Read() : 일기 처리 블로킹

블로킹 소켓은 데이터 입출력에서 스레드의 블로킹이 발생하기 때문에 동시에 여러 클라이언트에 대한 처리가 불가능하게 된다.

블로킹 모드의 소켓을 사용하는 서버가 다중 클라이언트의 접속 처리를 하지 못하는 문제점을 해결하기 위해서 등장한 모델은 연결된 클레스별로 각각 스레드를 할당하는 방법이다.

클라이언트가 서버에 접속하면 서버 소켓의 accept 메서드를 통해서 연결된 클라이언트 소켓을 얻어와 새로운 스레드를 하나 생성하고 그 스레드에게 클라이언트 소켓에 대한 I/O 처리를 넘겨주게 된다.

스레드방식의 문제점

  • Accept() 메서드가 여전히 병목지점이다.
  • 서버에 접속하는 클라이언트 수가 증가하면 애플리케이션 서버의 스레드 수가 증가하게 되는데, 이때 자바의 힙 메모리 부족으로 인한 OOM(Out Of Memory) 오류가 발생할 수 있다.

스레드 증가에 따른 OOM 오류를 피하기 위해 스레드 풀을 사용한다. 이와 같은 구조에서는 동시에 접속 가능한 사용자 수가 스레드 풀에 지정된 스레드 수에 의존하게 된다.

동시접속 : 동일한 시간에 서버에 연결되어 있는 클라이언트 수

동시 접속 수를 늘리기 위해서 스레드 풀의 크기를 자바 힙이 허용하는 최대 한도에 도달할 때까지 늘리는 것이 합당한지에 대한 두 가지 관점

  • 가비지 컬랙션에 대한 부담(stop the world): 힙크기가 크면 클수록 가비지 컬랙션에 드는 시간이 길어진다. 힘 크그와 생성 가능한 스레드 수는 비례한다. 힙에 할당된 메모리가 크면 클수록 가비지 컬랙션이 수행되는 횟수는 중어들지만 수행시간은 상대적으로 길어진다.
  • 컨텍스트 스위칭 : 프로세스에서 수행되는 스레드들이 CPU 점유를 위해서 자신의 상태를 변경하는 작업

2.2.2 논블로킹 소켓

블로킹 모드의 소켓은 read, write, accept 메서드 등과 같은 입출력 메서드가 호출되면 처리가 완료될 때까지 스레드가 멈추게 되어 다른 처리를 할 수 없었다. 이와 같은 단점을 해결하는 방식이 논블로킹 소켓이다.

  1. 자바 1.7에 새로 등장. Try 블록이 끝날 때 소괄호 안에서 선언된 자원이 자동으로 해제됨.
  2. 새로운 Selector 객체 생성(open). 자바 NIO 컨포넌트 중의 하나인 Selector는 자신에게 등록된 채널에 변경 사항이 발생했는지 검사하고 변경 사항이 발생한 채널에 대한 접근을 가능하게 해준다.
  3. 블로킹 소켓의 ServerSocket에 대응되는 논블로킹 소켓의 서버 소켓 채널을 생성(open)한다. 블로킹 소켓과 다르게 소켓 채널을 먼저 생성하고 사용할 포트를 바인딩한다.
  4. 생성한 Selector와 ServerSocketChannel 객체가 정상적으로 생성되었는지 확인한다.
  5. 소켓 채널의 블로킹 모드의 기본값은 true다. 즉, 별도로 논블로킹 모드로 지정하지 않으면 블로킹 모드로 동작한다. 여기서는 ServerSocketChannel 객체를 논블로킹 모드로 설정했다.
  6. 클라이언트의 연결을 대기할 포트를 지정하고 생성된 ServerSocketChannel 객체에 할당한다. 이작업이 완료되면 ServerSocketChannel 객체가 지정된 포트로부터 클라이언트의 연결을 생성할 수 있다.
  7. ServerSocketChannel 객체를 Selector 객체에 등록한다. Selector가 감지할 이벤트는 연결 요청에 해당하는 SelectionKey.OP_ACCEPT다.
  8. Selector에 등록된 채널에서 변경 사항이 발생했는지 검사한다. Selector에 아무런 I/O 이벤트도 발생하지 않으면 스레드는 이 부분에서 블로킹된다. I/O 이벤트가 발생하지 않았을 때 블로킹을 피하고 싶다면 selectNow 메서드를 사용하면 된다.
  9. Selector에 등록된 채널 중에서 I/O 이벤트가 발생한 채널들의 목록을 조회한다.
  10. I/O 이벤트가 발생한 채널에서 동일한 이벤트가 감지되는 것을 방지하기 위하여 조회된 목록에서 제거한다.
  11. 조회된 I/O 이벤트의 종류가 연결 요청인지 확인한다. 만약 연결 요청 이벤트라면 연결처리 메서드로 이동한다.
    1. (14) 연결 요청 이벤트가 발생한 채널은 항상 ServerSocketChannel이므로 이벤트가 발생한 채널을 ServerSocketChannel로 캐스팅한다.
    2. (15) ServerSocketChannel을 사용하여 클라이언트의 연결을 수락하고 연결된 소켓 채널을 가져온다.
    3. (16) 연결된 클라이언트 소켓 채널을 논블로킹 모드로 설정한다.
    4. (17) 클라이언트 소캣 채널을 Selector에 등록하여 I/O 이벤트를 감시한다.
  12. 조회된 I/O 이벤트의 종류가 데이터 수신인지 확인한다. 만약 데이터 수신 이벤트라면 데이터 읽기 처리 메서드로 이동한다.
    1. 데이터 수신 이벤트가 발생한 채널은 항상 SocketChannel이므로 데이터 수신 이벤트가 발생한 채널을 SocketChannel로 캐스팅한다.
    2. buffer를 지운다.
    3. SocketChannel에서 데이터를 read하여 buffer에 담고 읽은 byte갯수를 반환한다.
    4. 만일 read 함수의 반환값이 -1이면 연결을 종료(close)한다.
    5. 클라이언트에 echo를 회신한다.
  13. 조회된 I/O 이벤트의 종류가 데이터 쓰기 가능인지 확인한다. 만약 데이터 쓰기 가능 이벤트라면 데이터 읽기 메서드로 이동한다.
    1. 데이터 수신 이벤트가 발생한 채널은 항상 SocketChannel이므로 데이터 수신 이벤트가 발생한 채널을 SocketChannel로 캐스팅한다.
    2. keepDataTrack에서 socketData를 가져온다.
    3. socketChannel에 write한다.

그림 2-6 블로킹 소켓과 논블로킹 소켓 동작 방식 비교

논블로킹 소켓을 사용하면 더 많은 클라이언트 연결을 수용할 수 있다.

네티는 사용하는 소켓의 모드와 상관없이 개발할 수 있도록 추상화된 전송 API를 제공한다. 따라서 소켓 모드를 바꾸기 위해서 데이터 송수신 부분의 로직을 고치치 않아도 된다.