[열혈TCP/IP] 07 소켓의 우아한 연결종료 Half-close

1 minute read

07 소켓의 우아한 연결종료 Half-close

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

리눅스의 clsoe()의 호출은 완전종류를 의미한다. 완전종료라는 것은 데이터를 전송하는 것은 물론, 수신하는 것도 더 이상 불가능한 상황을 의미한다. 때문에 한쪽에서의 일방적인 close()의 호출은 우아하지 못하다.

두 호스트가 데이터를 주고 받을 때 한 쪽이 먼저 완전종료를 해버리면 아직 송신했지만 수신되지 못한 데이터가 있을 수 있다. 이러한 문제를 해결하기 위해서 Half close의 개념을 사용한다. 이는 전송은 가능하지만 수신은 불가능한 상황, 혹은 수신은 가능하지만 전송은 불가능한 상황을 뜻한다. 말 그대로 스트림의 반만 닫는 것이다.

소켓과 스트림Stream / connection 과 stream

소켓을 통해서 두 호스트가 연결되면, 그 다음부터는 상호간에 데이터를 송수신이 가능한 상태가 된다. 이러한 상태를 stream이 형성된 상태라고 한다. 즉, 두 소켓이 연결되어서 데이터의 송수신이 가능한 상태를 일종의스트림으로 보는 것이다. stream은 물의 흐름을 의미한다. 물의 흐름은 한쪽 방향으로만 형성된다. 따라서 소켓 스트림 역시 양방향 데이터의 이동이 가능하기 위해서는 두 개의 스트림이 필요하다.

때문에 두 호스트간에 소켓이 연결되면, 각 호스트 별로 입력 스트림과 출력 스트림이 형성된다. 물론 한 호스트의 출력 스트림은 다른 호스트의 입력스트림으로 이어진다. Half close라는 것은 둘 중 하나의 stream만 끊는 것이다. 물론 리눅스의 close() 호출은 두 가지 스트림을 동시에 끊어서 우아하지 못하다.

우아한 종료를 위한shutdown() 함수

#include <sys/socket.h>

int shutdown(int sock, int howto);
/*
return 성공시 0, 실패시 -1
sock : 종료한소켓의 fd 전달
howto : 종료 방법에 대한 정보 전달
    - SHUT_RD : 입력 스트림 종료
    - SHUT_WR : 출력 스트림 종료
    - SHUT_RDWR : 입출력 스트림 종료
*/
  • SHUT_RD를 전달하면 입력스트림이 종료된다. 데이터가 입력 버퍼에 전달되더라도 지워지고 입력관련 함수의 호출도 불가능한 상태가 된다.
  • SHUT_WR를 전달하면 출력스트림이 종료된다. 더이상의 데이터를 전송하는 것이 불가능해지지만 아직 출력버퍼에 데이터가 남아있으면 해당데이터는 모두 목적지로 전송된다.

Half-close가 필요한 이유

  1. 연결 종료 직전에 클라가 서버에 전송할 데이터(“아래의 Thank you”)가 있는 경우, 서버는 데이터를 계속 보내면 되지만 클라는 언제 데이터를 수신해야하는지 잘 모른다. 계속해서 입력 함수를 호출하면 블로킹상태(호출된 함수가 반환하지 않는 상태)에 빠질 수 있다.
  2. 전송되는 파일의 끝에 특정 문자를 추가해도 특정 문자가 데이터 파일에 존재할 수도 있기 때문에 적절하지 못하다. 따라서 이를 위해서 서버는 파일의 전송이 끝났음을 의미하기 위해 EOF를 전송해야한다. 클라는 EOF의 수신을 함수의 반환값을 통해서 확인이 가능하기 때문에 저장된 데이터와 중복될 일도 없다.
  3. 서버는 출력 스트림을 종료하면 상대 호스트에게 EOF가 전송되기 때문에 신뢰가능한 데이터 송수신이 완성된다.

  4. 클라 : 연결요청
  5. 서버 : 파일 데이터
  6. 서버 : EOF
  7. 클라 : Thank you

파일 전송 예제 필요하면 책 174 페이지 참조