개발을 하다 보면 네트워크 통신이 필요한 경우가 정말로 많습니다.
당장 백엔드 애플리케이션에서 DB 서버를 호출하는 것도 네트워크 통신이죠
그런데 이 네트워크 통신이 I/O 작업의 한 종류입니다
그렇기 때문에 I/O가 무엇인지, 특히 네트워크 I/O가 어떻게 동작하는지 기본적인 지식을 잘 알고 있어야 보다 더 나은 성능의 애플리케이션을 개발을 할 수 있습니다
백엔드에서 최근 몇 년 간의 트렌드는 nonblock I/O를 적극 사용함으로써 애플리케이션의 성능을 극대화하는 패러다임이 주류를 이루고 있습니다
가령 Spring의 webflux을 제대로 쓰려면 webflux 위에 동작하는 기능을 구현할 때 I/O가 nonblock으로 동작하도록 구현해야 합니다
만약 webflux를 쓰는데 block I/O로 통신하는 JDBC를 쓴다면 webflux의 장점을 제대로 발휘하지는 못할 망정 오히려 역효과를 낳게 되죠
그래서 개발을 제대로 알고 하려면, block I/O와 nonblock I/O의 차이를 잘 알고 있는 것이 중요합니다.
출처 : 쉬운코드 유튜브
I/O : input/output, 데이터의 입력과 출력
socket : 네트워크 통신은 socket을 통해 데이터가 입출력 된다.
네트워크 상의 요청자들과 각각 소켓을 열고 통신한다.
스레드에서 작성한 코드가 실행 중 read blocking 시스템콜을 호출하면 I/O를 위해 커널모드로 들어가고 커널은 I/O 작업을 시작한다. 응답값을 받고 커널 스페이스에서 유저 스페이스로 데이터를 옮긴다. 블락되었던 스레드는 값을 받은 후 다시 코드를 실행시킨다.
socket S에서 socket A로 데이터를 전송하고 socket A의 read 시스템 콜을 발생한 스레드는 recieve buffer에 데이터가 들어올때 까지 block된다.
socket S는 send buffer가 비어서 공간이 생길때 까지 block 된다.
프로세스/스레드를 블락시키지 않고 요청에 대한 현재 상태를 즉시 리턴
스레드에서 read non-blocking 시스템콜을 호출하면 커널모드로 컨텍스트 스위칭이 발생하고, 커널은 I/O 작업을 시작하고 응답값이 없어도 스레드에 바로 리턴한다. 스레드는 이어서 다른 코드를 시작하고, 커널에서 응답값이 준비되고 다시 non-blocking 시스템 콜을 발생시키면 커널은 응답값을 전달한다.
non-blocking I/O : 블락되지 않고 즉시 리턴하기 때문에 스레드가 다른 작업을 수행할 수 있다.
socket A의 recieve buffer에 데이터가 존재하지 않아도 read를 호출한 스레드는 블락되지 않고, socket S에 데이터가 없다는 현재 상태값만 리턴받는다.
socket S의 send buffer에 값이 채워져있어도 write를 호출한 스레드를 블락시키지 않고 send buffer에 대한 값만 리턴한다.
I/O 작업 완료를 어떻게 확인할 것인가?
읽기 작업을 요청한 스레드는 응답값을 기다리지 않고 다른 프로그램을 실행한다. 그 후 응답값이 준비되고 컨텍스트 스위칭이 발생한 경우 그 응답값을 확인하고 읽기 작업을 요청한 프로그램으로 리턴한다. (컨텍스트 스위칭이 읽어날때 마다 확인)
-> 문제점 : 완료된 시간과 완료를 확인한 시간 사이의 갭으로 인해 처리 속도가 느려질 수 있다.
non-block I/O 작업이 완료됐는지 반복적으로 확인하는 것은 CPU 낭비가 발생한다.
socket A는 recieve buffer에 값이 들어오는지 반복적으로 확인해야한다. -> CPU 낭비
네트워크 통신에 많이 사용
관심있는 I/O 작업들을 동시에 모니터링하고 그 중에 완료된 I/O 작업들을 한번에 알려줌
스레드는 I/O multiplexing system call을 호출하고 커널모드로 들어가 커널은 I/O를 시작한다. 스레드는 block or non-block 둘다 가능하다. 커널은 I/O 작업이 완료되면 응답값이 존재한다 스레드에 알리고 스레드는 응답값을 가지러 커널모드로 들어가 응답값을 리턴받는다.
select 와 poll은 성능면에서 떨어지기 때문에 잘 사용하지 않는다.
epoll(linux), kqueue(mac os), IOCP(window,I/O completion port)를 주로 사용한다.
epoll : epoll에 socket 중 한개라고 read event가 발생하면 알려달라 등록한다. 클라이언트로부터 데이터를 받은 소켓은 epoll은 어떤 socket이 클라이언트로부터 데이터를 받았는지 알리고, 스레드는 해당 소켓에서 데이터를 처리한다.
스레드는 aio_read non-blocking system call을 호출하고 컨텍스트 스위칭이 발생하고, 커널은 I/O 작업을 시작한다. I/O를 요청한 스레드는 바로 다른 프로세스를 동작하고, device로 부터 응답값이 오면 커널은 callback or signal과 함께 응답값을 리턴한다.
posix aio(대부분의 OS), linux aio
하지만, Callback/signal 형태는 자주 사용되지 않고 I/O multiplexing이 가장많이 사용된다.