프로그래밍/network

종료(closing)할 때, TCP-IP 상태의 이해

붐비붐비 2012. 8. 29. 11:59
TCP Closing STATUS

- TCP는 누가 먼저 연결했든지 다시말해서 서버든 클라이언트든 관계없이, 연결을 성공적으로 했다면 완전히 평등한 양방향(equivalent and full-duplex) 커넥션입니다. 연결은 근본적으로 대칭적이지 않은 이유로 3 hand-shaking로 이루어져 있고, 종료는 연결이 각자 평등하고 연결에 대한 입장이 서로 다르기 때문에 양방향으로 FIN, FIN_ACK를 2번씩 교환하면서 총 4번 패킷을 교환하면서 이루어집니다.



출처 : http://www.itmanage.info/technology/linux/statefull_firewall/Concept_of_State.htm


* CLOSE_WAIT 상태 : 원격에서 close()를 호출했으나, 응답하지 않음.

  이 경우는, 서버라기보다는 원격에서 close()를 호출함으로써, 로컬에서 FIN 패킷을 전달받은 상태입니다.
  일반적인 경우, FIN 패킷을 받으면 자동적으로 FIN_ACK를 전달하고 로컬에서는 스트림에 대한 EOF를 감지하게 되는데, 이를 바로 바로 처리해주지 않으면, CLOSE_WAIT 상태의 connection이 발생하게 되고 서버의 경우도 상응하는 FIN_WAIT1로 전이되어가 자원의 낭비가 양쪽에서 발생하게 됩니다. TCP state diagram를 보셔서 다들 아시겠지만 한 번 종료 프로세스를 밟은 connection은 다시 EST 상태로 전이되지 못합니다. 다시 말해 FIN을 전달받은 connection은 재사용할 수 없기 때문에 이는 즉각적으로 close()를 호출함으로써 빠르게 제거해야 합니다.


* FIN_WAIT2 상태 : 로컬에서 close()를 호출했으나, 원격에서 close()를 호출하지 않음.

  이 경우는, 로컬에서 close()를 보내고 FIN_ACK를 받고 FIN_WAIT1에서 FIN_WAIT2 상태로 바뀌어서 FIN을 기다리고 있는 상태입니다. 이 때 원격에서 close()를 호출함으로써 FIN 패킷을 전달하지 않으면 이상태가 지속되는데 이 역시 자원의 낭비요소입니다.


* TIME_WAIT : 원격에서 FIN_ACK 확인을 위한 정상적인 상태임.

  원격에서 FIN을 보냈는데, FIN_ACK를 받지 못한다면 다시 FIN을 보내서 종료를 재확인합니다. 이 때, 이를 기다려주지 않고 바로 종료를 하면 원격에서는 FIN_ACK를 받지 못해서 영영 LAST_ACK 상태가 됩니다. 이를 방지하기 위하여 FIN_ACK 확인을 위한 상태가 TIME_WAIT이며, 이는 커널 파라미터로 정의되어 있기도 합니다. (tcp_fin_timeout = 30)


* 그 밖에 SO_LINGER 옵션

  이 옵션은 close에 대한 행동을 조정하는 옵션입니다. (TIME_WAIT와 전혀 관계는 없습니다.) 열심히 데이터를 주고 받고 있는 경우, 다시 말해서 커널 tcp buffer에 데이터가 있는데, FIN을 받게 되면 보통은 버퍼에 있는 데이터를 다 처리하고 FIN_ACK를 전달하게 되는데, linger 값을 0으로 세팅하게 되면, 버퍼의 데이터를 버리고 소켓을 종료하게 됩니다.

  TCP는 신뢰 가능한 바이트 스트림 채널(reliable byte-stream channel)이기 때문에 데이터를 받지 않고 종료하게 되면 송신측에서 문제가 야기되고 재전송할 수 있습니다. 또한 stream이기 때문에, 먼저 처리되야할 데이터가 처리되지 않으면, 다른 데이터를 보낼 수가 없기도 합니다.

 이런 이유 때문에 강제 종료를 위해서 뭔가 특별한 것이 필요하게 됐는데요. 이 때 사용되는 TCP flag가 RST라는 리셋 플래그입니다. tcp 버퍼에 데이터가 있는데, setLinger(0)이 세팅된 상태에서 close()를 통해 종료 요청을 하게 되면, 버퍼를 전부 비우고 상대방에 RST flag 패킷이 전달되게 됩니다.

  이는 정상적인 종료 방법이 아니기 때문에 되도록 사용하지 않는 것이 좋습니다. 만약 close()를 명백히 호출했음에도 소켓이 빠르게 닫히지 않는다면, I/O를 담당하는 thread의 read() 함수가 더디게 동작하고 있지는 않은지 확인해볼 필요가 있습니다.