반응없는 접속 다루기

http://www.mamiyami.com/document/cpp_network_prog_volume2/0201795256_ch03lev1sec3.html



원글은 여기서 가져오고 있지만 실제로 저 글은 C++ 네트워크 프로그래밍 2권에 해당하는 내용일 뿐이다.

ACE 적인 설명이 들어있지만 정리하자면 (일반적인 이야기로 풀어서 표기하자면)

왠만한 클라이언트가 끊어지는 경우는 서버쪽에서 모두 알 수가 있다. 소켓 자체에 무언가 읽을 수 있다고 표기가 되기 때문이다. 이때 recv 함수들을 이용해서 그 내용을 받아오면 recv 함수가 0 아니면 -1 을 리턴한다. 이것때문에 소켓의 접속이 끊어졌다는걸 알 수가 있는데.

-  랜선이 갑자기 뽑힌 경우나 , 아니면 잠시 뽑혔다가 다시 꼽힌 경우

- 클라이언트 컴퓨터가 갑자기 이상이 생긴 경우 , 그래서 클라이언트의 접속이 이상해서 계속 이어지지 않는 경우


이런 경우는 소켓에 생긴 이벤트를 알 수가 없다.

이럴때를 위해서 다루는 방법 몇가지 방법이 있는데

- TCP 가 킵얼라이브 시스템을 이용해서 (자체적인) 아무 응답이 없을때 소켓을 트리거가 종료하는 시스템이 있다. 닫히는 순간을 이용해서 recv 함수가 반응하는것을 이용하는 방법이지만 아쉽게도 매우 오랜 시간이 걸린다.

- 어플리케이션 레벨 정책으로 구현도 가능하다. 흔히 말하는 '살아 있어?' 라고 확인하는 메시지 이다. '살아 있어' 메시지를 일정 기간마다 전송하는데 실패하거나 응답이 없으면 접속이 끊어진것으로 판단한다.

- ACE 에서 지원하는 일정 기간 동안 소켓 통신이 없으면 접속을 끊어버리는 기능이다.


실제로 코딩상에서 어떻게 구현하는지 그 예를 살펴 보겠다.

ACE_Time_Value tv(60 * 3);
int n = _client_peer.recv(buf, sizeof (DWORD), &tv) ;

이 코드를 보면 첫 줄은 시간을 설정하는것이다. 60*3 이니 180 초 즉 3분이다. 3분동안 클라이언트에서 무엇인가 값이 오기를 기다리고 있는것이다.

recv 함수는 에러시에는  -1 이나 0 을 리턴한다.

0인 경우는 소켓이 Full 이 되서 더이상 읽을 것이 없을 때로 알고 있다. (확실하지는 않다. )

-1 인 경우가 다채로운 에러가 났을 경우다. 대표적인 경우가 클라이언트가 갑자기 연결을 끊었을때나 클라이언트 프로그램이 에러가 났던가 하는 경우이다.

여기서는 시간을 설정했기 때문에 3분동안 클라이언트로 부터 값이 오지 않으면 -1 과 errno 값이 ETIMEOUT 값이 들어 있지만, 일정에 쫓겨 TIMEOUT 은 설정하지 않고 단순히

-1 이면 한 에러로 처리하는 루틴이 들어가 있다.


if ( n == -1  )
{
    // time out & socket error
    if (!SendKeepAlive())
       return -1;

    // proper action
    return 0;
}
else if ( n == 0)
{
     ACE_ERROR_RETURN ((LM_ERROR, "(%P|%t) socket closed \n"), -1);
}
else if ( n != sizeof (DWORD))
{
     ACE_ERROR_RETURN ((LM_ERROR, "(%P|%t) received wrong packet \n"), -1);
}

// keep alive reset
NotRequireKeepAlive() ;

n 은 위에서 recv 함수에서 리턴되는 값이다. -1 인 경우가 접속이 종료됐거나 TIMEOUT 인 경우라고 했었다. 그밖의 다른 경우는 에러처리를 해주고 맨 아랫줄의 NotRequireKeepAlive 함수는 정상적으로 클라이언트가 Packet 을 보내서 정상적으로 Packet 의 길이를 받았을때 Keep Alive 상태를 리셋 해주는 함수이다. (단순히 Bool 변수 세팅)

BOOL Client_Handler::SendKeepAlive()
{
  // 현재 시간 얻어오기  
  ACE_Time_Value now = ACE_OS::gettimeofday ();

  // Keep Alive 메시지를 한번도 안 보냈다면
  // Keep Alive 메시지를 보내야 한다.
  if ( _bIsSendingKeepAlive == FALSE)
  {
     // sending Keep Alive
    _bIsSendingKeepAlive = TRUE ;
     // 현재 시간을 저장한다.
    _last_send_keepalive = now ;

     // Keep Alive Packet 을 클라이언트로 전송하는 부분이다.
     CmdKeepAlive packet ;
     MemArchive sendAR (MemArchive::encode);
     packet.Serialize(sendAR);

     // 상대편 Client 가 접속이 끊어져 있는 상태면
     // 전송이 실패할것이다.
     if (!_ptr_packetHandler->Write(_client_peer, sendAR))
      return FALSE ;

     // 단지 잠수(?) 하고 있을뿐이면 성공할것이다.
      return TRUE ;
}
else
{

     // 지금 시간과 마지막으로 Keep Alive 보낸 시간의 차이가
     // 지정해둔 Max 시간과 차이가 있다면
     // Client 는 비정상 접속 종료로 여겨도 무방하다.
     if ( now - _last_send_keepalive >= _max_client_timeout )
    {
       // keep alive failed

        return FALSE ;
    }
   }

  return TRUE;
}

위와 같은 형식으로 처리하는 루틴을 첨가하였다.

해결에 도움을 준 코에군에게 무한한 감사를 (코에군 갱장해!!)

written by 광이랑