596 lines
15 KiB
C++
596 lines
15 KiB
C++
#include "BaseSocket.h"
|
|
|
|
|
|
#include <sys/select.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/types.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
#include <netdb.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <time.h>
|
|
|
|
|
|
#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;
|
|
|
|
}
|
|
|