서버는 꺼지지 않고 무한히 클라이언트의 요청에 응답해야 한다.

이러한 특성 때문에 서버를 이루는 코드는 일반적으로 무한 루프를 포함한다.
그리고 그 무한 루프는 대충 아래와 같은 구조를 띈다.


새로운 클라이언트의 연결을 수락(accept()) -> 클라이언트로부터 요청이 있는지 체크(read()) -> 응답(write()) -> 반복...

문제는 accept(), read(), write() 모두 blocking 될 수 있는 함수라는 것이다.

accept()는 클라이언트의 연결 요청이 없을 경우 새로운 요청이 도착할 때까지 대기한다.
read()의 경우 버퍼에 읽을 데이터가 없을 때 새로운 데이터가 도착할 때까지 대기한다.
write()의 경우 버퍼에 데이터를 작성할 공간이 없을 때 작성할 공간이 생길 때까지 대기한다.

문제는 서버에서 이러한 blocking이 발생할 경우 다음 로직의 실행에 지연이 발생하기 때문에 여러 개의 클라이언트의 요청을 동시에 처리하는 것이 아닌 순차적으로 처리하게 된다는 문제점이 있다.


새로운 클라이언트의 연결을 수락하기 위해 accept()에서 대기 -> 클라이언트로부터 요청을 받기 위해 read()에서 대기 -> 응답을 위해 write()에서 대기(다만 write()에서 대기가 발생하는 경우는 드물다) -> 반복...

이러한 문제점을 해결하여(즉, blocking으로부터 발생하는 지연을 최대한 없애버림으로서) 접속하고 있는 클라이언트에게 동시에 서비스를 제공하는 서버를 다중 접속 서버라 칭한다.

다중 접속 서버는 다음의 수단을 사용하여 구현할 수 있다.

  1. 멀티 프로세스 기반 서버
  2. 멀티 쓰레딩 기반 서버
  3. 멀티 플렉싱 기반 서버
  4. fcntl이나 set_socket 등의 시스템 콜을 사용하여 소켓이 가리키는 fd가 non-blocking으로 동작하도록 설정한다.

위의 방법 중 4번은 시스템 콜을 호출하는 빈도가 매우 증가하여 성능 상의 불이익이 있으므로 사용하지 않는 것이 좋다.

참고할만한 자료들


원본 링크


참고자료

윤성우의_열혈_TCP_IP_소켓_프로그래밍