본문 바로가기
네트워크, 서버/소켓,프로토콜

(소켓,프로토콜)TCP 소켓의 종료

by 흥부와놀자 2020. 10. 5.

1. 깔끔한 종료

TCP에서 연결된 소켓을 종료할때는 4-way handshake라는 과정을 거친다. 총 4단계로 tcp의 특성에 맞게 안전하게 종료 가능한 방법이다. 

 

- 과정

우아한 종료의 과정

먼저 종료를 원하는 쪽에서 closesocket과 같은 소켓 종료함수를 호출한다. 그럼 해당 소켓에 대한 권한이 tcp커널로 넘어가게 되고 만약 소켓이 블로킹 소켓이면 위의 종료과정이 완전히 끝날때까지 블로킹되고, 논블로킹 소켓이면 EWOULDBLOCK을 리턴한다.

 

그리고 해당 호스트는 FIN_WAIT상태에 들어가고 상대호스트의 FIN을 기다린다(FIN_WAIT). FIN 메시지를 받은 상대 호스트는 받은 메시지에 대한 ACK 신호를 보내고 역시나 closesocket과 같은 소켓종료함수를 보내기 전까지 ClOSE_WAIT상태가 된다.

 

FIN신호를 받은 쪽에서 다시 소켓종료를 호출하지 않고 기다린 모습

 

상대 호스트가 종료함수를 호출하여 FIN을 다시 보내면 받은 호스트는 받은 FIN에 대한 ACK를 보낸 후 일정 시간동안 TIME_WAIT에 들어가게 된다. TIME_WAIT은 자신이 보낸 ACK가 잘 도착했는지 확인 하기 위해 기다렸다 종료하는 유예 시간으로 리눅스의 경우 90초 정도라고 한다. 자신이 보낸 ACK가 손실됬다면 상대는 다시 FIN을 보내게 되고 그에 따른 ACK를 보낼 수 있다.

 

만약 TIME_WAIT에 걸린 측은 해당 포트로 다시 접속하기 위해선 TIME_WAIT이 끝날때까지 기다려야 한다. 

그렇기에 고정적인 포트에 바인딩하는 서버의 경우 에러가 나서 먼저 FIN을 보내고 TIME_WAIT에 걸렸을시 곧바로 서버를 재가동 시키지 못하는 불상사가 발생한다.

 

서버가 TIME_WAIT 걸림으로써 다시 접속시 connect error 발생

 

그리고 해당 TIME_WAIT이 끝나면 상대방에게 RST패킷을 보내고 비로소 tcp연결이 종료된다. 해당 종료 과정을 우아한 종료라는 표현을 사용하는곳이 많은데 graceful이라는 용어때문이라고 한다. 하지만 이러한 종료방법이 우아한 까지는 아니고 그냥 깔끔한 정도로 표현하는게 좋을듯 하다.

 

2. SO_LINGER 옵션

서로 입출력 stream이 연결된 tcp 통신의 종료에서 신경써야 할 부분 중 하나가 소켓의 출력버퍼 속 남은 내용을 보내고 종료할껀가 아님 그냥 종료할건지가 중요하다. 또한 위에서 설명한 깔끔한 종료과정을 계속 진행시킬지 아니면 TIME_WAIT같은 문제를 남기지 않기 위해 즉시 종료할지도 선택 해야 한다.

 

SO_LINGER는 이러한 옵션을 선택할 수 있도록 해준다.

linger구조체 안에는 onoff와 linger변수가 들어있고 setsocketopt(해당 소켓,SOL_SOCKET, SO_LINGER, (char*)&linger, sizeof(linger))함수를 통해 소켓의 linger옵션을 변경할 수 있다.

 

만약 onoff가 0이면 소켓은 깔끔한 종료를 수행한다. 물론 그과정에서 출력버퍼속 남아있는 내용은 전송된다.

onoff가 1이면 해당 linger(기다리는 시간초)동안 깔끔한 종료를 수행하다가 0이되면 출력 버퍼속 내용을 다 버리고 FIN대신 RST패킷을 전송한다. (물론 일부러 linger를 0으로 줘서 바로 RST 패킷을 보낼 수 있다.)

 

RST패킷을 받은 원격호스트는 recv호출 시 -1을 반환(error코드 10054)하고 연결을 끊는다. 물론 이 과정에서 TIME_WAIT은 없다. 

 

3. half-close

위에서 언급한 SO_LINGER는 closesocket시 적용되는 옵션이다. 소켓을 종료하는 방식에는 closesocket 말고 shutdown 방식도 있는데, closesocket이 입출력 버퍼를 닫고 해당 소켓의 메모리 해제까지 진행하는 거라면 shutdown은 닫을 소켓버퍼를 선택하고 해당 소켓의 메모리 해제까지는 하지 않는다.  

 

호출 방법은 shutdown(해당소켓, SD_SEND / SD_RECIVE / SD_BOTH)로 호출하면 되고 만약 SD_SEND로 출력 버퍼를 닫을 시 기존 출력버퍼에 남아있던 내용은 모두 전송된 뒤 닫힌다. 즉 shutdown함수의 요지는 입출력 버퍼 자체를 폐쇄 하는게 아니라 더이상 입출력 함수(send/recv)를 호출하지 못한다는것에 있다.

 

소켓 종료 후 상대방과 주고받을 정보가 있을때 사용하면 유용한 함수이다. 

자동으로 메모리 해제가 되지않기에 꼭 closesocket을 호출해야 한다.