#include "BaseSocket.h" #include #include #include #include #include #include #include #include #include #include #define SOCKET_NOT_VALID -1 #define MAX_SEND_RETRY_COUNT 10 // Socket send 실패시 최대 재전송 시도 횟수. /// @brief 생성자. /// @param socket [in] 처리할 socket descriptor CBaseSocket::CBaseSocket( const int& socket ) : m_sock( socket ) { if( m_sock < 0 ) { m_sock = SOCKET_NOT_VALID; m_bConnected = false; } else { m_bConnected = true; } } /// @brief 소멸자 CBaseSocket::~CBaseSocket() { // 소멸자 Socket 명시적 Close 처리. Close(); } /// @brief 소켓의 Close 처리를 수행함. void CBaseSocket::Close() { m_bConnected = false; if( m_sock != SOCKET_NOT_VALID ) { close( m_sock ); m_sock = SOCKET_NOT_VALID; } } /// @brief 전달받은 Target 으로 Socket 접속을 수행 /// @param szTarget [in] 접속 대상 Host name 또는 IP /// @param nPort [in] 접속 Port /// @return 접속 성공시 true, 실패시 false 반환. ( 오류 내역은 로깅처리됨.) bool CBaseSocket::Connect( const std::string& szTarget, int nPort ) { // 기존 접속을 Close 처리 Close(); unsigned int nHost = ConversionAddr( szTarget.c_str() ); if( nHost == INADDR_NONE ) { LOG( LERR, "target host name resolve fail. [%s]->INADDR_NONE", szTarget.c_str()); return false; } struct sockaddr_in stTargetAddr; m_sock = socket(AF_INET, SOCK_STREAM, 0); if( m_sock == -1 ) { int errorNum = errno; LOG( LERR, "socket create failed.[%d][%s] Target:[%s]", errorNum, strerror(errorNum), szTarget.c_str() ); m_sock = SOCKET_NOT_VALID; return false; } stTargetAddr.sin_family = AF_INET; stTargetAddr.sin_addr.s_addr = nHost; stTargetAddr.sin_port = htons( nPort ); // NEW 2012-05-18 huibong // Connnection 종료시 많은 TIME_WAIT 상태 발생으로 인해 .. 이를 제거하기 위해 SO_LINGER 옵션을 설정처리한다. struct linger opt_linger; opt_linger.l_onoff = 1; /* LINGER ON */ // CHG 2014-05-27 huibong // 아래와 같이 No Wait 로 처리할 경우... Server 역활 수행 중 Close 신호를 받지 못하는 경우가 발생 가능함. // 따라서 5 sec 정도 대기하도록 수정 처리한다. //opt_linger.l_linger = 0; /* No Wait => 0 for abortive disconnect */ opt_linger.l_linger = 5; // 5 sec 대기 int result = setsockopt( m_sock, SOL_SOCKET, SO_LINGER, &opt_linger, sizeof(opt_linger)); if( result != 0 ) { int errorNum = errno; LOG( LERR, "setsockopt func SO_LINGER set fail.[%d][%s]", errorNum, strerror(errorNum)); } /* Send Timeout 설정. */ struct timeval tv_timeo = { 5, 0 }; result = setsockopt( m_sock, SOL_SOCKET, SO_SNDTIMEO, &tv_timeo, sizeof(tv_timeo)); if( result != 0 ) { int errorNum = errno; LOG( LERR, "setsockopt func SO_SNDTIMEO set fail.[%d][%s]", errorNum, strerror(errorNum)); } if(connect(m_sock, (struct sockaddr *)&stTargetAddr, sizeof(stTargetAddr)) < 0 ) { int errorNum = errno; LOG( LERR, "connect fail.[%s][%d] [%d][%s]", szTarget.c_str(), nPort, errorNum, strerror( errorNum ) ); Close(); return false; } else { m_bConnected = true; return true; } } /// @brief 멤버 변수인 m_sock 이 유효하고 연결된 상태인 경우 true 반환. /// @return socket이 유효하지 않거나 연결이 끊어지 경우 false 반환, 그외에는 true 반환. bool CBaseSocket::IsValidSocket() { if( m_sock != SOCKET_NOT_VALID && m_bConnected == true ) return true; else return false; } /// @brief m_sock 으로부터 지정된 size 만큼 데이터 read 를 시도 ( read 함수와 동일 ) /// @param vptr [out] Read 된 데이터를 저장하기 위한 변수에 대한 포인터. /// @param size [in] read 하고자 하는 데이터의 크기. /// @return read 된 데이터의 크기. 0: fd closed, -1: 오류 ssize_t CBaseSocket::Read( void * vptr, size_t size ) { if( IsValidSocket() == false ) return 0; ssize_t nRead = 0; while( (nRead = read( m_sock, vptr, size )) < 0 ) { int errorNum = errno; if( errorNum == EINTR || errorNum == EAGAIN || errorNum == EWOULDBLOCK ) { nRead = 0; continue; } else if( errorNum == ECONNRESET || errorNum == EPIPE ) { // 2010-07-23 BUG huibong 잘못된 대입연산자를 비교연산자로 수정. // 2015-08-28 CHG huibong EPIPE(32) 오류 발생시.. 연결 종료로 처리되도록 수정 m_bConnected = false; return 0; } else { LOG( LERR, "read func fail.[%d][%s]", errorNum, strerror(errorNum) ); return -1; } } return nRead; } /// @brief m_sock 로부터 지정된 size 만큼 read 를 시도, \n ///< 만약 지정된 크기만큼 데이터가 존재하지 않을 경우 해당 크기만큼 데이터를 read 할때까지 대기. /// @param vptr [out] Read 된 데이터를 저장하기 위한 변수에 대한 포인터. /// @param size [in] read 하고자 하는 데이터의 크기. /// @return read 된 데이터의 크기. 0: fd closed, -1: 오류 ssize_t CBaseSocket::ReadN( void * vptr, size_t size ) { if( IsValidSocket() == false ) return 0; ssize_t nRead = 0; size_t nLeft = size; char * ptr = (char *)vptr; while( nLeft > 0 ) { if( (nRead = read( m_sock, ptr, nLeft )) < 0 ) { int errorNum = errno; if( errorNum == EINTR || errorNum == EAGAIN || errorNum == EWOULDBLOCK ) { nRead = 0; continue; } else if( errorNum == ECONNRESET || errorNum == EPIPE ) { // 2015-08-28 CHG huibong EPIPE(32) 오류 발생시.. 연결 종료로 처리되도록 수정 m_bConnected = false; return 0; } else { LOG( LERR, "read func fail.[%d][%s]", errorNum, strerror(errorNum) ); return -1; } } else if( nRead == 0 ) { m_bConnected = false; return 0; } nLeft -= nRead; ptr += nRead; } return size; } /// @brief m_sock 로부터 지정된 size 만큼 read 를 시도, \n ///< 만약 지정된 크기만큼 데이터가 존재하지 않을 경우 해당 크기만큼 데이터를 read 할때까지 대기. \n ///< 또는 지정된 Timeout 값 동안 read 를 하지 못하는 경우 오류 처리. /// @param vptr [out] Read 된 데이터를 저장하기 위한 변수에 대한 포인터. /// @param size [in] read 하고자 하는 데이터의 크기. /// @param timeout [in] Timeout value (sec) /// @return read 된 데이터의 크기. 0: fd closed, -1: 오류 , -2: Timeout ssize_t CBaseSocket::ReadNTimeout( void * vptr, size_t size, int timeout ) { if( IsValidSocket() == false ) return 0; ssize_t nRead = 0; size_t nLeft = size; char * ptr = (char *)vptr; struct timeval timeOver; int result; fd_set selectFds; FD_ZERO( &selectFds ); while( nLeft > 0 ) { timeOver.tv_sec = timeout; timeOver.tv_usec = 0; FD_SET( m_sock, &selectFds ); result = select( m_sock+1, &selectFds, (fd_set *)NULL, (fd_set *)NULL, &timeOver ); if( result > 0 ) { if( FD_ISSET( m_sock, &selectFds )) { if( (nRead = read( m_sock, ptr, nLeft) ) < 0 ) { int errorNum = errno; if(errorNum == EINTR || errorNum == EAGAIN || errorNum == EWOULDBLOCK) { nRead = 0; continue; } else if( errorNum == ECONNRESET || errorNum == EPIPE ) { // 2015-08-28 CHG huibong EPIPE(32) 오류 발생시.. 연결 종료로 처리되도록 수정 m_bConnected = false; return 0; } else { LOG( LERR, "read func fail.[%d][%s]", errorNum, strerror(errorNum) ); return -1; } } else if( nRead == 0 ) { m_bConnected = false; return 0; } nLeft -= nRead; ptr += nRead; } } else if( result == 0 ) // Timeout { //LOG( LDBG, "read timeout"); return -2; } else { int errorNum = errno; LOG( LERR, "select func error.[%d][%s]", errorNum, strerror(errorNum) ); return -1; } } return size; } /// @brief m_sock 로부터 지정된 size 만큼 read 를 시도, \n ///< 만약 지정된 크기만큼 데이터가 존재하지 않을 경우 해당 크기만큼 데이터를 read 할때까지 대기. \n ///< 또는 지정된 Timeout 값 동안 read 를 하지 못하는 경우 오류 처리. ///< 본 함수는 Socket 상에 이미 Data 가 존재하는 경우에만 사용. /// @param vptr [out] Read 된 데이터를 저장하기 위한 변수에 대한 포인터. /// @param size [in] read 하고자 하는 데이터의 크기. /// @param timeout [in] Timeout value (sec) /// @return read 된 데이터의 크기. 0: fd closed, -1: 오류, -2: Timeout ssize_t CBaseSocket::ReadNTimeout2( void * vptr, size_t size, int timeout ) { if( IsValidSocket() == false ) return 0; ssize_t nRead = 0; size_t nLeft = size; char * ptr = (char *)vptr; int errorNum; // 우선은 읽기 시도. if( (nRead = read( m_sock, ptr, nLeft) ) < 0 ) { errorNum = errno; if( errorNum == EINTR || errorNum == EAGAIN || errorNum == EWOULDBLOCK ) { nRead = 0; } else if( errorNum == ECONNRESET || errorNum == EPIPE ) { // 2015-08-28 CHG huibong EPIPE(32) 오류 발생시.. 연결 종료로 처리되도록 수정 m_bConnected = false; return 0; } else { LOG( LERR, "read func fail.[%d][%s]", errorNum, strerror(errorNum) ); return -1; } } else if( nRead == 0 ) { m_bConnected = false; return 0; } nLeft -= nRead; ptr += nRead; // 만약 더 받을 데이터가 존재한다면. if( nLeft > 0 ) { struct timeval timeOver; int result; fd_set selectFds; FD_ZERO( &selectFds ); while( nLeft > 0 ) { timeOver.tv_sec = timeout; timeOver.tv_usec = 0; FD_SET( m_sock, &selectFds ); result = select( m_sock+1, &selectFds, (fd_set *)NULL, (fd_set *)NULL, &timeOver ); if( result > 0 ) { if( FD_ISSET( m_sock, &selectFds )) { if( (nRead = read( m_sock, ptr, nLeft) ) < 0 ) { errorNum = errno; if(errorNum == EINTR || errorNum == EAGAIN || errorNum == EWOULDBLOCK) { nRead = 0; continue; } else if( errorNum == ECONNRESET || errorNum == EPIPE ) { // 2015-08-28 CHG huibong EPIPE(32) 오류 발생시.. 연결 종료로 처리되도록 수정 m_bConnected = false; return 0; } else { LOG( LERR, "read func fail.[%d][%s]", errorNum, strerror(errorNum) ); return -1; } } else if( nRead == 0 ) { m_bConnected = false; return 0; } nLeft -= nRead; ptr += nRead; } } else if( result == 0 ) // Timeout { //LOG( LDBG, "read timeout"); return -2; } else { errorNum = errno; LOG( LERR, "select func error.[%d][%s]", errorNum, strerror(errorNum) ); return -1; } } } return size; } /// @brief m_sock 으로 지정된 크기만큼 vptr 의 데이터를 전송 시도.\n ///< Send Timeout 옵션 설정으로 Write Timeout 설정 가능. \n ///< 지정된 횟수만큼 재전송 실패시 오류로 처리함. /// @param vptr [in] 전달할 데이터를 저장한 변수에 대한 포인터. /// @param size [in] write 하고자 하는 데이터의 크기. /// @return write 된 데이터의 크기. 0: fd closed, -1: 오류 ssize_t CBaseSocket::WriteN( const void * vptr, size_t size ) { if( IsValidSocket() == false ) return 0; size_t nLeft; ssize_t nWrite; const char * ptr = (const char *)vptr; nLeft = size; int nTryCount = 0; while( nLeft > 0 ) { if( (nWrite = send( m_sock, ptr, nLeft, 0 )) < 0 ) { int errorNum = errno; if( errorNum == EINTR || errorNum == EAGAIN || errorNum == EWOULDBLOCK ) { nWrite = 0; ++nTryCount; } else if( errorNum == ECONNRESET || errorNum == EPIPE ) { // 2015-08-28 CHG huibong EPIPE(32) 오류 발생시.. 연결 종료로 처리되도록 수정 m_bConnected = false; return 0; } else { LOG( LERR, "send func fail.[%d][%s]", errorNum, strerror(errorNum)); return -1; } } else if( nWrite == 0 ) { m_bConnected = false; return 0; } if( nTryCount > MAX_SEND_RETRY_COUNT ) { LOG( LERR, "send func failure due to exceeding count of retry[%d/%d]", nTryCount, MAX_SEND_RETRY_COUNT ); return 0 ; } nLeft -= nWrite; ptr += nWrite; } return size; } // CHG 2011-05-03 huibong // gethostbyname() 함수가 Thread Safe 하지 않기 때문에 // DNS resolve 처리시 잘못된 정보를 반환할 가능성이 존재 // 이에 따라 본 함수를 수정처리함. /* unsigned int CBaseSocket::ConversionAddr( const char * name ) { struct hostent *he; int max; unsigned int retval; if ((retval = inet_addr(name)) != INADDR_NONE) return retval; he = gethostbyname(name); if (he == NULL) return INADDR_NONE; for (max = 0; he->h_addr_list[max]; max++) ; if (max == 1) return *((unsigned int *)(he->h_addr_list[0])); else return *((unsigned int *)(he->h_addr_list[random() % max])); } */ unsigned int CBaseSocket::ConversionAddr( const char * name ) { unsigned int retval; struct addrinfo hints; struct addrinfo *result = NULL; int error; // 전달받은 정보가 IP 주소인 경우... // - DNS resolve 할 필요 없이 변환시킨 값을 그대로 사용한다. if( (retval = inet_addr(name)) != INADDR_NONE ) return retval; // getaddrinfo() 함수 호출을 위한 Hint 설정 memset( &hints, 0x00, sizeof(hints)); hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; // Thread Safe 한 DNS Resolve 처리함수 호출 // int getaddrinfo(const char *hostname, const char *servname, const struct addrinfo *hints, struct addrinfo **res); // CHG 2014-11-14 huibong // getaddrinfo 호출시 종종 EAI_NONAME 오류가 반환됨. (#20783) // - 따라서 오류 반환시 재시도하도록 기능 추가 // CHG 2015-07-15 huibong // usleep 의 multi thread 상에서 block 발생 가능 // - nanosleep 을 사용토록 변경 처리 for( int count = 0 ; count < 3; count++ ) { error = getaddrinfo( name, NULL, &hints, &result ); // 오류 발생시 if( error != 0 ) { if( result != NULL) { freeaddrinfo(result); result = NULL; } // 잠시 대기 후 재시도 처리 struct timespec sleep; sleep.tv_sec = 0; sleep.tv_nsec = 500000000; // 0.5 sec nanosleep( &sleep, NULL ); } else { // 정상 처리된 경우... // - loop 탈출 break; } } // 최종 오류 발생시 if( error != 0 ) { LOG( LERR, "name[%s] dns resolve fail. getaddrinfo return error [%d][%s]", name, error, gai_strerror(error) ); return INADDR_NONE; } struct sockaddr_in * addr = (struct sockaddr_in *)result->ai_addr; retval = (unsigned int)(addr->sin_addr.s_addr); // DNS Resovle 결과 확인용 코드 /* struct addrinfo *temp; for( temp = result; temp; temp = temp->ai_next ) { addr = (struct sockaddr_in *)temp->ai_addr; printf ("getaddrinfo result = %s\n",inet_ntoa( addr->sin_addr)); } */ // getaddrinfo() 함수에서 생성한 메모리 영역 해제 처리. freeaddrinfo(result); return retval; }