http://www.cse.iitb.ac.in/perfnet/cs456/tcp-state-diag.pdf
Posted by 패스맨

댓글을 달아 주세요


출처 http://www.gpgstudy.com/forum/viewtopic.php?t=16635&highlight=connectex

IOCP서버에서 서버간 connect를 하기 위해 Overlap방식으로 Connect 부분을 처리해주는
connectex함수를 사용해봤습니다. 원래는 소켓 재사용을 사용할 생각은 없어서
소켓을 생성해서 접속하고 끊을때는 소켓을 해제해주는 방식으로 했었는데 그부분은 잘됬습니다.
그런데 소켓 재사용을 위해서 Acceptex()함수를 쓸때 사용하던 Transmitfile함수를 옵션값을 그대로
사용해서 connectex의 소켓에 적용하고 다시 접속을 하려하면 에러가 납니다.

우선 소스를 보면서 말씀드리겠습니다.
코드:
// Connect함수는 이렇게 구성되어 있습니다.
// ( 복잡해 보일듯해서 로그 남기는 함수,GetLastError,기타 관련없는 코드 등은 삭제했습니다. )
BOOL CSession::Connect( const CHAR * szIp, const USHORT nPort,HANDLE & hIOCP )
{
INT nReturn;
BOOL bReturn;
DWORD dwBytes = 0;
LPFN_CONNECTEX lpfnConnectEx = NULL;
GUID GuidConnectEx = WSAID_CONNECTEX;
// Overlap의 Connection을 위한 함수포인터인 lpfnConnectEx을 얻어온다.
nReturn = ::WSAIoctl(
m_socketClient,
SIO_GET_EXTENSION_FUNCTION_POINTER,
&GuidConnectEx,
sizeof(GuidConnectEx),
&lpfnConnectEx,
sizeof(lpfnConnectEx),
&dwBytes,
NULL,
NULL
);
if( SOCKET_ERROR == nReturn )
{
return FALSE;
}
m_pOverlappedEx->m_emOperation = IO_CONNECT;
m_pOverlappedEx->m_pSession = this;
// Note : 소켓주소구조체를 구성해서 bind작업을 한다.( connect 작업이 되지 않음 )
// bind시에는 Local주소중 하나를 사용해주고
// Protocol family만 지정해주면 된다.
SOCKADDR_IN localAddr;
::ZeroMemory( &localAddr,sizeof( SOCKADDR_IN ) );
localAddr.sin_family = AF_INET;
localAddr.sin_addr.s_addr = inet_addr( GET_MY_IP() );
nReturn = ::bind( m_socketClient,( struct sockaddr * )&localAddr,sizeof( localAddr ) );
if( SOCKET_ERROR == nReturn )
{
return FALSE;
}
// 입력받은 서버의 ip와 port로 소켓주소 구조체를 구성한다.
SOCKADDR_IN si_addr;
::ZeroMemory( &si_addr,sizeof( SOCKADDR_IN ) );
si_addr.sin_family = AF_INET;
si_addr.sin_port = htons( nPort );
si_addr.sin_addr.s_addr = inet_addr( szIp );
// 입력받은 IOCP핸들과 socket핸들을 Associate한다.
if( !AssociateWithIOCP( hIOCP ) )
{
return FALSE;
}
// Note : Connect에 관한 Overlap작업을 요청한다.
bReturn = lpfnConnectEx(
m_socketClient,
(struct sockaddr * )&si_addr,
sizeof( struct sockaddr ),
NULL,
0,
NULL,
(LPOVERLAPPED)&m_pOverlappedEx );
if( FALSE == bReturn && ( WSAGetLastError() != WSA_IO_PENDING ) )
{
return FALSE;
}
return TRUE;
}


오류는 중간에 있는 bind함수에서 10022가 나옵니다.
물론 처음 connect사용시에는 잘되고요. 끊고 다시 접속할 때 이 소켓값(m_socketClient)을 그대로
사용하기 위해 TransmitFile함수를 써서 소켓 재사용 적용 후에 나온다는 겁니다.
그리고 TransmitFile함수를 break을 걸고 정상 리턴될때 보니 소켓값은 확실히 살아 있었습니다.
그리고 이 함수 사용후 접속도 끊기고요. 재사용이 안됩니다....

그래서 소켓 Reuse를 하는 시점에서 connectex를 호출할때 bind부분을 건너뛰도록 했더니...
AssociateWithIOCP()함수에서 FALSE가 리턴됬습니다. 아! 이 함수는 CreateCompletionPort함수를
이용해서 소켓값( m_socketClient )과 함수에서 받은 IOCP핸들값을 묶어주는 함수인데 NULL값이
리턴되서 FALSE가 나는 상황입니다.

이외에 bind()함수 전에 소켓 setsockopt로 SOCKREUSE옵션인가? 그것도 줘봤는데 실패...
MSDN에 나온것 처럼 전에 쓰던 소켓 옵션을 그대로 적용받기 위해서 lpfnConnectEx리턴된 후
SO_UPDATE_CONNECT_CONTEXT 옵션을 줘봤는데 역시 실패...입니다.

lpfnConnectEx함수를 호출하기 전에 계속 에러가 나는걸 봐서 뭔가 들어가야할 과정이
더 있을것도 같습니다만...구글링도 대답을 안해줍니다;;;

그리고 접속을 끊는 함수부분입니다..
코드:
//.... 생략....
m_pOverlappedEx->m_emOperation = IO_CLOSE;
m_pOverlappedEx->m_pSession = this;
::shutdown( m_socketClient,SD_BOTH );
// Note : TF_DISCONNECT 와 TF_REUSE_SOCKET 옵션값으로 소켓종료후 재사용이 가능토록한다.
bReturn = ::TransmitFile(
m_socketClient,
NULL,
0,
0,
(LPOVERLAPPED)&m_pOverlappedEx,
NULL,
TF_DISCONNECT | TF_REUSE_SOCKET
);
if( ( FALSE == bReturn ) && ( WSAGetLastError() != WSA_IO_PENDING ) )
{
return FALSE;
}


이 함수에서 혹시 에러가 걸리지 않을까 했는데 여기선 안걸리더군요. 정상적으로 실행됩니다...
( 참고로 이미 Acceptex함수와 맞춰 접속자의 소켓값을 재사용하면서 아무 문제없이 실행되던 코드입니다. )

혹시 이 부분을 구현해보셨거나 어디가 이상한지 보이시면 조언 좀 부탁드립니다.
아니면 어디 자료를 참고하면 된다는 거라도 좀 알려주십시요 ㅜ.ㅜ
저도 알게되면 여기 댓글로 남기겠습니다.

그럼..답변을 기다리며...마지막 제헌절 휴일 잘보내세요.....
위로
smurpe



가입: 2006년 2월 7일
올린 글: 1

올리기 올려짐: 2007-08-24 17:45
인용과 함께 답변 이 게시물을 del.icio.us에 추가

ConnectEx를 호출하기전에 bind를 하는데 이때 bind함수는 최초 한번만 호출이 되어야 합니다. 소켓 핸들을 사용후 반납후 재사용시 이미 bind가 되어 있던 소켓 핸들이라면 10022에러가 납니다.

Posted by 패스맨

댓글을 달아 주세요


출처 : http://kukuta.tistory.com/27

 오늘은 소켓의 종료와 그에 따라 발생하는 소켓의 상태 변화에 대해 알아 보도록 하겠다.

 먼저 소켓은 생성될 때는 3-way hand shaking을 통해서 생성되지만, 종료 될때는 한단계 더 많은 4-way hand shaking을 거쳐 종료 된다.

four way handshake

four way handshake


  위에서 분명히 4-way.. 4단계라고 이야기 했다. 그럼 어떤 4단계를 거치는지 알아 보자.

  1. A가 B에게 연결 종료를 요청한다.
  2. B는 바로 종료를 하는 것이 아니라, 단순히 ACK만을 날리고 있다. B도 종료 하기 전에 할
     일이 있기 때문에 바로 FIN을 날리지 않고, 단순히 ACK를 날리고 CLOSE_WAIT 상태로 넘어
     간다.
 3. 볼일을 다 보고난 B는 이제서야 FIN을 날리고 연결을 종료 하고자 한다.
 4. A는 B의 FIN을 잘 받았다는ACK를 B에게 보내게 되고, A의 ACK를 받으면 B는 종료한다.

 4의 과정에서 A는 ACK를 날리고 난후 소켓이 제거될때 까지 TIME_WAIT상태에 있게 되며 이 시간은 대략 30 초 정도지만 시스템 마다 다르다.
 TIME_WAIT에 있는 동안에는 커널이 주소와 port를 바인딩 하고 있기때문에 재사용 할 수가 없다. 게다가 TIME_WAIT에 있는 주소와 port를 재사용 요청을 하면 TIME_WAIT 시간을 더욱 늘릴 뿐이다.
 
 그렇다면 이렇게 불편한 TIME_WAIT를 왜 사용해야만 하는가??
 만일 A가 B로 보낸 마지막 종료 메시지(SEQ:5001,ACK:6002) 이후 바로 종료 했다고 가정하자. 그런데 라우터의 문제라던지 기타등등의 네트워크 상에서 발생하는 어떠한 원인에 의해 마지막 종료 메시지가 B에 도착하지 않게되었다.
 이때쯤 되면 B는 종료하지 못하고(TCP는 소심해서 뭘 하던지 완료 메시지를 받아야 마음을 놓는다) 다시 A에게 한번 더 FIN(SEQ:6001, ACK:5001)을 날린다.
 여기서 A가 TIME_WAIT가 아니라 바로 종료 되어버린 상태 였다면, 이 마지막 FIN 역시 무시했을 테고 B는 여전히 A가 종료 되었는지 살아 있는지 모르고 아둥바둥거리고 있게 될 것이다. 하지만 TIME_WAIT에 있는 A의 소켓은 여전히 주소와 포트를 바인딩 하고 있는 상태이기 때문에 다시 날아 오는 B의 FIN을 감지하고 다시한번 마지막 ACK를 날려 줄 수 있는 것이다.

참 고 :
 열혈강의 TCP/IP 소켓 프로그래밍 : 윤성우
 Unix Network Programming : Stevens

관련 글 :  
 TIME_WAIT state vs SO_REUSEADDR option -> http://kukuta.tistory.com/17

Posted by 패스맨

댓글을 달아 주세요

  1. kukuta 2011.05.28 13:31 신고 Address Modify/Delete Reply

    참고로 http://kukuta.tistory.com/155 같이 보시면 더 도움이...
    암튼 들러 주셔서 감사합니다.