(네트워크)네트워크 프로그래밍에서의 blocking, non-blocking, syncronous, asyncronous

<소켓 프로그래밍에서의 blocking>
소켓 프로그래밍 책을 보다보면 read, write 함수를 가장 쉽게 보게 됩니다. 리눅스에서 read, write는 소켓 프로그래밍에서 패킷을 보내거나 받을때 사용하는 함수입니다. 이 함수는 소켓이 blocking이냐 non-blocking이냐 에 따라 작동방식이 살짝 다릅니다
소켓이 blocking 일 경우 Server가 Client의 메시지 요청을 받기 위해 read에서 기다릴 수 있습니다. 즉 Client가 read에서 빠져나오지를 못하는 것입니다.

Blocking I/O 모델에서의 작동 방식 : 어플리케이션(프로그램)에서 recvfrom(read)를 호출 할 때, 커널은 데이터가 들어올 때까지 봉쇄(blocking)을 시킨다. 데이터가 들어오면 recvfrom(read)함수는 return이 되고, 프로그램 흐름은 어플리케이션(프로그램)으로 돌아온다.


<소켓 프로그래밍에서의 non-blocking>위와 같이 blockig 형태의 프로그래밍을 하다보면 서버 프로그램 입장에서는 여러 클라이언트 처리가 매우어렵습니다. 그래서 나온 방법이 클라이언트 접속 별로 쓰레드를 생성하여 클라이언트 별 read함수를 호출 하는 것인데, 하지만 이 방법도 대규모 처리를 하기 위해서는 context switchig에 발생하는 비용이 만만치 않아 프로그램의 성능을 떨어뜨립니다. 이러한 방법을 피할 수 있는 방법이 (non-blocking)방법입니다. non-blocking은 위의 그림처럼 시스템 함수 호출 후 멈출 필요가 없습니다. 읽을 데이터가 있으면 읽고, 없으면 넘어가는 방법입니다.





Non-blocking I/O 모델에서의 작동 방식 : 어플리케이션(프로그램)에서 recvfrom(read)를 호출 할 때, 커널은 읽을 데이터가 없으면 EWOULDBLOCK(또는 EAGAIN, 둘의 의미는 똑같다.)을 return 한다.위의 그림과 같이 소켓에 읽을 데이터가 있는지 없는지 recvfrom(read)을 loop 돌면서 계속 호출해주어야 합니다. (이 부분이 나중에 설명할 비동기 I/O와 살짝 다릅니다.)  이 방법은 결국 모든 소켓에 대한 체크를 해야하는 busy-waiting 상태가 됩니다. 리눅스에서는 적절한 sleep이 없는 loop는 100%의 CPU 점유율을 갖게 됩니다. 참고로 윈도우즈에서는 그런 현상이 없습니다.모든 소켓에 대해 확인을 할 수가 없어 사용하는 방법이 멀티 플렉스를 사용하는 것입니다. 리눅스 계열은 select, poll, epoll을 사용하게 됩니다. 즉, 이 기술들은 데이터 입출력에 직접적으로 필요한 기술이 아니라 파일디스크립터(소켓)을 관리하기 위한 것이라 보면 됩니다.<blocking과 non-blocking의 정리>봉쇄와 비봉쇄의 차이점은 파일디스크립터 함수에서 기다리느냐 마느냐의 차이입니다.비봉쇄에서는 read는 읽을 데이터가 없을 시 에러(-1)을 return을 하게되어 errno.h의 errno을 확인하여 EWOULDBLOCK(또는 EAGAIN)일 겨우 적절한 처리를 해주어야 하기 떄문에 프로그래밍이 조금 더 복잡해 질 수도 있습니다. 참고로 non-blocking소켓으로 connect를 할떄도 connectㅎㅁ수가 에러를 return(-1)할 수 가 있습니다. 

<동기(Synchronous)>
동기는 사실상 blocking과 차이점이 없다고 보면 된다. "내(어플리케이션)가 너(커널)의 작업이(recvfrom)이 끝날 때까지 기다려줄게, 또는 시간을 맞춰줄게.또는 동기화 해줄게"라고 생각하면된다. 어플리케이션이 커널에 작업을 요청하고 커널에서 작업이 끝날 때 까지 어플리케이션이 기다려주는 것이다.즉, 동기화 하기 위해서 봉쇄(blocking)을 하는 것입니다. 그렇다면 비동기화(Asyncronous)하기 위해서 비봉쇄(non-blocking) 하는 것일 까요?
<비동기(Asynchronous)>
비동기의 간단한 개념을 따지자면 비동기 read함수를 호출하면 바로 return이 됩니다. 비동기 함수를 호출할 떄는  작업이 완료될 떄 알려줄수 있는 event나 callback함수를 설정하게 됩니다. 즉, 어플리케이션이 커널에게"읽을게 있으면 나에게 알려주던가(event), callback함수를 호출해~"라고 하는 것이다."Asyncronous I/O 모델에서의 작동 방식 : 어플리케이션(프로그램)에서 aio_read를 호출 할 때, 커널은 읽을 데이터가 있던 없던 return을 하게 되며, 읽을 데이터가 발생하게 되면 event를 발생하거나 callback함수를 호출하게 된다.
마무리
비봉쇄(non-blocking)과 비동기(Asyncronous)는 다른 개념입니다. 특히 비동기 프로그래밍을 택한다면 소켓을 비봉쇄 소켓으로 만들면 안된다는 이야기가 있습니다. 제가 직접 테스트를 해본 것은 아니지만 stack overflow 사이트에서는 섞어서 사용하면 안된다고 나와 있네요. 아마도 커널 내부적으로 소켓이 blocking되어 작업이 끝나고 어플리케이션에 알려주어야 하는데 비봉쇄로 나와 버린다면 적업에 에러가 발생할 것 같습니다. 이부분에 대해서는 좀 더 연구가 필요하겠습니다.   


댓글

이 블로그의 인기 게시물

(네트워크)폴링방식 vs 롱 폴링방식

(ElasticSearch) 결과에서 순서 정렬

(18장) WebSocekt과 STOMP를 사용하여 메시징하기