#include "WorkerRecvServiceStat.h"

#include "Logger.h"
#include "ProcessRename.h"
#include "ProcessConfig.h"
#include "SocketFimngd.h"
#include "FixedQueue.h"
#include "ReportLog.h"

#include <stdlib.h>
#include <signal.h>
#include <pthread.h>
#include <errno.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <time.h>
#include <sqlite3.h>


// client ip 주소 저장을 위한 최대 크기값
// - NULL 포함 40자까지, IPv6 지원 목적
#define IP_ADDR_LENGTH      40

// fimngd 로 부터 전달받은 Data 를 저장 thread 에 전달시 사용할 Queue 의 최대 갯수
#define MAX_DATA_QUEUE_COUNT		3000

// 요청 수신 관련 최대 대기 시간. (sec)
// - fimngd 가 매 300 sec (5분) 마다 전송하므로... 최대 대기 시간은 
#define MAX_REQUEST_TIMEOUT      360

// sqlite SQLITE_BUSY 잦은 발생으로 INSERT 지연 발생, 이로 인해 CCDB 통계 누락 발생 (#21440)
// - 이를 해결하기 위해 lock timeout 값을 3 sec -> 0.6 sec 로 변경 처리 
// - 이와 더불어 Lock 발생시 재시도 횟수를 30 -> 60 으로 변경 처리
#define SQLITE_DB_LOCK_TIMEOUT       600    // 세션 중에 SQLITE_BUSY 상황 발생 문제 해결을 위해 DB Lock 관련 timeout 설정값. ( 0.6 sec )
#define SQLITE_DB_LOCK_MAX_RETRY      60    // SQLITE_BUSY 발생시 재시도 최대 횟수


// FHS 의 opendav-listener 로 부터 서비스 관련 통계 (Network, 세션등의 통계 등)정보를 수신하기 위한 UDP socket...
// Main.cpp 에 전역변수로 정의 되어 있다.
extern int g_sockDavListenerRecv;

// FHS fimngd 가 전송하는 서비스별 Network 통계 정보 수신을 위한 TCP Socket
// Main.cpp 에 전역변수로 정의 되어 있다.
extern int g_sockServiceStatRecv;


// Data 수신부와 이를 파싱하여 처리할 Thread 객체간에 Data 전달을 위한 Queue 객체 생성
CFixedQueue * g_pRecvNetworkStatQueue = NULL;


// 각 Client 를 처리할 Thread 에 전달할 구조체 
struct stRecvServiceStatThreadArg {

	// client socket descriptor
	int clientSocketDesc;

	// Client 의 IP 주소 정보를 저장한 변수	
	char szClientAddress[IP_ADDR_LENGTH];

};


// 프로세스 종료 전 처리할 종료 관련 각 작업을 일괄로 처리하기 위한 함수.
void ReadyToExitWorkerRecvServiceStat()
{
	// open 된 socket 관련 Close 처리
	if( g_sockServiceStatRecv != -1 )
		close( g_sockServiceStatRecv );

	// Queue 객체 해제
	if( g_pRecvNetworkStatQueue != NULL )
	{
		CFixedQueue * temp = g_pRecvNetworkStatQueue;

		g_pRecvNetworkStatQueue = NULL;
		delete temp;
	}


	// Logger 객체 종료 처리.
	CLogger::Exit();

	return;
}

// Worker RecvServiceStat 프로세스에 대한 대한 종료 처리 수신시.
static void SignalWorkerRecvServiceStatTerminate( int nSignalNumber )
{
	// Signal Number 에 따른 로깅처리.
	if( nSignalNumber == SIGTERM )
	{
		_LOG( LINF, "[RecvServiceStat:%d] Process exit by SIGTERM signal. Good Bye..", getpid() );
	}
	else
	{
		_LOG( LWAR, "[RecvServiceStat:%d] Process exit by abnormal signal[%d], Good Bye..", getpid(), nSignalNumber );
	}

	ReadyToExitWorkerRecvServiceStat();

	// 종료전 잠시 대기
	struct timespec sleep;
	sleep.tv_sec = 0;
	sleep.tv_nsec = 500000000;  // 0.5 sec
	nanosleep( &sleep, NULL );
	
	exit( EXIT_SUCCESS );
}

// Worker::RecvServiceStat Process 의 signal 처리기...
void SetWorkerRecvServiceStatSignalHandler()
{
	sigset_t set;
	struct sigaction act;

	memset( &act, 0x00, sizeof( act ) );
	sigfillset( &set );
	sigprocmask( SIG_SETMASK, &set, NULL );
	sigfillset( &act.sa_mask );

	// 무시 처리 signal
	act.sa_handler = SIG_IGN;
	sigaction( SIGPIPE, &act, NULL );	/* desciptor 오류 발생시 Process가 죽는 것은 방지하기 위하여 설정 */
	sigaction( SIGHUP, &act, NULL );	/* process를 기동시킨 관리자의 로그아웃시, 또는 config reload 등의 처리 signal*/
	sigaction( SIGINT, &act, NULL );	/* ^C 키를 누른 경우 받는 신호 => demon 으로 기동되기 땜시 이 신호 못받음 */
	sigaction( SIGQUIT, &act, NULL );	/* 키보드에 의한  Abort 신호 처리 => ? */

	// Worker 프로세스는 자식 프로세스가 존재하지 않으므로 그냥 무시처리함.
	act.sa_handler = SIG_IGN;
	sigaction( SIGCHLD, &act, NULL );

	/* 각종 에러나 사용자의 종료 신호 처리 */
	act.sa_handler = SignalWorkerRecvServiceStatTerminate;
	sigaction( SIGTERM, &act, NULL );	/* kill -TERM 에 의한 프로세스 종료시 */

	// 나머지 신호는 default 처리.

	sigemptyset( &set );                  /* 신호 처리기 처리 설정 위한 블록 해제 */
	sigprocmask( SIG_SETMASK, &set, NULL );
}



// 전역 Queue 에 저장된 network 통계 수신 data 를 파싱, 저장 처리를 담당하는 Thread
void * WorkerRecvServiceStatSaveThread( void * arg )
{
	// 메인 함수에서 이 스레드가 종료되기를 기다리지 않게 분리
	pthread_detach( pthread_self() );

	_LOG( LINF, "[RecvServiceStat:%d] SaveThread: start...", getpid() );


	// Queue pop data 저장 관련 변수 초기화.
	int pop_result;

	struct ServiceNetworkStat stNetworkStat;	
	

	// sqlite 관련 변수 초기화
	sqlite3      * db = NULL;
	sqlite3_stmt * stmt = NULL;

	int db_result;
	int nRetryCount;

	const char * insert_sql = "INSERT INTO network_stat( "
		"    timestamp, user_seq, svc_seq, fhs, insert_time, send_flag, down_traffic, up_traffic, "
		"    down_content_size, up_content_size ) "
		"VALUES ( ?, ?, ?, ?, datetime('now', 'localtime'), 0, ?, ?, ?, ? ) ";


	// 본 Thread 는 main 에서 통계  정보를 수신한 후 동작해야 하므로... 약간 기다린 후 동작시킨다.
	sleep( 3 );


	// sqlite 는 centos 5.4 에 설치된 기본 버전을 사용한다. ( 3.3.6 version )
	// 해당 버전에서는 최신 _v2 함수를 지원하지 않는다.
	// 기동시 db 파일이 없는 경우... -> open 명령에 의해 db 파일이 생성됨..(open 성공).. 하지만 prepare 에서 table 이 없다는 오류가 발생함.
	// 동작 중에 db 파일이 삭제되는 경우... -> Query 가 모두 정상적으로 수행됨... 따라서 감지가 안됨...

	// 본 sqlite3_open 함수에서는 오류가 발생하지 않는다.. 왜냐하면.. 해당 파일이 없을 경우.. 해당 파일을 0 size 파일로 강제로 생성하기 때문임.
	// 따라서 하위 오류 코드는 그냥 형식상 존재하는 코드임.

	db_result = sqlite3_open( CProcessConfig::GetInstance()->GetLocalDbFileName(), &db );
	if( db_result != SQLITE_OK )
	{
		LOG( LERR, "[RecvServiceStat:%d] SaveThread: sqlite3 open failed.[%s][%d][%s]", getpid(), CProcessConfig::GetInstance()->GetLocalDbFileName(), db_result, sqlite3_errmsg( db ) );
		sqlite3_close( db );
		db = NULL;
	}
	else
	{
		// DB File 이 open 된 경우...
		nRetryCount = 0;

		// bind 관련 Query 를 수행할 stmt 객체 생성.
		// - 만약 sqlite DB File 이 사전에 생성되어 있지 않은 경우...  위의  open 함수에서 0 파일을 생성되어 정상적으로 open 처리되지만.. 
		//   sqlite3_prepare 함수에서 table 이 생성되어 있지 않으므로 오류가 발생함.
		//   따라서 loop 중에 재시도하도록 한다.
		db_result = sqlite3_prepare( db, insert_sql, strlen( insert_sql ), &stmt, NULL );

		// sqlite3_prepare() 함수 실행시 SQLITE_BUSY 오류가 발생할 수 있으므로.. 재시도 로직을 추가한다.
		while( db_result == SQLITE_BUSY  && nRetryCount < SQLITE_DB_LOCK_MAX_RETRY )
		{
			// sqlite 에서 SQLITE_BUSY 반환 관련 lock 대기 timeout 을 설정.
			sqlite3_busy_timeout( db, SQLITE_DB_LOCK_TIMEOUT );
			++nRetryCount;

			LOG( LWAR, "[RecvServiceStat:%d] SaveThread: sqlite3 prepare SQLITE_BUSY, Retry[%d]", getpid(), nRetryCount );

			db_result = sqlite3_prepare( db, insert_sql, strlen( insert_sql ), &stmt, NULL );
		}

		if( db_result != SQLITE_OK )
		{
			LOG( LERR, "[RecvServiceStat:%d] SaveThread: sqlite3 prepare failed.[%d][%s]", getpid(), db_result, sqlite3_errmsg( db ) );
			sqlite3_close( db );
			db = NULL;
			stmt = NULL;
		}
	}
	

	// 루프를 돌면서... Queue 에 저장된 수신 정보를 POP -> 파싱하여.. sqlite DB 에 저장한다.
	// g_pRecvNetworkStatQueue 값은 프로세스 종료 처리시 NULL 로 변경된다.. 따라서 본 Thread 가 종료될 수 있도록 처리한다.
	while( g_pRecvNetworkStatQueue != NULL )
	{
		// Queue 에서 Data 를 Pop 한다..
		// Queue 객체 자체에서 인자값을 전달한 버퍼를 memset 하므로.. 따로 수행하지 않는다.
		pop_result = g_pRecvNetworkStatQueue->Pop( (char *)(&stNetworkStat), sizeof( struct ServiceNetworkStat ) );

		if( pop_result == 0 ) // Pop 성공시..
		{			
			_LOG( LDBG, "[RecvServiceStat:%d] SaveThread: stamp[%u] svc[%d][%d] traffic[u:%lu][d:%lu] size[u:%lu][d:%lu]"
				  , getpid()
				  , stNetworkStat.timestamp
				  , stNetworkStat.user_seq, stNetworkStat.svc_seq
				  , stNetworkStat.up_traffic, stNetworkStat.down_traffic
				  , stNetworkStat.up_content_size, stNetworkStat.down_content_size
				  );
			
			// 통계 정보 중.. DB 에 저장할 값이 모두 0 인 통계 정보는 불필요하므로... DB 에 입력처리 하지 않는다.
			// traffic = 0 (content size 도 자동으로 0이므로 ) 일 경우 불필요한 data 이므로 버린다.
			if( stNetworkStat.down_traffic == 0 && stNetworkStat.up_traffic == 0 )
				continue;

			// sqlite open 이 되지 않은 경우.. 재시도 처리 
			if( db == NULL )
			{
				_LOG( LINF, "[RecvServiceStat:%d] SaveThread: sqlite3 open retry.[%s]"
					  , getpid(), CProcessConfig::GetInstance()->GetLocalDbFileName() );

				db_result = sqlite3_open( CProcessConfig::GetInstance()->GetLocalDbFileName(), &db );
				if( db_result != SQLITE_OK )
				{
					LOG( LERR, "[RecvServiceStat:%d] SaveThread: sqlite3 open failed2.[%s][%d][%s]"
						 , getpid(), CProcessConfig::GetInstance()->GetLocalDbFileName(), db_result, sqlite3_errmsg( db ) );

					sqlite3_close( db );
					db = NULL;
				}
				else
				{
					nRetryCount = 0;

					// bind 관련 Query 를 수행할 stmt 객체 생성.
					db_result = sqlite3_prepare( db, insert_sql, strlen( insert_sql ), &stmt, NULL );

					// sqlite3_prepare() 함수 실행시 SQLITE_BUSY 오류가 발생할 수 있으므로.. 재시도 로직을 추가한다.
					while( db_result == SQLITE_BUSY  && nRetryCount < SQLITE_DB_LOCK_MAX_RETRY )
					{
						// sqlite 에서 SQLITE_BUSY 반환 관련 lock 대기 timeout 을 설정.
						sqlite3_busy_timeout( db, SQLITE_DB_LOCK_TIMEOUT );
						++nRetryCount;

						LOG( LWAR, "[RecvServiceStat:%d] SaveThread: sqlite3 prepare SQLITE_BUSY, Retry[%d]", getpid(), nRetryCount );

						db_result = sqlite3_prepare( db, insert_sql, strlen( insert_sql ), &stmt, NULL );
					}

					if( db_result != SQLITE_OK )
					{
						LOG( LERR, "[RecvServiceStat:%d] SaveThread: sqlite3 prepare failed2.[%d][%s]"
							 , getpid(), db_result, sqlite3_errmsg( db ) );

						sqlite3_close( db );
						db = NULL;
						stmt = NULL;
					}
				}
			}

			// sqlite 접속 불가시
			// - 위에서 접속 불가 발생시.. 해당 Data 는 처리 방법이 없으므로.. 버린다.
			// - 만약 잠시 대기 후 재시도할 경우... data 가 밀려... 최신 data 가 입력이 밀리고.. 이로 인해 traffic 제어 등에 문제가 발생할 수 있다.


			// sqlite 접속이 정상적으로 된 경우...
			if( stmt != NULL )
			{
				// insert query 에 변수 bind 처리. ( bind 값을 1부터 시작 )
				// text binding 에 사용되는 변수는 sqlite3_step() 함수 호출되기 전까지 변경이 없는 변수들이므로... SQLITE_TRANSIENT 대신 SQLITE_STATIC 을 사용한다.

				sqlite3_bind_int( stmt, 1, stNetworkStat.timestamp );
				sqlite3_bind_int( stmt, 2, stNetworkStat.user_seq );
				sqlite3_bind_int( stmt, 3, stNetworkStat.svc_seq );
								
				sqlite3_bind_text( stmt, 4, stNetworkStat.hostname, strlen( stNetworkStat.hostname ), SQLITE_STATIC );

				sqlite3_bind_int64( stmt, 5, stNetworkStat.down_traffic );
				sqlite3_bind_int64( stmt, 6, stNetworkStat.up_traffic );
				sqlite3_bind_int64( stmt, 7, stNetworkStat.down_content_size );
				sqlite3_bind_int64( stmt, 8, stNetworkStat.up_content_size );

				// insert query 수행.
				db_result = sqlite3_step( stmt );
				nRetryCount = 0;

				// 만약 BUSY 상태로 인해 오류가 발생하면..지정된 횟수 만큼 재시도
				while( db_result == SQLITE_BUSY  && nRetryCount < SQLITE_DB_LOCK_MAX_RETRY )
				{
					sqlite3_busy_timeout( db, SQLITE_DB_LOCK_TIMEOUT );
					++nRetryCount;

					LOG( LWAR, "[RecvServiceStat:%d] SaveThread: sqlite3 step SQLITE_BUSY, Retry[%d]", getpid(), nRetryCount );

					// query 재시도
					db_result = sqlite3_step( stmt );
				}


				if( db_result != SQLITE_DONE )
				{
					LOG( LERR, "[RecvServiceStat:%d] SaveThread: sqlite3 step error.[%d][%s]", getpid(), db_result, sqlite3_errmsg( db ) );

					// 만약 오류 코드가 SQLITE_ERROR, SQLITE_INTERNAL, SQLITE_EMPTY, SQLITE_SCHEMA 등이라면.... DB에 문제 있는 것으로 보고.. db 객체를 초기화한다.
					if( db_result == SQLITE_ERROR || db_result == SQLITE_INTERNAL || db_result == SQLITE_EMPTY || db_result == SQLITE_SCHEMA )
					{
						LOG( LERR, "[RecvServiceStat:%d] SaveThread: sqlite db force closed", getpid() );

						sqlite3_finalize( stmt );
						sqlite3_close( db );

						db = NULL;
						stmt = NULL;
					}
					else
					{
						// Query 수행 오류가 발생된 경우... 해당 값은 포기하고... 다음 prepared 문을 재사용하기 위해 reset 처리한다.
						sqlite3_reset( stmt );
					}
				}
				else
				{
					// Query 수행된 경우.. prepared 문을 재사용하기 위해 reset 처리한다.
					sqlite3_reset( stmt );
				}
			}
		}
		else if( pop_result == 1 )  // Queue 가 empty 인 경우..
		{
			// pop 할 data 가 없으므로.. 잠시 쉰다.. ( 1 sec 정도 )
			//_LOG( LDBG, "[RecvServiceStat:%d] SaveThread: queue empty. sleep", getpid());

			// usleep 대신 sleep, nanosleep 으로 변경 처리.
			//usleep( 1000000 );
			sleep( 1 );

		}
		else	// 나머지 상황은 오류..
		{
			LOG( LERR, "[RecvServiceStat:%d] SaveThread: queue pop unknown error.[%d]", getpid(), pop_result );
		}


	} // while


	// Thread 종료 관련 처리.
	return NULL;
}


// Worker::RecvServiceStat 에서 각 Client 에 대한 처리를 수행하기 위한 Thread.
void * WorkerRecvServiceStatClientThread( void * arg )
{
	// 전달받은 변수를 내부 변수에 저장 처리.

	// client socket 관련 처리 변수 생성	
	CSocketFimngd clientSocket( ( ( struct stRecvServiceStatThreadArg * ) arg )->clientSocketDesc );

	// client 주소 처리 관련 
	char szClientAddress[IP_ADDR_LENGTH];
	memcpy( szClientAddress, ( ( struct stRecvServiceStatThreadArg * ) arg )->szClientAddress, IP_ADDR_LENGTH );
		
	// malloc 로 생성된 변수 해제 처리
	free( arg );
	

	// 본 thead 는 client 연결 상태에 따라 종료되므로.. process 에서 분리 처리
	pthread_detach( pthread_self() );

	_LOG( LINF, "[RecvServiceStat:%d] client thread start. [%s]", getpid(), szClientAddress );

	// 송수신 처리 관련 변수 
	int  nResult;
	char command;

	// 수신된 서비스별 network 통계 정보를 저장하기 위한 vector
	// - ServiceNetworkStat 구조체는 SocketFimngd.h 에 정의
	std::vector< struct ServiceNetworkStat > vecNetworkStat;
	std::vector< struct ServiceNetworkStat >::iterator it;

	// socket 수신 대기
	while( 1 )
	{
		// 요청 수신 대기
		nResult = clientSocket.GetPacketHeader( command, MAX_REQUEST_TIMEOUT );

		if( nResult == 0 )
		{
			// timeout 발생시...
			// - 지정된 시간동안 요청이 없는 경우 세션을 종료 처리한다.
			_LOG( LINF, "[RecvServiceStat:%d] client[%s] request timeout[%d]. session close.", getpid(), szClientAddress, MAX_REQUEST_TIMEOUT );

			break;
		}
		else if( nResult < 0 )
		{
			// Socket 종료, 오류 또는 정의되지 않은 형식의 패킷 header 수신시...
			// 오류 내역은 socket 모듈에서 로깅하므로... 그냥 종료 처리.
			break;
		}
		else
		{
			// 정상 header 수신시.
			// - 수신된 command 에 따른 처리.
			
			if( command == COMMAND_SERVICE_NETWORK_STAT )
			{
				// service network stat 전송인 경우..

				// 수신된 통계 정보를 저장할 vector 초기화 후 Data 수신 처리.
				vecNetworkStat.clear();

				if( clientSocket.GetPacketBodyNetworkStat( vecNetworkStat ) == false )
				{
					// 수신 오류 발생시.
					LOG( LERR, "[RecvServiceStat:%d] client[%s] service network stat recv error. session close."
						 , getpid(), szClientAddress );

					// 세션 종료 처리
					break;
				}
				else
				{
					// service network 통계 정보가 정상 수신된 경우...
					// queue 에 저장 처리한다.					
					for( it = vecNetworkStat.begin(); it != vecNetworkStat.end(); ++it )
					{
						_LOG( LDBG, "[RecvServiceStat:%d] client[%s] [%s] stamp[%u] svc[%d][%d] traffic[u:%lu][d:%lu] size[u:%lu][d:%lu]"
							  , getpid(), szClientAddress
							  , it->hostname, it->timestamp, it->user_seq, it->svc_seq
							  , it->up_traffic, it->down_traffic, it->up_content_size, it->down_content_size );

						// queue 에 저장 처리						
						if( g_pRecvNetworkStatQueue != NULL )
						{
							nResult = g_pRecvNetworkStatQueue->Push( it->hostname, sizeof( struct ServiceNetworkStat ) );

							// Push 처리시 반환값
							// -1 : 전달 인자값에 NULL 존재
							// -2 : Queue 생성시 설정한 Data 크기보다 더 큰 nRead 값을 전달한 경우.
							//  1 : Queue 생성시 설정한 최대 Queue 갯수를 초과한 경우..
							//  0 : 정상 처리
							if( nResult != 0 )
							{
								LOG( LWAR, "[RecvServiceStat:%d] recv queue push error[%d]", getpid(), nResult );
							}
						}						
					}
				}
			}
			else
			{
				// 정의되지 않은 command 인 경우..
				// - 오류 로깅 후 세션 종료 처리.
				LOG( LERR, "[RecvServiceStat:%d] client[%s] request type not defined. session close.", getpid(), szClientAddress );
				clientSocket.PrintHeaderToLog();
				break;
			}
		}

	} // while(1)
	

	// client 와 연결 종료 처리.
	clientSocket.Close();
	_LOG( LINF, "[RecvServiceStat:%d] client[%s] connection closed.", getpid(), szClientAddress );
	
	// Thread 종료 관련 처리.
	return NULL;
}


// Worker::RecvServiceStat 프로세스의 Main 역활을 수행하기 위한 함수.
int WorkerRecvServiceStatMain()
{
	// 전역 변수로 설정된 socket 중 해당 worker 에서 사용하지 않는 socket 은 close 처리한다.
		
	// Process Rename..
	set_ps_display( PROG_NAME": RecvServiceStat", false );
	
	// Set Signal Handler
	SetWorkerRecvServiceStatSignalHandler();
	

	// 사용할 객체 초기화 처리

	// Data 전달을 담당할 Queue 객체 생성	
	g_pRecvNetworkStatQueue = new CFixedQueue( sizeof( struct ServiceNetworkStat ), MAX_DATA_QUEUE_COUNT );
	if( g_pRecvNetworkStatQueue == NULL )
	{
		// 객체 생성 실패시
		LOG( LERR, "[RecvServiceStat:%d] data queue create failed. process exit", getpid() );

		ReadyToExitWorkerRecvServiceStat();
		return EXIT_FAILURE;
	}

	// Queue 로 전달된 Data 를 파싱하여 local db에 저장 처리를 담당할 Thread 를 생성한다.
	pthread_t workerSaveThreadId;
	if( pthread_create( &workerSaveThreadId, NULL, WorkerRecvServiceStatSaveThread, NULL ) != 0 )
	{
		int errorNum = errno;
		LOG( LERR, "[RecvDavListener:%d] save thread create failed[%d][%s]. process exit", getpid(), errorNum, strerror( errorNum ) );

		ReadyToExitWorkerRecvServiceStat();
		return EXIT_FAILURE;
	}


	// 수신 처리 관련 변수 정의 및 초기화.
	int clientSock;
	struct sockaddr_in clientSockAddr;
	socklen_t clientSockLen = sizeof( clientSockAddr );

	pthread_t clientThreadId;
	struct stRecvServiceStatThreadArg * pThreadArg = NULL;
	

	while( 1 )
	{
		clientSock = accept( g_sockServiceStatRecv, ( struct sockaddr * ) &clientSockAddr, &clientSockLen );

		if( clientSock == -1 )
		{
			// 오류 발생시.... 해당 내역 로깅 처리.
			int errorNum = errno;
			LOG( LERR, "[RecvServiceStat:%d] client accept failed [%d][%s]", getpid(), errorNum, strerror( errorNum ) );
		}
		else
		{
			// 정상적인 Client 인 경우...
			// Thread 로 전달할 변수를 malloc 로 생성 처리
			pThreadArg = ( struct stRecvServiceStatThreadArg * ) malloc( sizeof( struct stRecvServiceStatThreadArg ) );
			if( pThreadArg == NULL )
			{
				int errorNum = errno;
				close( clientSock );    // client 접속 종료 처리.

				char tempBuffer[IP_ADDR_LENGTH];
				memset( tempBuffer, 0x00, sizeof( tempBuffer ) );
				if( inet_ntop( AF_INET, &( clientSockAddr.sin_addr ), tempBuffer, sizeof( tempBuffer ) ) != NULL )
				{
					LOG( LERR, "[RecvServiceStat:%d] client[%s] thread malloc failed [%d][%s]", getpid(), tempBuffer, errorNum, strerror( errorNum ) );
				}
				else
				{
					LOG( LERR, "[RecvServiceStat:%d] client thread malloc failed [%d][%s] and inet_ntop error[%d][%s]"
						 , getpid(), errorNum, strerror( errorNum ), errno, strerror( errno ) );
				}

				continue;
			}
			else
			{
				// 인자로 전달할 변수정보 설정.
				pThreadArg->clientSocketDesc = clientSock;

				char tempBuffer[IP_ADDR_LENGTH];
				memset( tempBuffer, 0x00, sizeof( tempBuffer ) );
				if( inet_ntop( AF_INET, &( clientSockAddr.sin_addr ), tempBuffer, sizeof( tempBuffer ) ) != NULL )
				{
					memcpy( pThreadArg->szClientAddress, tempBuffer, sizeof( tempBuffer ) );
				}
				else
				{
					// inet_ntop 오류 발생시
					// client ip 를 공유메모리 할당 관련 key 값으로 사용하므로.....
					// ip 가 불분명할 경우.. 잘못된 공유메모리 할당으로 인해 문제 발생 가능.
					// 따라서 관련 상세 내역 로깅 후.. 세션 종료 처리한다.
					//snprintf( pThreadArg->szClientAddress, IP_ADDR_LENGTH - 1, "0.0.0.0" );

					int errorNum = errno;
					LOG( LERR, "[RecvServiceStat:%d] client ip not valid. connection closed. inet_ntop error[%d][%s]", getpid(), errorNum, strerror( errorNum ) );

					close( clientSock );
					free( pThreadArg );

					continue;
				}
								

				// Client 처리를 담당할 Thread 생성 처리
				if( pthread_create( &clientThreadId, NULL, WorkerRecvServiceStatClientThread, (void *)pThreadArg ) != 0 )
				{
					// 생성 실패시...
					// - 로깅 
					// - client socket close 
					// - malloc 로 생성된 전달 변수 free 처리.

					int errorNum = errno;
					LOG( LERR, "[RecvServiceStat:%d] client[%s] thread create failed [%d][%s]"
						 , getpid(), pThreadArg->szClientAddress, errorNum, strerror( errorNum ) );


					close( clientSock );
					free( pThreadArg );
				}
				else
				{
					_LOG( LDBG, "[RecvServiceStat:%d] client[%s] accept & client thread create ok.", getpid(), pThreadArg->szClientAddress );
				}
			}
		}		
	} // while(1)
	

	// 종료시 정리 작업 수행
	ReadyToExitWorkerRecvServiceStat();
	return EXIT_SUCCESS;
}
