/**    
    @file     tcp.c
    @date     2009-03-20
    @author   오재경 freefrug@falinux.com
    @brief    tcp 를 사용한 통신을 담당한다.
              
    @modify   write() 함수 호출시 블럭되는 일이 발생하여 tcp_write_thread()함수 추가
              send() 함수를 사용하여 블럭이 발생하지 않도록 tcp_write()함수를 수정하였으며
              BF_POLICY_WRITE_NONBLOCK 옵션으로 사용한다.
              
              2010-03-19 (오재경) poll_obj_t 구조체에서 on_disconnect 멤버 추가
                                  소켓 접속이 끊어질 경우 on_disconnect 함수 호출
              2010-08-18 (장길석) mingw와 함께 사용할 수 있는 코드 추가
    @todo     
    @bug     
    @remark   쓰레드를 위해서 컴파일 옵션에 LDFLAGS += -lpthread 추가
    
    @warning 
*/
//
//  저작권    에프에이리눅스(주)
//            외부공개 금지
//
//----------------------------------------------------------------------------
#define EMBEDDED_LINUX                                          // 이렇게 처리하지 않으면 EClipse에서 C 영역이 회색 바탕이 됨

#ifdef MS_WIN32
    #undef EMBEDDED_LINUX
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
#include <sys/types.h>

#ifdef EMBEDDED_LINUX

    #include <sys/un.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <sys/un.h>
    #include <sys/socket.h>
    #include <sys/poll.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <pthread.h>

#else

    #include <windows.h>
    #include <winsock2.h>

#endif


#include <tlist.h>
#include <pollmng.h>
#include <tcp.h>
#include <util.h>

char desc_tcp[] = "falinux tcp ver 0.2.1";

#ifdef EMBEDDED_LINUX

// #define _USE_SEND_THREAD_

#endif

/// tcp 개별 구조체
typedef struct {
	
	char  host[256];
	int   port;
	int   sock_type;
    struct sockaddr_in addr;
	
	// client 정보
	poll_obj_t *obj_server;   // 서버에서 분리된 클라이언트를 위해
	long        life_sec;     // 입력이 특정시가나 동안 없으면 접속을 끊는다.
	
	// 서버용 정보
	tlist *client_list;
	int    client_max;

#ifdef _USE_SEND_THREAD_	
	// 쓰레드 관련정보
	pthread_mutex_t mutex_send;     // 아래의 변수를 보호한다.
	int    send_cnt;                // 전송개수
	int    send_buf_len;            // 전송버퍼 크기
	char  *send_buf;                // 전송버퍼
#endif	
	
} tcp_priv_t;

/// 변수 정의
static unsigned int tcp_live_sec = DEFAULT_LIVE_SEC;
static unsigned int bf_policy    = BF_POLICY_CLOSE_IOERR | BF_POLICY_READ_ZERO_ERROR; // | BF_POLICY_WRITE_NONBLOCK;

#ifdef EMBEDDED_LINUX

//------------------------------------------------------------------------------
/** @brief    소켓접속이 끊어진 소켓접속시 에러를 캡쳐 함수
    @param    signo  시그널 번호
*///----------------------------------------------------------------------------
static void sig_capture_SIGPIPE( int signo )
{
	// 시그널 캡쳐만하고 에러에 대한 처리는 하지 않는다.
	// printf("siganl BrokenPipe\n" );
	
	signo   = signo;                                                            // jwjw : warning을 피하기 위해
}
//------------------------------------------------------------------------------
/** @brief    broken pipe 캡쳐 설정
    
*///----------------------------------------------------------------------------
static int  hook_sigpipe = 0;
static void hook_SIGPIPE( void )
{
	struct sigaction sig;
	
	if ( 0 == hook_sigpipe )
	{
		sig.sa_handler = sig_capture_SIGPIPE;
		sigemptyset(&sig.sa_mask);
		sig.sa_flags = 0;	
		sigaction( SIGPIPE, &sig, 0 );
		
		hook_sigpipe = 1;
	}
}
//------------------------------------------------------------------------------
/** @brief    tcp 정책을 설정한다.
    @param    option  bit 에 옵션을 설정한다.
    @remark   BF_POLICY_CLOSE_IOERR        read(), write() 함수 에러발생시 접속을 끊는다.
              BF_POLICY_CLOSE_TIMEOUT      SOCK_TYPE_SERVER_CLIENT 타입에 한해서 특정시간이 지난후라면 접속을 끊는다.
              BF_POLICY_RELOAD_TIMEOUT     SOCK_TYPE_SERVER_CLIENT 타입에 한해서 시간을 재갱신한다.
              BF_POLICY_CLOSE_OFF_LINK     SOCK_TYPE_SERVER_CLIENT 타입에 한해서 하드웨어적인 링크가 끊어지면 접속을 끊는다.
              BF_POLICY_READ_ZERO_ERROR    read() 함수에서 읽은 값이 0이면 에러처리한다.
*///----------------------------------------------------------------------------
void  tcp_set_policy( unsigned int option )
{
	bf_policy = option;	
}
//------------------------------------------------------------------------------
/** @brief    tcp 정책을 얻는다.
    @return   bf_policy
*///----------------------------------------------------------------------------    
unsigned int  tcp_get_policy( void )
{
	return bf_policy;
}
//------------------------------------------------------------------------------
/** @brief    통신이 일정시간 없으면 접속을 끊는데 사용하는 시간을 설정한다.
    @param    sec  생존시간 초단위
    @remark   SOCK_TYPE_SERVER_CLIENT 타입인 경우만 적용된다.
*///----------------------------------------------------------------------------
void  tcp_set_livesec( int sec )
{
	tcp_live_sec = sec;	
}

#endif

//------------------------------------------------------------------------------
/** @brief    tcp 소켓을 서버형태로 open 한다.
    @param    port  포트번호
    @param    max_client  접속할수 있는 클라이언트 최대개수
    @return   poll_obj_t 형태의 포인터
*///----------------------------------------------------------------------------

#ifdef EMBEDDED_LINUX

poll_obj_t  *tcp_open_server( int port, int max_client )
{
	struct sockaddr_in addr_in;
	poll_obj_t *obj;
	tcp_priv_t *tcp;
	int option;
	int sockfd;
	
	// broken pipe 캡쳐 설정
	hook_SIGPIPE();
	
	sockfd = socket( PF_INET, SOCK_STREAM, 0 );
	if ( sockfd < 0 )
	{
		perror( "tcp open error:" );
		return NULL;	
	}

	// TIME-WAIT 상태에 있는 소켓에 할당되어 있는 IP 주소와 포트를 바로 사용할 수 있도록
	// SO_REUSEADDR 의 옵션 값을 TRUE 로
	option = 1;                                                         
	setsockopt( sockfd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option));

	// 소켓을 시스템에 연결한다.
	bzero( &addr_in, sizeof(struct sockaddr_in) );
	addr_in.sin_family      = AF_INET;
	addr_in.sin_addr.s_addr = htonl( INADDR_ANY );
	addr_in.sin_port        = htons( port );
	
	if( bind( sockfd, (struct sockaddr *)&addr_in, sizeof(struct sockaddr_in) ) < 0 )
	{
		perror("tcp bind error :"); 
	    close( sockfd );
	    return NULL;
	}

	// listen
	if ( listen( sockfd, max_client ) < 0 )
	{
		perror("tcp listen error :"); 
		close( sockfd );
	    return NULL;
	}
	
	// tcp 만의 정보를 설정한다.
	tcp = (tcp_priv_t *)malloc( sizeof(tcp_priv_t) );
	
	tcp->sock_type    = SOCK_TYPE_SERVER;
	tcp->port         = port;
	tcp->client_max   = max_client;
	tcp->client_list  = tlist_create();
#ifdef _USE_SEND_THREAD_	
	pthread_mutex_init( &tcp->mutex_send, NULL );
	tcp->send_buf     = NULL;
	tcp->send_buf_len = 0;
	tcp->send_cnt     = 0;
#endif	

	// 폴관리객체에 등록한다.
	obj = poll_add( sockfd );
	obj->type = STYP_TCP;
	obj->priv = (void *)tcp;
	//obj->on_poll_in  = tcp_default_on_accept;
	
	poll_rebuild();

	return obj;
}

#else

poll_obj_t  *tcp_open_server( int port, int max_client )
{
    SOCKET      hSock;
    SOCKADDR_IN sckAddr;

    poll_obj_t *obj;
    tcp_priv_t *tcp;
    char        option;

    hSock = socket( PF_INET, SOCK_STREAM, 0 );
    if ( INVALID_SOCKET == hSock){
        perror( "tcp server open error:");
        return NULL;
    }

    hSock = socket( PF_INET, SOCK_STREAM, 0 );
    if ( INVALID_SOCKET == hSock){
        perror( "udp open error:");
        return NULL;
    }

    // TIME-WAIT 상태에 있는 소켓에 할당되어 있는 IP 주소와 포트를 바로 사용할 수 있도록
    // SO_REUSEADDR 의 옵션 값을 TRUE 로
    option = 1;
    setsockopt( hSock, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option));

    // 소켓을 시스템에 연결한다.
    memset( &sckAddr, 0, sizeof( sckAddr));
    sckAddr.sin_family      = AF_INET;
    sckAddr.sin_addr.s_addr = htonl( INADDR_ANY );
    sckAddr.sin_port        = htons( port );

    if( SOCKET_ERROR == bind( hSock, ( SOCKADDR *)&sckAddr, sizeof( sckAddr) )){
        perror("tcp bind error");
        closesocket( hSock );
        return NULL;
    }

    // listen
    if ( SOCKET_ERROR == listen( hSock, max_client ) ){
        perror("tcp listen error :");
        closesocket( hSock );
        return NULL;
    }

    // tcp 만의 정보를 설정한다.
    tcp = (tcp_priv_t *)malloc( sizeof(tcp_priv_t) );

    tcp->sock_type    = SOCK_TYPE_SERVER;
    tcp->port         = port;
    tcp->client_max   = max_client;
    tcp->client_list  = tlist_create();
#ifdef _USE_SEND_THREAD_
    pthread_mutex_init( &tcp->mutex_send, NULL );
    tcp->send_buf     = NULL;
    tcp->send_buf_len = 0;
    tcp->send_cnt     = 0;
#endif

    // 폴관리객체에 등록한다.
    fd_t  pSock         = malloc( sizeof( SOCKET));
    *( SOCKET*)pSock    = hSock;
    obj = poll_add( pSock );
    obj->type = STYP_TCP;
    obj->priv = (void *)tcp;
    //obj->on_poll_in  = tcp_default_on_accept;

    poll_rebuild();

    return obj;
}

#endif

//------------------------------------------------------------------------------
/** @brief    tcp 소켓을 클라이언트 형태로 open 하고 상대편에 접속한다
	@param    ip  IP문자열 포인터
	@param    port  포트번호
    @return   poll_obj_t 형태의 포인터
*///----------------------------------------------------------------------------
#ifdef EMBEDDED_LINUX

poll_obj_t  *tcp_open_client( char *ip, int port )
{
	struct sockaddr_in addr_in;
	poll_obj_t *obj;
	tcp_priv_t *tcp;
	int option;
	int sockfd;
	
	sockfd = socket( PF_INET, SOCK_STREAM, 0 );
	if ( sockfd < 0 )
	{
		perror( "tcp open error:" );
		return NULL;	
	}

	// TIME-WAIT 상태에 있는 소켓에 할당되어 있는 IP 주소와 포트를 바로 사용할 수 있도록
	// SO_REUSEADDR 의 옵션 값을 TRUE 로
	option = 1;                                                         
	setsockopt( sockfd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option));

	// 소켓을 상대편에 연결한다.
	bzero( &addr_in, sizeof(struct sockaddr_in) );
	addr_in.sin_family      = AF_INET;
	addr_in.sin_addr.s_addr = inet_addr( ip );
	addr_in.sin_port        = htons( port );
	
	if ( 0 > connect( sockfd, (struct sockaddr *)&addr_in, sizeof(struct sockaddr_in) ) )
	{
		perror( "tcp connect error :" );
		close( sockfd );
		return NULL;
	}
	
	// tcp 만의 정보를 설정한다.
	tcp = (tcp_priv_t *)malloc( sizeof(tcp_priv_t) );
	
	tcp->sock_type    = SOCK_TYPE_CLIENT;
	tcp->port         = port;
	tcp->obj_server   = NULL;
	tcp->life_sec     = get_cur_sec() + tcp_live_sec;
#ifdef _USE_SEND_THREAD_	
	pthread_mutex_init( &tcp->mutex_send, NULL );
	tcp->send_buf     = NULL;
	tcp->send_buf_len = 0;
	tcp->send_cnt     = 0;
#endif	
	
	sprintf( tcp->host, "%s", ip );
	memcpy ( &tcp->addr, &addr_in, sizeof(addr_in) );

	// 폴관리객체에 등록한다.
	obj = poll_add( sockfd );
	obj->type = STYP_TCP;
	obj->priv = (void *)tcp;

	return obj;
}

#else

poll_obj_t  *tcp_open_client( char *ip, int port )
{
    SOCKET      hSock;
    SOCKADDR_IN sckAddr;

    poll_obj_t *obj;
    tcp_priv_t *tcp;
    char        option;

    hSock = socket( PF_INET, SOCK_STREAM, 0 );
    if ( INVALID_SOCKET == hSock){
        printf( "tcp server open error:\n\r");
        return NULL;
    }

    // 소켓을 시스템에 연결한다.
    memset( &sckAddr, 0, sizeof( sckAddr));
    sckAddr.sin_family      = AF_INET;
    sckAddr.sin_addr.s_addr = inet_addr( ip );
    sckAddr.sin_port        = htons( port );

    // TIME-WAIT 상태에 있는 소켓에 할당되어 있는 IP 주소와 포트를 바로 사용할 수 있도록
    // SO_REUSEADDR 의 옵션 값을 TRUE 로
    option = 1;
    setsockopt( hSock, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option));

    // 서버에 연결을 시도한다.
    if ( SOCKET_ERROR ==  connect( hSock, ( SOCKADDR *)&sckAddr, sizeof( sckAddr) ) ){
        printf( "tcp connect error :\n\r" );
        closesocket( hSock );
        return NULL;
    }

    // tcp 만의 정보를 설정한다.
    tcp = (tcp_priv_t *)malloc( sizeof(tcp_priv_t) );

    tcp->sock_type    = SOCK_TYPE_CLIENT;
    tcp->port         = port;
    tcp->obj_server   = NULL;
    tcp->life_sec     = get_cur_sec() + tcp_live_sec;
#ifdef _USE_SEND_THREAD_
    pthread_mutex_init( &tcp->mutex_send, NULL );
    tcp->send_buf     = NULL;
    tcp->send_buf_len = 0;
    tcp->send_cnt     = 0;
#endif

    sprintf( tcp->host, "%s", ip );
    memcpy ( &tcp->addr, &sckAddr, sizeof(sckAddr) );

    // 폴관리객체에 등록한다.
    fd_t  pSock         = malloc( sizeof( SOCKET));
    *( SOCKET*)pSock    = hSock;
    obj = poll_add( pSock );
    obj->type = STYP_TCP;
    obj->priv = (void *)tcp;

    return obj;
}

#endif

//------------------------------------------------------------------------------
/** @brief    클라이언트 소켓의 접속을 폴관린 객체에 등록한다.
	@param    obj_server 폴객체 포인터
	@param    sockfd     클라이언트 소켓
	@param    paddr      인터넷 어드레스구조체 포인터
    @return   poll_obj_t 형태의 포인터
*///----------------------------------------------------------------------------

#ifdef EMBEDDED_LINUX

static poll_obj_t  *tcp_connect_client( poll_obj_t *obj_server, int sockfd, struct sockaddr_in *paddr )
{
	poll_obj_t *obj;
	tcp_priv_t *tcp, *server_tcp;
	
	// 클라이언트 접속의 개수를 파악한다.
	server_tcp = (tcp_priv_t *)obj_server->priv;
	if ( tlist_getcount(server_tcp->client_list) >= server_tcp->client_max )
	{
		close( sockfd );
		printf( "tcp connect error : client is full (%d)\n", server_tcp->client_max );
		return NULL;
	}
	
	// tcp 만의 정보를 설정한다.
	tcp = (tcp_priv_t *)malloc( sizeof(tcp_priv_t) );
	
	tcp->sock_type    = SOCK_TYPE_SERVER_CLIENT;
	tcp->port         = -1;
	tcp->obj_server	  = obj_server; // 클라이언트 소켓을 할당한 서버정보
	tcp->life_sec     = get_cur_sec() + tcp_live_sec;
#ifdef _USE_SEND_THREAD_	
	pthread_mutex_init( &tcp->mutex_send, NULL );
	tcp->send_buf     = NULL;
	tcp->send_buf_len = 0;
	tcp->send_cnt     = 0;
#endif
	
	sprintf( tcp->host, "%s", inet_ntoa( paddr->sin_addr) );
	memcpy( &tcp->addr, paddr, sizeof(struct sockaddr_in) );
	
	// 폴관리객체에 등록한다.
	obj = poll_add( sockfd );
	if ( obj )
	{
		// 서버에 접속된 클라이언트를 리스트에 등록
		tlist_add( server_tcp->client_list, obj );
		
		// 접속시간 관리가 필요하다면
		if ( bf_policy & (BF_POLICY_CLOSE_TIMEOUT | BF_POLICY_CLOSE_OFF_LINK) ) 
		{
			obj->on_timeout = tcp_check_time;
		}
		
		obj->type = STYP_TCP;
		obj->priv = (void *)tcp;
		return obj;
	}
	else
	{
		close( sockfd );
		free( tcp );
		return NULL;	
	}
}

#else

static poll_obj_t  *tcp_connect_client( poll_obj_t *obj_server, SOCKET hSockClient, SOCKADDR_IN *sckAddr){

    poll_obj_t *obj;
    tcp_priv_t *tcp, *server_tcp;
    fd_t        pSock;

    // 클라이언트 접속의 개수를 파악한다.
    server_tcp = (tcp_priv_t *)obj_server->priv;
    if ( tlist_getcount(server_tcp->client_list) >= server_tcp->client_max )
    {
        closesocket( hSockClient);
        printf( "tcp connect error : client is full (%d)\n", server_tcp->client_max );
        return NULL;
    }

    // tcp 만의 정보를 설정한다.
    tcp = (tcp_priv_t *)malloc( sizeof(tcp_priv_t) );

    tcp->sock_type    = SOCK_TYPE_SERVER_CLIENT;
    tcp->port         = -1;
    tcp->obj_server   = obj_server; // 클라이언트 소켓을 할당한 서버정보
    tcp->life_sec     = get_cur_sec() + tcp_live_sec;
#ifdef _USE_SEND_THREAD_
    pthread_mutex_init( &tcp->mutex_send, NULL );
    tcp->send_buf     = NULL;
    tcp->send_buf_len = 0;
    tcp->send_cnt     = 0;
#endif

    sprintf( tcp->host, "%s", inet_ntoa( sckAddr->sin_addr) );
    memcpy( &tcp->addr, sckAddr, sizeof(struct sockaddr_in) );

    // 폴관리객체에 등록한다.
    pSock = malloc( sizeof( SOCKET));
    *( SOCKET*)pSock = hSockClient;
    obj = poll_add( pSock );
    if ( obj ){
        // 서버에 접속된 클라이언트를 리스트에 등록
        tlist_add( server_tcp->client_list, obj );

        // 접속시간 관리가 필요하다면
        if ( bf_policy & (BF_POLICY_CLOSE_TIMEOUT | BF_POLICY_CLOSE_OFF_LINK) )
        {
            obj->on_timeout = tcp_check_time;
        }

        obj->type = STYP_TCP;
        obj->priv = (void *)tcp;
        return obj;
    }
    else
    {
        closesocket( hSockClient);
        free( tcp );
        return NULL;
    }
}

#endif

//------------------------------------------------------------------------------
/** @brief    tcp 소켓을 close 한다.
    @param    obj  폴객체 포인터
*///----------------------------------------------------------------------------
void tcp_close( poll_obj_t *obj )
{
	tcp_priv_t *tcp, *server_tcp;
	
	// 소켓 끊어짐을 사용자에게 알린다.
	if ( obj->on_disconnect )
	{
		obj->on_disconnect( obj );
	}
	
	tcp = (tcp_priv_t *)obj->priv;
	
	switch( tcp->sock_type )
	{
	case SOCK_TYPE_SERVER_CLIENT :
		{
			// 서버가 관리하는 클라이언트를 리스트에서 삭제
			server_tcp = (tcp_priv_t *)(tcp->obj_server->priv);
			if ( server_tcp  )
			{
				tlist_remove( server_tcp->client_list, obj );
			}
		}
		break;
		
	case SOCK_TYPE_SERVER :
		{
			// 서버가 관리하는 클라이언트의 server_tcp 를 모두 NULL 로 만든다.
			// @@@@@@@@@@
			tlist_free( tcp->client_list );
		}
		break;
	}

#ifdef EMBEDDED_LINUX
	
	close( obj->fd );

#else

	closesocket( *( ( SOCKET *)obj->fd));
    free( obj->fd);                                             // SOCKET * 를 해제한다.

#endif

	if ( obj->priv )
	{
#ifdef _USE_SEND_THREAD_	
		if ( tcp->send_buf ) free( tcp->send_buf );
#endif			
		free( obj->priv );
	}
	
	poll_delete( obj );
}

//------------------------------------------------------------------------------
/** @brief    tcp 소켓의 접속요구를 처리한후 클라이언트소켓을 등록한다.
    @param    obj_server   tcp 서버  폴객체 포인터
    @return   tcp 클라이언트 폴객체 포인터
*///----------------------------------------------------------------------------
#ifdef EMBEDDED_LINUX

poll_obj_t  *tcp_accept_client( poll_obj_t *obj_server )
{
	struct sockaddr_in client_addr;
	int       client;
	socklen_t addr_len;
	
	addr_len = sizeof(struct sockaddr_in);
	
	client = accept( obj_server->fd, (struct sockaddr *)&client_addr, &addr_len);

	return tcp_connect_client( obj_server, client, &client_addr );
}

#else

poll_obj_t  *tcp_accept_client( poll_obj_t *obj_server )
{
    SOCKET      hSockServer = *( SOCKET *)obj_server->fd;
    SOCKET      hSockClient;
    SOCKADDR_IN sckAddr;
    int         addr_len;

    addr_len = sizeof( sckAddr);
    hSockClient = accept( hSockServer, ( SOCKADDR *)&sckAddr, &addr_len);

    return tcp_connect_client( obj_server, hSockClient, &sckAddr );
}

#endif

//------------------------------------------------------------------------------
/** @brief    tcp 소켓을 통해 데이타를 전송한다.
    @param    obj     폴객체 포인터
    @param    buf     전송버퍼
    @param    len     버퍼의 길이
    @return   전송한 데이타 개수
    @remark   에러에 대한 처리를 해야한다.
              tcp_close( obj ) 를 호출하여 접속을 끊는 방법이 일반적이다.
              BF_POLICY_AUTO_CLOSE 옵션이 있을경우 자동으로 소켓을 닫는다.
*///----------------------------------------------------------------------------
int  tcp_write( poll_obj_t *obj, char *buf, int len )
{
	tcp_priv_t *tcp;
	int  wrcnt;

#ifdef EMBEDDED_LINUX

    // NON 블럭 옵션이 있다면
    if ( bf_policy & BF_POLICY_WRITE_NONBLOCK )
    {
		wrcnt = send( obj->fd, buf, len, MSG_DONTWAIT);
    }
    else
    {
        wrcnt = write( obj->fd, buf, len );
    }

#else
		SOCKET  hSock   = *(( SOCKET *)obj->fd);

		wrcnt = send( hSock, buf, len, 0);
#endif

	if ( 0 > wrcnt ){
		perror( "tcp send error:" );
		
		if ( bf_policy & BF_POLICY_CLOSE_IOERR )
		{
			tcp_close( obj );
		}
	}
	else
	{
		// 정책에 대한 결정을 한다.
		tcp = (tcp_priv_t *)obj->priv;
		if ( tcp->sock_type  == SOCK_TYPE_SERVER_CLIENT )
		{
			if ( bf_policy & BF_POLICY_RELOAD_TIMEOUT )
			{
				tcp->life_sec = get_cur_sec() + tcp_live_sec;
			}
		}
	}
		
	return wrcnt;
}
#ifdef _USE_SEND_THREAD_	
//------------------------------------------------------------------------------
/** @brief    전송 쓰레드함수이다.
    @param    obj  폴객체 포인터
    @return   
*///----------------------------------------------------------------------------
static void	*thread_write_func( poll_obj_t *obj )
{
	tcp_priv_t  *tcp;
	int          wrcnt;
	
	tcp   = (tcp_priv_t *)obj->priv;
	wrcnt = 0;
	
	if ( tcp->send_buf && ( 0 < tcp->send_cnt ) )
	{
		wrcnt = write( obj->fd, tcp->send_buf, tcp->send_cnt );	
	}
	
	pthread_mutex_lock( &tcp->mutex_send );
	
	tcp->send_cnt = 0;
	
	pthread_mutex_unlock( &tcp->mutex_send );
	
	return (void *)wrcnt;
}

//------------------------------------------------------------------------------
/** @brief    쓰레드를 생성하여 데이타를 전송한다.
    @param    obj     폴객체 포인터
    @param    buf     전송버퍼
    @param    len     버퍼의 길이
    @return   전송한 데이타 개수
*///----------------------------------------------------------------------------
int  tcp_write_thread( poll_obj_t *obj, char *buf, int len )
{
    obj = obj;                                                                  // jwjw: warning을 피하기 위해
    buf = buf;                                                                  // jwjw: warning을 피하기 위해
    len = len;                                                                  // jwjw: warning을 피하기 위해
        
#ifdef _USE_SEND_THREAD_	
	tcp_priv_t *tcp;
	int         rtn = 0;
	tcp = (tcp_priv_t *)obj->priv;

	pthread_mutex_lock( &tcp->mutex_send );
	
	// 전송중인 쓰레드가 살아 있는가?
	if ( 0 == tcp->send_cnt )
	{
		// 버퍼의 크기가 작다면 재 할당한다.
		if ( len > tcp->send_buf_len )
		{
			if ( tcp->send_buf ) free( tcp->send_buf );
				
			tcp->send_buf_len = ((len + 4096-1)/4096)*4096;
			tcp->send_buf     = malloc( tcp->send_buf_len );
		}
		
		tcp->send_cnt = len;
		memcpy( tcp->send_buf, buf, tcp->send_cnt );
		
		// 쓰레드 생성
		{
			pthread_t	   thr_id;
			pthread_attr_t thr_attributes;
			int		       thr_status;
    		
			// 쓰레드 생성시 프로세스와 분리
			pthread_attr_init( &thr_attributes );
			pthread_attr_setdetachstate( &thr_attributes, PTHREAD_CREATE_DETACHED );
    		
			// 쓰레드 생성
			thr_status = pthread_create( &thr_id, &thr_attributes, thread_write_func, (void *)obj );
			if ( thr_status != 0 ) 
			{
				perror( "pthread_create() " );
				rtn = -1;
			}
			else
			{
				rtn = len;	
			}
		}
		
	}
	else
	{
		rtn = 0;
	}
	
	pthread_mutex_unlock( &tcp->mutex_send );

	return rtn;
#else
	printf( "not surported %s()\n", __FUNCTION__ );
    
    return -1;
#endif
	
}

#endif

//------------------------------------------------------------------------------
/** @brief    tcp 소켓을 통해 데이타를 읽는다.
    @param    obj     폴객체 포인터
    @param    len     버퍼의 길이
    @return   전송된 데이타 개수
    @remark   에러에 대한 처리를 해야한다.
              tcp_close( obj ) 를 호출하여 접속을 끊는 방법이 일반적이다.
              BF_POLICY_AUTO_CLOSE 옵션이 있을경우 자동으로 소켓을 닫는다.
*///----------------------------------------------------------------------------
int  tcp_read( poll_obj_t *obj, char *buf, int len )
{
	tcp_priv_t *tcp;
	int  rdcnt;

#ifdef EMBEDDED_LINUX
	
	rdcnt = read( obj->fd, buf, len );
	
#else

	SOCKET  hSock = *(( SOCKET *)obj->fd);

    rdcnt = recv( hSock, buf, len, 0 );

#endif

	if ( 0 > rdcnt )
	{
		perror( "tcp recv error:" );
		
		if ( bf_policy & BF_POLICY_CLOSE_IOERR )
		{
			tcp_close( obj );
		}
	}
	else if ( 0 == rdcnt )
	{
		if ( 0 > errno )
		{
			perror( "tcp recv error:" );
			if ( bf_policy & BF_POLICY_CLOSE_IOERR )
			{
				tcp_close( obj );
			}
		}
		else
		{
			if ( bf_policy & BF_POLICY_READ_ZERO_ERROR )
			{
				printf( "tcp read 0 error: close socket\n" );
				tcp_close( obj );
			}
		}
	}
	
	// 정책에 대한 결정을 한다.
	tcp = (tcp_priv_t *)obj->priv;
	if ( tcp->sock_type  == SOCK_TYPE_SERVER_CLIENT )
	{
		if ( bf_policy & BF_POLICY_RELOAD_TIMEOUT )
		{
			tcp->life_sec = get_cur_sec() + tcp_live_sec;
		}
	}
	
	return rdcnt;
}
//------------------------------------------------------------------------------
/** @brief    일정시간이 지난을때 호출되며 tcp 정책에 의해 일을 수행한다.
    @remark   BF_POLICY_CLOSE_TIMEOUT, BF_POLICY_CLOSE_OFF_LINK 옵션을 사용한다면
              이함수가 자동 등록되어 수행된다.
              link-off 처리는 되지 않는다.
    @return   리턴값은 의미없다
*///----------------------------------------------------------------------------
int  tcp_check_time( poll_obj_t *obj )
{
	tcp_priv_t *tcp;
	
	// 타임오버 처리
	if 	( bf_policy & BF_POLICY_CLOSE_TIMEOUT ) 
	{
		tcp = (tcp_priv_t *)obj->priv;
		
		if ( ( unsigned int)tcp->life_sec < get_cur_sec() )
		{
			printf( "tcp close by timeout\n" );
			tcp_close( obj );
		}
	}

	// 링크 off 처리
	if 	( bf_policy & BF_POLICY_CLOSE_OFF_LINK ) 
	{
		//
	}
	
	return 0;
}

//------------------------------------------------------------------------------
/** @brief    tcp 서버에서 접속된 소켓의 개수를 얻는다.
    @param    obj_server    tcp 서버 폴객체 포인터
    @return   접속된 클라이언트 개수
*///----------------------------------------------------------------------------
int tcp_client_count( poll_obj_t *obj_server )
{
	tcp_priv_t *tcp;
	
	tcp = (tcp_priv_t *)obj_server->priv;
	if ( tcp->client_list )
	{
		return tlist_getcount( tcp->client_list );
	}

	return 0;
}
//------------------------------------------------------------------------------
/** @brief    tcp 서버에서 인덱스로 접속된 소켓 폴 객체를 얻는다.
    @param    obj_server   tcp 서버 폴객체 포인터
    @param    idx          인덱스
    @return   클라이언트 폴객체
*///----------------------------------------------------------------------------
poll_obj_t *tcp_get_client( poll_obj_t *obj_server, int idx )
{
	tcp_priv_t *tcp;
	
	tcp = (tcp_priv_t *)obj_server->priv;
	if ( tcp->client_list && ( 0 <= idx ) )
	{
		if ( idx < tlist_getcount( tcp->client_list ) )
		{
			return tlist_get( tcp->client_list, idx );
		}
	}

	return NULL;
}
//------------------------------------------------------------------------------
/** @brief    tcp 클라이언트 폴객체에서 서버객체를 얻는다.
    @param    obj_client   tcp 클라이언트 폴객체 포인터
    @return   서버 폴객체
*///----------------------------------------------------------------------------
poll_obj_t *tcp_get_server( poll_obj_t *obj_client )
{
	tcp_priv_t *tcp;
	
	tcp = (tcp_priv_t *)obj_client->priv;
	
	return tcp->obj_server;
}
//------------------------------------------------------------------------------
/** @brief    연결된 상대편의 IP 문자열을 얻는다.
    @param    obj_client   tcp 클라이언트 폴객체 포인터
    @return   연결된 ip를 문자열로 돌려준다. 없으면 NULL
*///----------------------------------------------------------------------------
const char *tcp_peer_ip_string( poll_obj_t *obj_client )
{
	tcp_priv_t *tcp;
	
	tcp = (tcp_priv_t *)obj_client->priv;
	if ( tcp )
	{
		return tcp->host;
	}
	
	return NULL;
}