프로그래밍/C++

소켓을 올바르게 닫기

보니아빠 2010. 3. 31. 18:48
펌글: 원본 링크 http://blog.naver.com/poins/50000828154

1.요약 

socket을 닫는 옳바른 방법에 대해서 알아본다. 


2.본문 

socket을 닫는 일반적이고 안전한 방법은 다음과 같다. 

shutdown(m_socket, SD_BOTH); 

closesocket( m_socket ); 

각각의 의미는 다음과 같다. 

shutdown : 연결된 상대방 소켓에 연결이 종료됨을 알린다. 
closesocket : 소켓 핸들을 닫는다. 

위에서 설명한것 같이 closesocket은 열려진 소켓의 핸들을 닫는 역활만을 하는것이 공식적이다. 그러나 실제 동작을 살펴보면 shutdown을 한것같은 효과를 볼 수 있다. 이것은 socket의 timeout처리 때문일것으로 생각된다. 

다음은 이런한 과정을 거칠때 내부적인 수행과정이다. 

Client side 
(1) Invokes shutdown(s, SD_SEND) to signal end of session and that client has no more data to send. 

Server side 
(2) Receives FD_CLOSE, indicating graceful shutdown in progress and that all data has been received. 
(3) Sends any remaining response data. 
(4) Invokes shutdown(s, SD_SEND) to indicate server has no more data to send. 
(4') Invokes closesocket. 


Client side 
(5') Gets FD_READ and calls recv to get any response data sent by server. 
(5) Receives FD_CLOSE indication. 
(6) Invokes closesocket. 

위의 과정을 살펴보면 우리는 shutdown과 closesocket사이에 recv가 있어야함을 알 수 있다. 하지만 보통은 이과정을 생략하고 있다. 

다음은 뉴스 그룹에서 socket종료에 관련된 내용을 번역한것이다. 

jclin란 아이디를 가진 사람이 자기는 정상적으로 소켓을 종료했는데 netstat로 종료과정을 관찰하니 소켓이 'TIME_WAIT'상태로 남아있다가 얼마후에 종료되는것을 발견했다. 그래서 이런한 timeout을 해소할 수 있는 방법을 물어왔다. 

------------------------------------------------------------------- 
jclin 
저는 에코서버와 에코 클라이언트를 만들었습니다. 
... 
<중략> 
... 
서버와 클라이언트는 다음과 같은 과정으로 종료했습니다. 

1:   linger li = {0, 0}; 

2:   setsockopt(m_socket, SOL_SOCKET, SO_LINGER, (char*)&li, sizeof(li)); 

3:   shutdown(m_socket, SD_BOTH); 

4:   closesocket( m_socket ); 

서버나 클라이언트를 종료하고 netstat로 종료시에 소켓에 어떠한 일이 일어나는지 보았습니다. 저는 많은 소켓들이 'TIME_WAIT'으로 표시되어있는것을 보았습니다. 그리고 그것들은 잠시후에 사라졌습니다. 

1,2,3,4의 과정들을 생략하거나 조정해 보았지만 마찬가지였습니다. 

제 질문은 waiting timeout없이 종료할수 없냐는것입니다. 

이유는 많은 연결을 해야하는 서버에서 일정시간동안 소켓을 wait하고 있는것은 시스템 리소스를 낭비하는것이 되기 때문입니다. 

------------------------------------------------------------------- 

Ed Astle 

TIME_WAIT는 TCP/IP의 내용이기 보다는 socket에 관련된 내용입니다. 이것은 2MSL(maximum segment lifetime) wait state이라고 불리기도합니다. 

tcp stack이 이런한 상태일때 이 연결에 관련된 어떠한 TCP segment도 거부됩니다. 

... 
<중략> 
... 
다음과 같이 추가하기를 권합니다. 

code = ::shutdown( m_hSocket, SD_BOTH );     // Send a FIN here 



// Wait for socket to fail (ie closed by other end) 



if( code != SOCKET_ERROR ) 

{   fd_set readfds; 

    fd_set errorfds; 

    timeval timeout; 



    FD_ZERO( &readfds ); 

    FD_ZERO( &errorfds ); 

    FD_SET( m_hSocket, &readfds ); 

    FD_SET( m_hSocket, &errorfds ); 



    timeout.tv_sec  = MAX_LINGER_SECONDS; 

    timeout.tv_usec = 0; 

    ::select( 1, &readfds, NULL, &errorfds, &timeout ); 

} 



code = ::closesocket( m_hSocket ); 

m_hSocket = INVALID_SOCKET; 

이것이 ACK과 FIN을 기다는 방법입니다. 이렇게 하지 않았을때는 TIME_WAIT을 감수해야합니다. 

저는 이러한 방법으로 정상적으로 종료하는 서버들을 만들었습니다. 이렇게 하지않았을때는 ACK신호를 받지 못해 mainframe에서 사용자가 logout했다는 로그가 남지 않게되었습니다. 

------------------------------------------------------------------- 

결론을 내자면 마지막에 신호를 recv를 하는 코드를 꼭 써야 한다는것이된다. 많은 예제 코드들이 이러한 방법을 쓰지만 많은 프로그래머들이 이러한 방법을 무시한다. 종료시 recv종류를 쓴 이유가 서버측의 남을 데이터를 모두 받아들이기위한 방법이라고 예상했기 때문이다. 또한 winsock의 경우는 많이 BSD소켓에서 많은 부분이 변형 되었기 때문에 내부적으로 이런 처리들이 이루어지고 있을지도 모르겠다. 하지만 정상적인 방법을 이것이라는것을 설명하고자 했다.