[Socket] 소켓의 Half-Close

TCP에서는 연결과정보다 중요한 것이 종료 과정이다. 종료 과정에서는 예상치 못한 일이 발생할 수 있기 때문이다.


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

close 함수호출은 완전 종료를 의미한다. 완전 종료라는 것은 데이터를 전송하는 것은 물론 수신하는 것조차 더 이상 불가능한 상황을 의미한다. 상대방의 상태에 상관없이 일방적인 종료의 형태를 띤다. 때문에 한쪽에서의 일방적인 close 함수호출은 경우에 따라 좋지 못하다.

위 그림은 양방향으로 통신하고 있는 두 호스트의 상황을 나타낸 그림이다.

Host A가 마지막 데이터를 전송하고 나서 close함수의 호출을 통해 연결을 종료하였다. 그래서 그 이후부터는 Host A는 Host B가 전송하는 데이터를 수신하지 못한다(데이터 수신과 관련된 함수의 호출 자체가 불가능). 결국에 Host B가 전송한, Host A가 반드시 수신해야 할 데이터라 할지라도 그냥 소멸되고 만다.

이러한 문제를 해결하기 위해 데이터의 송수신에 사용되는 스트림의 일부만 종료하는 Half-close방식을 사용한다. → 전송을 가능하지만 수신은 불가능한 상황, 혹은 그 반대.

 

소켓과 스트림(Socket & Stream)

소켓을 통해 두 호스트가 연결되면, 상호 간에 데이터의 송수신이 가능한 상태가 되는데, 이러한 상태를 가리켜 스트림이 형성된 상태라고 한다. 소켓의 스트림은 한쪽 방향으로만 데이터의 이동이 가능하기 때문에 양방향 통신을 위해서는 아래의 그림과 같이 두 개의 스트림이 필요하다.

따라서 두 호스트간에 소켓이 연결되면, 각 호스트 별로 입력 스트림출력 스트림이 형성된다(한 호스트의 입력 스트림은 다른 호스트의 출력 스트림으로 이어진다). Half-close란, 이 두 스트림을 모두 끊어버리는 것이 아니라, 이 중 하나의 스트림만 끊는 것이다. 일반적으로 Half-close라 하면, 입력 스트림만 종료하는 것을 의미한다. close함수호출은 두 스트림을 동시에 끊어버린다.


Shut-down 함수와 그 필요성

Half-close에 사용되는 함수로, 스트림의 일부를 종료한다.

#include <sys/socket.h>
int shutdown(int sock, int howto);
//성공 시 0, 실패 시 -1 return
  • sock : 종료할 소켓의 FD
  • howto : 종료방법에 대한 정보
    • SHUT_RD : 입력 스트림 종료
      • 더 이상 데이터를 수신할 수 없는 상태
      • 데이터가 입력 버퍼에 전달되더라도 지워져 버린다.
    • SHUT_WR : 출력 스트림 종료
      • 더 이상 데이터를 전송할 수 없는 상태
      • 출력 버퍼에 아직 전송되지 못한 상태로 남아있는 데이터가 존재하면, 해당 데이터는 목적지로 전송된다.
    • SHUT_RDWR : 입출력 스트림 종료
출력 스트림을 종료하면 상대 호스트로 EOF도 전송되고, 입력 스트림은 여전히 살아있어 데이터의 수신이 가능하다.

Half-close 기반 파일 전송 프로그램

파일 전송 서버, 클라이언트의 데이터 흐름을 정리하면 다음과 같다.

Server.c

...
while(1)//accept 함수호출을 통해서 연결된 clinet에게 파일 데이터를 전송
{
	read_cnt = fread((void*)buf, 1, BUF_SIZE, fp);

	if(read_cnt<BUF_SIZE)
	{
		write(clnt_sd, buf, read_cnt);
		break;
	}

	write(clnt_sd, buf, BUF_SIZE);
}

shutdown(clnt_sd, SHUT_WR);
read(clnt_sd, buf, BUF_SIZE);//입력 스트림을 통한 데이터 수신(사용 가능)
printf("Msg from client: %s\n", buf);

fclose(fp);
close(clnt_sd);
close(serv_sd);

파일 전송 후에 출력 스트림(SHUT_WR)에 대한 Half-close를 진행하고 있다. 이로써 클라이언트에게는 EOF가 전송되고, 이를 통해 클라이언트는 파일 전송이 완료되었음을 인식할 수 있다.

출력 스트림만 닫았기 때문에, read함수를 이용해 입력 스트림을 통한 데이터의 수신은 여전히 가능하다.

 

Client.c

...
//EOF가 전송될 때까지 데이터를 수신
while((read_cnt=read(sd, buf, BUF_SIZE))!=0)
	fwrite((void*)buf, 1, read_cnt, fp);

puts("Received file data\n");

//서버로 msg를 전송. 서버의 입력 스트림이 닫히지 않았다면, 이 msg를 수신할 수 있다.
write(sd, "Thank you", 10);

fclose(fp);
close(sd);