본문 바로가기

3.구현/보안

Socket에서 비동기 connect 처리하기

소켓 프로그래밍에서 비동기 방식으로 처리를 많이 사용하고 있다. 장점도 있고 단점도 있다. 그러나 최근 고성능 서버 프로그램 작성할 때에는 거의 대부분이 비동기 방식으로 처리한다.
이런 부분의 장점과 단점은 인터넷에 잘 나와있으니 알아서 찾아보시고, 여기에서는 서버 보다는 클라이언트에 집중해보려고 한다. 즉 접속를 하는 시스템을 집중하겠다.

사실 서버에서도 접속을 요청할 수 있다. 이는 push방식 인가 pull 방식 인가에 따라서 서버에서도 사용할 가능성 있다. 이런 push와 pull도 인터넷에서 검색하시길 바란다.

이제 본론으로 들어가보자.

작성일: 2009.09.25 (http://ospace.tistory.com/), ospace114@엠팔.컴

Connect 함수

기본적으로 소켓 생성이 끝나고 연결만 남은 상태라고 보자. 그리고, 대부분 소켓 프로그래밍에 대해 기초적인 부분을 알고 있다고 생각해서 진행하겠다.

int connect(int, const struct sockaddr*, socklen_t); // for linux
int connect(SOCKET, const struct sockaddr*, int); // for windows

많이 보던 함수라서 익숙 할 것이다. 각 인자마다 들어가는 타입이 틀리지는 모르지만, 결국 같은 값을 사용한다. 일반적인 동기 접속이 이뤄지는 경우 반환 값은 다름과 같다.

동기접속 Return Value

  • 성공: 0 반환
  • 실패: -1 (Linux), SOCKET_ERROR(Windows)

비동기 인경우는 조금 다르게 처리한다. 리턴 값이 바로 접속이 성공하면 0 이지만, -1 값이 실패로 처리되지 않는다. -1은 비동기 처리에서는 기본적이며, Linux는 ierrno값을 이용하여 현재 처리되는 상태를 확인하고 Windows는 WSAGetLastError()를 사용해서 에러를 확인한다.

비동기접속 Return Value

  • 성공: 0 반환
  • 진행중: -1 반환. errno값을 비교하여 진행 상태 확인
errno == EINPROGRESS(linux), EAGAIN  
WsaGetLastError() == WSAEWOULDBLOCK

한 가지 주의할 것은 linux에서는 처리 중인 상태를 errno값에서 EINPROGRESS를 사용한다. 간혹 EAGIN를 사용하는 프로그램이 있다. 이부분을 좀더 검증이 필요하다.

연결 상태 확인

connect를 호출해서 연결을 한다고 해도, errno 값이 EINPROGRESS라고 해서 연결이 성공적으로 완료되었다고는 말하기 힘들다. 비동기이기 때문에 그 결과를 반환 값에 넣을 수 없다. 그 때 필요한 것이 getsockopt()를 이용한 것이다.

int getsockopt(int, int, int, void*, socklen_t*); // for linux
int getsockopt(SOCKET, int, int char*, int*);   // for windows

그럼 비동인 경우 연결 결과를 가져오는 코드는 다음과 같다.

int error = 0;
socklen_t len = sizeof( error );
if( getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0 ) {
    // 값을 가져오는데 에러 발생
    // errno을 가지고 에러 값을 출력
    // 연결 오류로 처리
}

getsockopt()인 경우는 BSD와 Solaris에서 결과가 매우 다르다. Solaris는 확인하기 어렵고 일단 Linux와 Widows을 중심으로 살펴보았다.

Linux Return Value

  • 성공: 0
  • 실패: -1, 에러 종류는 errno에 저장됨

Windows Return Value

  • 성공: 0
  • 실패: SOCKET_ERROR, 에러 종류는 WSAGetLastError()로 가져옴

둘다 SO_ERROR 값 획득에 성공하면, error를 통해 연결 상태에 대한 결과를 확인해 볼 수 있다.

Linux

  • ECONNREFUSED: 연결 거부
  • ETIMEDOUT: 연결 대기 시간 초과

Windows

  • WSAECONNREFUSED: : 연결 거부
  • WSATIMEDOUT: : 연결 대기 시간 초과

error 값을 가지고 위의 값과 비교해보면 알 수 있다.
당연히 error 값이 0이면 에러가 없으므로 성공적으로 접속했다는 의미이다.

최종코드

다음은 위의 결과를 정리한 코드이다. 물론 직접 테스트해본 코드가 아니기 때문에 컴파일시 에러가 발생할 수 있다. 그리고 기본 플랫폼은 Linux로 정했다.

int fd = socket(AF_INET, SOCK_STREAM, 0); // tcp socket
if( fcntl( fd, F_SETFL, O_NONBLOCK) == -1 ) {
    return -1; // error
}
// Windows인 경우
// unsigned long nonblock = 1; 
// nonblock 설정
// ioctlsocket(fd, FIONBIO, &nonblock);
// struct sockaddr_in 형의 peer를 초기화함
int result = connect (fd, (struct sockaddr*)&peer, sizeof(peer));
if( 0 == result ) {
    // 연결 성공
} else if ( EINPROGRESS == errno ) {
    // 비동기 연결 이벤트로 등록
} else {
    // 연결 실패
}

비동기 연결 이벤트에 의해서 해당 이벤트가 활성화되어 실행하는 경우, getsockopt()를 이용해서 결과를 확인한다.

// 연결 이벤트 헨들러
int error = 0;
socklen_t err_len = sizeof(error);
if( getsockopt(fd, SOL_SOCKET, SO_ERROR, (void*) &error, &len) < 0 ) {
    // 결과값 갖고 오는데 에러 발생
    // 연결중 에러 발생으로 errno 값으로 결과 확인
    // 에러 리턴
}
if( 0 != error ) {
    if( ECONNREFUSED == error ) {
        // 연결 거부로 연결 실패
        // 에러 처리
    } else if( ETIMEOUT == error ) {
        // 연결 대기 시간 초과로 연결 실패
        // 에러 처리
    }
    // 원인 모를 에러?
    // 알아서 처리 ㅡ.ㅡ;
}
// error가 0이기 때문에 연결 성공 ^^

이상입니다. 안에 코드는 두서없이 작성한 것이라서 나름대로 적당히 수정하면 되겠죠. error값은 switch문을 사용하는 식으로 말입니다.

결론

비동기 연결에 대해서 다룬 경우는 거의 없다. Stevnes 아저씨의 책을 많이 참고 했다. 이를 이용해서 다중 연결 요청을 만드는 프로그램을 작성할 수 있었다.

마지막으로 위에서 사용한 헤더 파일에 대해서 정리해보았다. 물론 다 알고 계신분은 상관없지만, 그 때마다 찾아쓰는 나와 같은 사람은 헤더파일 알아내느라 참 힘들다. ㅡ.ㅡ;

Windows

WinSock2.h: connect(), getsockopt(), WSAGetlastError(), WSAECONNREFUSED, WSAETIMEOUT

Linux

sys/socket.h: connect(), getsockopt()
errno.h: ECONNREFUSED, ETIMEOUT 정의

이상입니다. 모드 즐프하세요. ospace.

참조

[1] Stevens, Unix Network Programming Volumn1 2ed
[2] man, connect, getsockopt
[3] MSDN, connect, getsockopt

반응형

'3.구현 > 보안' 카테고리의 다른 글

c++를 이용한 byte order 변환  (0) 2011.08.11
ARP Protocol  (2) 2009.06.02