이전 시간에 소켓에 대해 다뤘다. 이제 실시간으로 양방향 통신을 하는 이녀석에 대해 파헤칠 시간이다. 소켓 이녀석은 블로킹/논블로킹, 동기/비동기로 나뉘어지는데, 이번 시간에는 블로킹에 대해 다뤄보고자 한다.
그렇다. 말 그대로 '막는다' 그 자체를 뜻한다. 특정 디바이스에 처리 요청을 보내고 이에 대한 응답을 대기하는 함수를 호출할 때, 스레드에서 발생하는 대기 현상을 블로킹이라고 한다. 블로킹 상태에 빠지면 스레드에서 CPU를 사용하지 않는다. 스레드는 처리 요청이 끝날 때까지 대기하고 작업이 완전히 끝났을 때 대기 상태에서 빠져나온다.
소켓에서의 블로킹도 비슷하다. 소켓을 통해 수신 함수를 호출하면 수신할 데이터가 도착할 때까지 대기 상태에 빠진다. 이것이 소켓에서의 블로킹이다. 근데 만약 데이터가 도착하지 않는다면, 스레드를 강제종료하지 않는 한, 영원히 대기상태에 빠질 것이다.
소켓은 송신 버퍼와 수신 버퍼가 있다. 송신버퍼의 경우 tcp socket 계열 함수 send함수를 호출하면 파라미터로 들어온 데이터가 송신버퍼에 채워진다. 버퍼에 데이터가 들어오면 전송이 시작되며, 데이터가 조금씩 버퍼에서 나오다가 전송이 완료되면 완전히 버퍼에서 빠져 나간다. 수신버퍼도 비슷하다. tcp socket 계열 함수 recv함수를 호출하면 다른 소켓이 send를 통해 전송한 데이터가 수신버퍼에 들어오게 된다. 수신버퍼의 데이터를 읽어내고 모두 읽었다면 버퍼에서 빠져나와 결과값으로 반환된다.
버퍼는 한정적인 메모리 공간이다.
버퍼도 하나의 메모리 공간이다. 당연히, 최대 크기가 정해져있기 때문에 최대 용량까지 찼다면 다음 데이터는 버퍼에 들어갈 수 없다. 버퍼에 빈 자리가 생길 때까지 기다릴 수 밖에 없다(그래도 버리진 않아서 다행). 우리는 이 순간을 블로킹에 빠졌다 라고 볼 수 있다.
이 때, TCP와 UDP 간 블로킹에 대응하는 방법이 차이가 난다. TCP의 경우, 데이터를 수신하는 소켓의 수신버퍼가 가득찰 경우 데이터를 송신하느 소켓의 송신파트도 블로킹에 빠지고 데이터를 송신하지 않는다. 다행인 점은 블로킹 상태에 빠져도 연결이 끊기지 않는다.
하지만 UDP의 경우는 다르다. 수신측에서 데이터를 수신하다가 버퍼가 가득 찼을 경우, 송신측에서는 이를 무시하고 데이터를 보내는데, 수신 버퍼는 가득 차서 못 들어가므로 이를 그냥 버린다. 그래서 UDP 통신은 데이터가 손실될 수 있다고 하는 이유가 바로 이것이다. 그렇다고 송신측은 송신을 멈추지 않는다(...).
블로킹을 블로킹하고 싶다.
게임 서버 입장에서 고려해보면, 하나의 서버가 여러 개의 클라이언트를 상대해야 한다. 서버측에서 클라이언트의 송신 데이터를 처리하다 결국 블로킹이 발생할 것이다. 그러면 클라이언트들은 애버랜드 줄서기마냥 처리를 기다려야 할 것이다. 실시간성이 중요한 온라인 게임에서는 용납할 수 없는? 일이다.
그렇다면, 우리는 대기 상태에 빠지는 것을 방지해야 한다. 클라이언트의 송신이 완료되는 시간이 길어지더라도, 계속 그 송신 작업을 기다릴 순 없다. 그래서 다음에 소스코드와 함께 논블로킹에 대해 다뤄보고자 한다.
게임서버 프로그래밍 교과서(저:배현직),
[C++과 언리얼로 만드는 MMORPG 게임개발 시리즈] Part4: 게임서버(강사 : 루키스)를 학습하고 정리한 내용입니다.
즐겁게 읽었습니다. 유용한 정보 감사합니다.