출처 - http://devsw.tistory.com/142
TCP/IP 네트워크 프로그래밍 책을 보다보면 봉쇄(blocking), 비봉쇄(non-blocking), 동기(syncronous), 비동기(asyncronous)에 대해서 나옵니다. 이 개념에 대해 자세히 알아보려 합니다.
소켓 프로그래밍에서의 blocking
소켓 프로그래밍 책을 보다보면 read, write 함수를 가장 쉽게 보게 됩니다. 리눅스에서 read, write는 소켓 프로그래밍에서 패킷을 보내거나 받을때 사용하는 함수입니다. 이 함수는 소켓이 blocking이냐 non-blocking이냐 에 따라 작동방식이 살짝 다릅니다. 소켓이 blocking일 경우 Server 가 Client의 메시지 요청을 받기 위해 read에서 기다릴 수가 있습니다. Client가 write하기 전에는 read에서 빠져나오지를 못하는 것이지요.
소켓 프로그래밍에서의 non-blocking
위와 같이 blocking형태의 프로그래밍을 하다보면 서버 프로그램 입장에서는 여러 클라이언트의 처리가 어렵습니다. 그래서 나온 방법이 클라이언트 접속 별로 쓰레드를 생성하여 클라이언트별 read 함수를 호출 하는 것인데, 하지만 이 방법도 대규모 처리를 하기 위해서는 context switching에 발생하는 비용이 만만치 않아 프로그램의 성능을 떨어뜨립니다. 이러한 방법을 피할 수 있는 방법이 비봉쇄(non-blocking) 방법입니다. non-blocking은 위의 그림처럼 시스템함수 호출 후 멈출 필요가 없습니다. 읽을 데이터가 있으면 읽고, 없으면 넘어가게 되는 방법입니다.
blocking과 non-blocking의 정리
여기까지 봉쇄(blocking), 비봉쇄(non-blocking)에 대해서 알아 봤습니다. 봉쇄와 비봉쇄의 차이점은 파일디스크립터 관련 함수에서 기다리냐 마느냐의 차이점입니다. 비봉쇄에서 read는 읽을 데이터가 없을 시 에러(-1)을 return을 하게 되어 errno.h의 errno을 확인하여 EWOULDBLOCK(또는 EAGAIN) 일 경우 적절한 처리를 해주어야 하기 때문에 프로그래밍이 조금 더 복잡해 질 수도 있습니다. 참고로 non-blocking 소켓으로 connect를 할 때에도 connect 함수가 에러를 return (-1) 할 수가 있습니다. 하지만 이것도 errno를 확인해보면 EINPROGRESS가 발생하는데 이 말은 즉, 연결요청이 진행중이다 라는 에러 입니다. 즉, '연결 완전 실패가 아닌 진행 중이지만 일단 에러로 return 하니 프로그래머가 알아서 처리를 하라' 라는 뜻입니다. 이렇듯 non-blocking 소켓 프로그래밍을 할 경우 에러처리를 톡톡히 해주어야 하는 단점이 있습니다. 하지만 에러처리를 잘하면 어떠한 빈틈도 없는 멋진 프로그램이 될 수도 있다는 뜻이지요.
동기(Syncronous)
이제 동기가 무엇인지 궁금해 졌습니다. 동기는 사실상 blocking과 차이점이 없다고 보시면 됩니다. "내(어플리케이션)가 너(커널)의 작업(recvfrom)이 끝날 때 까지 기다려 줄께. 또는 시간을 맞춰줄께. 또는 동기화 해줄께" 라고 생각하시면 됩니다. 어플리케이션이 커널에 작업을 요청하고 커널에서 작업이 끝날때 까지 어플리케이션이 기다려 주는 것이지요. 즉, 동기화 하기 위해서 봉쇄(blocking)을 하는 것입니다. 그렇다면 비동기화(Asyncronous)하기 위해서 비봉쇄(non-blocking) 하는 것일 까요?
비동기(Asyncronous)
위에도 설명하였 듯이 비동기화 하기 위해 비봉쇄를 한다고 설명할 수 있을까요? 그렇다고 하기엔 내부적으로 작동하는 방법에 차이가 있습니다. 비동기의 간단한 개념을 따지자면 비동기 read함수를 호출하면 바로 return이 됩니다. 비동기 함수를 호출할 때는 작업이 완료가 될 때 알려줄 수 있는 event나 callback함수를 설정하게 됩니다. 즉, 어플리케이션이 커널에게 "읽을게 있으면 나에게 알려주던가(event) callback함수를 호출해~" 라고 하는 것입니다.
마무리
비봉쇄(non-blocking)과 비동기(Asyncronous)는 다른 개념입니다. 특히 비동기 프로그래밍을 택한다면 소켓을 비봉쇄 소켓으로 만들면 안된다는 이야기가 있습니다. 제가 직접 테스트를 해본 것은 아니지만 stack overflow 사이트에서는 섞어서 사용하면 안된다고 나와 있네요. 아마도 커널 내부적으로 소켓이 blocking되어 작업이 끝나고 어플리케이션에 알려주어야 하는데 비봉쇄로 나와 버린다면 적업에 에러가 발생할 것 같습니다. 이부분에 대해서는 좀 더 연구가 필요하겠습니다.
TCP/IP 네트워크 프로그래밍 책을 보다보면 봉쇄(blocking), 비봉쇄(non-blocking), 동기(syncronous), 비동기(asyncronous)에 대해서 나옵니다. 이 개념에 대해 자세히 알아보려 합니다.
소켓 프로그래밍에서의 blocking
소켓 프로그래밍 책을 보다보면 read, write 함수를 가장 쉽게 보게 됩니다. 리눅스에서 read, write는 소켓 프로그래밍에서 패킷을 보내거나 받을때 사용하는 함수입니다. 이 함수는 소켓이 blocking이냐 non-blocking이냐 에 따라 작동방식이 살짝 다릅니다. 소켓이 blocking일 경우 Server 가 Client의 메시지 요청을 받기 위해 read에서 기다릴 수가 있습니다. Client가 write하기 전에는 read에서 빠져나오지를 못하는 것이지요.
Blocking I/O 모델에서의 작동 방식 : 어플리케이션(프로그램)에서 recvfrom(read)를 호출 할 때, 커널은 데이터가 들어올 때까지 봉쇄(blocking)을 시킨다. 데이터가 들어오면 recvfrom(read)함수는 return이 되고, 프로그램 흐름은 어플리케이션(프로그램)으로 돌아온다.
위와 같이 blocking형태의 프로그래밍을 하다보면 서버 프로그램 입장에서는 여러 클라이언트의 처리가 어렵습니다. 그래서 나온 방법이 클라이언트 접속 별로 쓰레드를 생성하여 클라이언트별 read 함수를 호출 하는 것인데, 하지만 이 방법도 대규모 처리를 하기 위해서는 context switching에 발생하는 비용이 만만치 않아 프로그램의 성능을 떨어뜨립니다. 이러한 방법을 피할 수 있는 방법이 비봉쇄(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의 정리
여기까지 봉쇄(blocking), 비봉쇄(non-blocking)에 대해서 알아 봤습니다. 봉쇄와 비봉쇄의 차이점은 파일디스크립터 관련 함수에서 기다리냐 마느냐의 차이점입니다. 비봉쇄에서 read는 읽을 데이터가 없을 시 에러(-1)을 return을 하게 되어 errno.h의 errno을 확인하여 EWOULDBLOCK(또는 EAGAIN) 일 경우 적절한 처리를 해주어야 하기 때문에 프로그래밍이 조금 더 복잡해 질 수도 있습니다. 참고로 non-blocking 소켓으로 connect를 할 때에도 connect 함수가 에러를 return (-1) 할 수가 있습니다. 하지만 이것도 errno를 확인해보면 EINPROGRESS가 발생하는데 이 말은 즉, 연결요청이 진행중이다 라는 에러 입니다. 즉, '연결 완전 실패가 아닌 진행 중이지만 일단 에러로 return 하니 프로그래머가 알아서 처리를 하라' 라는 뜻입니다. 이렇듯 non-blocking 소켓 프로그래밍을 할 경우 에러처리를 톡톡히 해주어야 하는 단점이 있습니다. 하지만 에러처리를 잘하면 어떠한 빈틈도 없는 멋진 프로그램이 될 수도 있다는 뜻이지요.
동기(Syncronous)
이제 동기가 무엇인지 궁금해 졌습니다. 동기는 사실상 blocking과 차이점이 없다고 보시면 됩니다. "내(어플리케이션)가 너(커널)의 작업(recvfrom)이 끝날 때 까지 기다려 줄께. 또는 시간을 맞춰줄께. 또는 동기화 해줄께" 라고 생각하시면 됩니다. 어플리케이션이 커널에 작업을 요청하고 커널에서 작업이 끝날때 까지 어플리케이션이 기다려 주는 것이지요. 즉, 동기화 하기 위해서 봉쇄(blocking)을 하는 것입니다. 그렇다면 비동기화(Asyncronous)하기 위해서 비봉쇄(non-blocking) 하는 것일 까요?
비동기(Asyncronous)
위에도 설명하였 듯이 비동기화 하기 위해 비봉쇄를 한다고 설명할 수 있을까요? 그렇다고 하기엔 내부적으로 작동하는 방법에 차이가 있습니다. 비동기의 간단한 개념을 따지자면 비동기 read함수를 호출하면 바로 return이 됩니다. 비동기 함수를 호출할 때는 작업이 완료가 될 때 알려줄 수 있는 event나 callback함수를 설정하게 됩니다. 즉, 어플리케이션이 커널에게 "읽을게 있으면 나에게 알려주던가(event) callback함수를 호출해~" 라고 하는 것입니다.
Asyncronous I/O 모델에서의 작동 방식 : 어플리케이션(프로그램)에서 aio_read를 호출 할 때, 커널은 읽을 데이터가 있던 없던 return을 하게 되며, 읽을 데이터가 발생하게 되면 event를 발생하거나 callback함수를 호출하게 된다.
위의 그림과 같이 어플리케이션에서 aio_read를 호출하고 바로 return을 받게 됩니다. 커널은 데이터가 들어오면 event를 발생시키거나 callback함수를 호출하게 됩니다. 비동기 네트워크 프로그래밍을 할 때, event나 callback함수를 사용하게 됩니다. 즉, 비동기 프로그래밍은 non-blocking처럼 지속적인 관찰이 필요한 프로그래밍이 아니라 일단 작업을 걸어 두고 다른 작업을 진행한다가 어떠한 신호나 작업이 들어올 경우 걸어둔 작업을 진행한다. 라고 생각하시면 좀 더 수월해 질 것입니다.마무리
비봉쇄(non-blocking)과 비동기(Asyncronous)는 다른 개념입니다. 특히 비동기 프로그래밍을 택한다면 소켓을 비봉쇄 소켓으로 만들면 안된다는 이야기가 있습니다. 제가 직접 테스트를 해본 것은 아니지만 stack overflow 사이트에서는 섞어서 사용하면 안된다고 나와 있네요. 아마도 커널 내부적으로 소켓이 blocking되어 작업이 끝나고 어플리케이션에 알려주어야 하는데 비봉쇄로 나와 버린다면 적업에 에러가 발생할 것 같습니다. 이부분에 대해서는 좀 더 연구가 필요하겠습니다.
안녕하세요. 도움이 되는 글 감사합니다. 제가 완벽히 이해하지는 못한채 일단 비봉쇄 비동기 소켓 서버를 만들어서 구현을 해봤는데 작동은 됩니다만, 이게 권장되지 않는다는 건가요?? 제가 아직 확실히 몰라서 여쭤봅니다.
답글삭제