Graceful shutdown, linger options and socket closure

-- VC++ 2008. 3. 21. 17:08
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

Graceful shutdown, linger options and socket closure

이 문서는 소켓의 접속을 셧다운 하는 것과 소켓을 닫는(close)것이라는 주제에 대해서 그 의미를 명확히 하기위해 제공되는 문서입니다. 소켓의 접속을 셧다운(shutting down)하는 것과 소켓을 닫는(close)것의 사이를 구분짖는 것은 매우 중요합니다. 소켓의 접속을 셧다운 한다는 것의 의미는 연결된 두 단말사이의 큐에 버퍼링 되어 있는 메시지를 주고 받는다는 것을 의미합니다. 셧다운 작업은 일반적으로 우아한(graceful)것과 중지(abortive or hard)로 크게 두가지 부분으로 정의 되어 있습니다.

여기서 잠깐! 컴퓨터 관련 문서를 많이 접해 보신분들은 위에서 설명한 graceful 이나 hard 라는 단어에 대해서 익숙하실 겁니다. 하지만, 그렇지 못하신 분들은 이게 먼소리냐고 할지도 모릅니다. 일반적으로 가장 이상적으로 동작하게 되는 형태를 "graceful ...." 한 작업이라고 칭하게 됩니다. 굳이 한국말로 표현하자면, 우아한 ... 가 되겠죠... 반면에 이상적이 형태가 아닌 묵시적이거나 바람직하지 않은 방향으로 처리되는 작업을 "hard ..."라 칭하게 됩니다.

우아한 셧다운의 처리과정에서 큐에 버퍼링 되었지만, 아직 전송되지 않은 데이터는 접속이 종료되기 전에 보내지게 됩니다. 중지처리되는 셧다운의 처리과정에서 이러한 아직 보내지지 않은 데이터는 손실되게 됩니다. 하지만, 셧다운이 되었다고 해서 소켓에 할당된 리소스나 소켓 핸들이 반환 되었다는 것을 의미하지는 않습니다. 즉, 소켓을 닫는다(closing)는 것은 할당된 소켓핸들을 해제(반환) 한다는 것을 의미합니다. 소켓핸들이 해제되었으므로 어떻한 방법으로든 이 해제된 소켓을 사용할 수 없습니다.

윈도즈 소켓에서 shutdown함수와 WSASendDisconnect함수는 모두 셧다운 처리과정을 시작하는데 사용할 수 있습니다. 반면에 closesocket함수는 할당받은 소켓핸들을 해제해고, 할당받은 관련된 리소스들을 해제 하는데 사용됩니다. 그런데 일반적인 프로그래밍 할 때 보면, closesocket함수가 셧다운 처리과정을 발생시킬 것이라고 하여 사용됩니다. 이러한 점은 우리를 매우 헷갈리게 만들죠, 사실상 closesocket함수는 셧다운 처리과정을 시작하고, 소켓 핸들을 해제하는 작업 모두에 사용된다고 합니다.

SO_LINGER 와 SO_DONTLINGER 소켓옵션에 대한 적절한 값을 설정 했을경우, closesocket 함수로 아래와 같은 처리의 경우를 얻어낼 수 있습니다.

  • 중지(Abortive) 셧다운 처리과정은, closesocket함수로부터 바로 반환됩니다.
     
  • 우아한 셧다운 처리과정은, 셧다운 처리과정이 완료될 때 까지나, 지정한 시간이 다 될 때까지 딜레이 됩니다.
     
  • 우아한 셧다운으로 처리되고, 바로 반환되는 경우 — 백그라운드로 처리완료 되도록 하는 방식입니다. 비록 이러한 방법이 기본 처리 방법이라 하더라도, 어플리케이션은 언제 우아한 셧다운 처리가 완료되는지 알 수 있는 방법이 없습니다.

접속이 해제되는 동안 발생할 수 있는 문제점을 최소화 하려면, closesocket함수를 호출해서 암시적으로 셧다운 되는 작업을 하는 것을 피하는 것입니다. closesocket대신에 shutdown이나 WSASendDisconnect함수를 명시적으로 사용하는 방법이 있습니다.

아래의 표는 우아한 셧다운을 시작하기 위한 서버와 클라이언트의 함수사용의 순서를 보여줍니다.

Client SideServer Side
(1) 세션을 종료하기 위해서 shutdown(s, SD_SEND) 함수를 호출합니다. 그리고, 클라이언트는 더 이상 보낼데이터를 가지고 있지 않고 보내지도 않는 다고 가정합시다. 
 (2) FD_CLOSE 메시지를 수신받고, 우아한 셧다운은 진행상태에 들어갑니다. 그리고, 수신해야 할 데이터는 모두 수신받습니다.
 (3) 남아있는 응답 데이터를 전송합니다.
(5') FD_READ 메시지를 얻어내고, 서버로부터 보내진 데이터를 얻어내기 위해 recv 함수를 호출합니다.(4) 서버는 더 이상 보낼 데이터가 없도록 내부 큐를 비우기 위해 sutdown(s, SD_SEND) 함수를 호출합니다.
(5-2) FD_CLOSE 메시지를 수신합니다.(4') closesocket 함수를 호출합니다.
(6) closesocket 함수를 호출합니다. 


[추가] 2008-03-25
socket을 제대로 닫기

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도 거부됩니다.

(중략)

다음과 같이 추가하기를 권합니다.

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

// 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소켓에서 많은 부분이 변형 되었기 때문에 내부적으로 이런 처리들이 이루어지고 있을지도 모르겠다. 하지만 정상적인 방법을 이것이라는것을 설명하고자 했다.


from debuglab

출처 : http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=50&MAEULNO=20&no=714507&ref=714507&page=5

'-- VC++' 카테고리의 다른 글

About warning:LNK4089  (0) 2008.04.11
realloc과 new_realloc  (0) 2008.03.25
VC 6.0을 쓰지 말아야하는 이유?  (0) 2008.03.17
좋은 UI 라이브러리  (0) 2008.03.06
#pragma pack() / __packed 에 대한 설명  (0) 2008.03.05
posted by 어린왕자악꿍