TCP 기반의 Half-close

TCP에서는 연결과정보다 중요한 것이 중료과정이다. 연결과정에서는 큰 변수가 발생하지 않지만 종료과정에서는 예상치 못한 일이 발생할 수 있기 때문이다. 따라서 종료과정은 명확해야 한다.

일방적인 연결종료의 문제점

리눅스의 close 함수호출과 윈도우의 closesocket 함수호출은 완전종료를 의미한다. 완전종료라는 것은 데이터를 전송하는 것은 물론이거니와 수신하는 것 조차 더 이상 불가능한 상황을 의미한다. 때문에 한쪽에서의 일방적인 close 또는 closesocket 함수호출은 경우에 따라서 우아해 보이지 못할 수 있다.

halfclose.png

위 그림은 양방향을 통신하고 있는 두 호스트의 상황을 묘사한 것이다.

호스트 A가 마지막 데이터를 전송하고 나서 close 함수의 호출을 통해서 연결을 종료하였다. 때문에 그 이후부터 호스트 A는 호스트 B가 전송하는 데이터를 수신하지 못한다. 아니! 데이터 수신과 관련돤 함수의 호출 자체가 불가능하다. 때문에 결국엔 호스트 B가 전송한, 호스트 A가 반드시 수신해야 할 데이터라 할지라도 그냥 소멸되고 만다. 이러한 상황을 해결하기 위해서 Half-close 가 제공된다.

소켓과 스트림

소켓을 통해서 두 호스트가 연결되면, 그 다음부터는 상호간에 데이터의 송수신이 가능한 상태가 된다. 그리고 바로 이러한 상태를 가리켜 ‘스트림이 형성된 상태’라 한다. 즉, 두 소켓이 연결되어서 데이터의 송수신이 가능한 상태를 일종의 스트림으로 보는 것이다.

스트림은 물의 흐름을 의미한다. 그런데 물의 흐름은 한 쪽 방향으로만 형성된다. 마찬가지로 소켓의 스트림 역시 한쪽 방향으로만 데이터의 이동이 가능하기 때문에 양방향 통신을 위해서는 두 개의 스트림이 필요하다.

doublestream.png

때문에 두 호스트간에 소켓이 연결되면, 각 호스트 별로 입력 스트림과 출력 스트림이 형성된다.

우아한 종료라는 것은 이 두 스트림을 모두 끊는 것이 아니라, 이 중 하나의 스트림만 끊는 것이다. 물론 리눅스의 close 함수와 윈도우의 closesocket 함수는 두 가지 스트림을 동시에 끊어버리기 때문에 우아한 연결종료와는 거리가 멀다.

소켓이 종료되면, 입력 버퍼에 있는 내용은 그대로 남아있고, 출력 버퍼에 있는 내용은 마저 상대에게 전송된다.

우아한 종료를 위한 shutdown 함수

그럼 이제 우아한 종료 즉, Half-close에 사용되는 함수를 소개하겠다. 다음 shutdown 함수가 스트림의 일부를 종료하는데 사용되는 함수이다.

#include <sys/socket.h>

int shutdown(int sock, int howto);
// 성공 시 0, 실패 시 -1 반환
  • sock: 종료할 소켓의 파일 디스크립터 전달
  • howto: 종료방법에 대한 정보 전달

위의 함수호출 시 두 번째 매개변수에 전달되는 인자에 따라서 종료의 방법이 결정된다. 두 번째 매개변수에 전달될 수 있는 인자의 종류는 다음과 같다.

howto 매개변수 의미
SHUT_RD 입력 스트림 종료
SHUT_WR 출력 스트림 종료
SHUT_RDWR 입출력 스트림 종료

Half-close가 필요한 이유
출력 스트림을 종료하여 상대에게 EOF를 보내고, 받을 데이터가 남아있을 떄 유용하다.