삽질의 현장/- 네트워크 프로그래밍

#017_Window_Network_Non Blocking Socket

shovelman 2015. 10. 8. 16:29


안녕하세요 삽잡이입니다.


이번 시간에는 Non Blocking Socket에 대해서 배워보려고합니다.


소켓에는 두 가지의 종류가 있습니다.

바로, '블록킹 소켓'과 '넌 블록킹 소켓'이 있는 것입니다.


필자가 Nework Programming 에 대해서 글을 올리며 사용했던 방식은 모두 블록킹 소켓이었습니다.

블록킹 소켓을 사용한다면,

블록킹 소켓 내 IO 함수들은 자신의 목적(기능)을 완료할 때 까지 Blocking 상태에 놓입니다.

이 블록킹 상태에 놓이는 I/O 함수들은 

accept(), recv(), send(), connect() 함수등과 UDP에서 사용하는 recvfrom(), sendfrom()함수 등이 있습니다.

즉, 이와 같은 I/O 함수들이 자신의 목적을 완료할 때 까지 Blocking 상태로 놓이게 된다 이겁니다...


accept() 함수를 예로 봅시다.

클라이언트의 접속이 들어오면 서버에서 요청을 수락하는 함수가 바로 accept()입니다.

목적을 다하는 동안 blocking 상태로 있습니다.


넌 블록킹 함수들은

I/O 함수들이 자신의 목적을 다하지 못했더라도 blocking 상태에 빠지지 않습니다.

즉, 목적을 달성하지 못하였더라도 return을 하고 다른 일을 수행하게 된다 이겁니다.


accept() 함수가 블로킹 소켓이라면 다루기 편합니다.

그런데, 넌 블록킹 소켓이라면 

클라이언트가 접속 요청을 하던 안하던 return을 하기 때문에 

자신의 목적을 달성하지 못했다면 다시 가봐야하겠지요...

결과적으로 코드가 블로킹 소켓을 사용할 때보다 복잡해질 수 있습니다.


그렇지만, 블로킹 소켓은 I/O 함수가 block 되기 때문에 다른 작업을 하지 못하지요...

넌 블로킹 소켓은 다른 작업을 할 수 있는데 말입니다...


즉, 블록킹 소켓은 쓰레드를 사용해서 I/O 함수를 수행하게 되면 다른 작업을 못하지만,

논 블로킹 소켓은 다른 작업과 병행할 수 있는 장점이 있다는 것입니다.

하지만, 단점으로는 계속해서 확인을 해줘야하는 복잡한 코드가 생길 수 있다 이겁니다.


필자가 지금까지 언급했던 네트워크 모델 방식은 모두 쓰레드 I/O 모델이었습니다.

즉, 입출력을 할 때에 쓰레드를 사용하는 모델을 말하는 것이었습니다.

이 방법은 쓰레드가 많아질 수록 쓰레드가 오고 가는 switching time이 오래 걸리기 때문에

쓰레드가 많아지면 비효율적으로 돌아가는 문제가 발생하게 됩니다.


따라서 대부분의 프로그램 모델에서는 넌 블로킹 소켓 모델을 사용합니다.

그렇지만, 그냥 넌 블로킹 소켓을 사용하는 것이 아니지요....

이 부분에 대해서는 나중에 다시 언급하도록 하겠습니다.


자... 블록킹 소켓을 만들어주기 위해서는 '넌 블록킹 소켓으로 전환'이 필요합니다.


 

ioctlsocket() 함수를 통해 넌 블록킹 소켓으로 전환을 하게 됩니다.

두번째 인자인 'FIONBIO'의 경우 Flag I/O Non Blocking I/O 의 줄임말로써,

unsigned long 타입의 데이터 형을 true로 해주면 

결과적으로 '넌 블로킹 함수'로 동작하게 된다 이겁니다.

넌 블로킹 함수로 만들어진 통신 소켓들은 모두 다 넌 블록킹 소켓이 됩니다.


지금까지 우리가 블로킹 소켓으로 만들어 사용할 때 사용하던 I/O 함수들은 블로킹 상태의 함수였습니다. 

그리고, 통신 소켓들도 블로킹 소켓이었지요... 

하지만, 넌 블로킹 소켓으로 전환이 된다면, 

블로킹 함수들도 모두 넌 블로킹 상태로, 통신 소켓들도 모두 넌 블로킹 소켓이 되는 것입니다.

왜냐하면, 통신 소켓은 대기 소켓에 의해 만들어지는데 

대기소켓이 넌 블록킹 소켓이 되었으니 통신 소켓도 넌 블로킹 소켓이 되는 것입니다.


그렇다면, 넌 블로킹 상태가 되었다면, I/O 함수가 성공했는지 실패했는지 모르게됩니다.

블로킹 상태가 아닌 바로 return 을 해주기 때문이지요...

따라서, return 값으로 성공 유무를 판단해야합니다.


또 accept() 함수를 예로 들어보겠습니다.

accept() 함수를 성공적으로 return 했다면 상관이 없겠지만 실패했을 경우에,

접속이 안되고 에러가 발생했기 때문에 return이 됬는지,

클라이언트가 접속을 하지 않아서 return을 했는지 return 값을 통해 알아야한다 이겁니다.


이 return 값들을 확인하기 위해서는 WSAGetLastError() 함수가 필요합니다.



모든 커널 오브젝트에는 에러 코드를 담을 수 있는 공간이 있다고 했습니다.

따라서, 해당 커널 오브젝트의 에러코드를 확인하는 것입니다.

그런데, 이렇게 계속해서 accept() 함수가 돌아가면...

즉, 연결이 계속 안되어서 루프를 돌며 accept()를 확인하다보면 굉장히 비효율적으로 동작하게 됩니다.


따라서 넌 블로킹 소켓은 해줘야할 일들도 많고, 

비효율적으로 동작할 가능성이 농후하기 때문에 단순하게 사용되지 않습니다. 

즉, 위에서 잠시 언급했던 

추가적으로 넌 블로킹을 제대로 동작시킬 '입출력 모델'이 필요하다 이겁니다.


넌 블로킹 소켓은 하나의 쓰레드로 네트워크 I/O 작업 외에 또 다른 작업들을 할 수 있는 장점이 있지만,

실제 네트워크 작업을 위해서는 계속해서 확인을 해야할 경우들이 많습니다.

따라서, 단지 넌 블로킹 소켓만을 사용하고 다른 메커니즘의 도움이 없다면 굉장히 비효율적이라는 것입니다.


정리하자면, 넌 블로킹 소켓은 독립적으로 사용되지 않고 

다른 메커니즘과 '결합'되어서 사용된다는 것입니다.


따라서 우리는 다음 시간부터 여러 모델들을 배우게 될 것입니다.


물론, 여러 모델들을 배우지 않더라도 

지금까지 배운 내용을 통해 충분히 네트워크 통신을 할 수 있습니다.

하지만, 효율성에 대해서는 의문이 들 수 있습니다.


아무튼... 다음시간부터

효율적으로 프로그램할 수 있는 I/O(입출력) 모델을 배워보도록 하겠습니다.


이상 삽잡이였습니다!