/** * \file cf_socket.c * * \author myusgun * * \brief TCP 소켓 구현 */ #include "cf_socket.h" #include "cf_local.h" #include "cf_error.h" #include "cf_util.h" #include #include #if defined(_WIN32) || defined(_WIN64) /* for windows {{{ */ # include # pragma comment (lib, "ws2_32.lib") /* }}} for windows */ #else /* for linux/unix {{{ */ # include # include # include # include # include # include # include # include # include # include /* }}} for linux/unix */ #endif #define ASSERT_CTX(__ctx) \ if (__ctx == NULL) \ return CF_ERROR_SOCKET_INVALID_CTX #define ASSERT_ARGS(x) \ if ((x)) \ return CF_ERROR_SOCKET_INVALID_ARGS #define ASSERT_INIT() \ if (!CF_Socket_IsInitialized ()) \ return CF_ERROR_SOCKET_NOT_INITIALIZED #define TRY_INIT() \ if (CF_Socket_Initialize ()) \ return CF_ERROR_SOCKET_NOT_INITIALIZED #define ASSERT_SOCKET(__sock) \ if (__sock < 0) \ return CF_ERROR_SOCKET_INVALID_SOCKET #if defined(_WIN32) || defined(_WIN64) typedef int socklen_t; # define sa_family_t unsigned short # define close(__sock) closesocket(__sock) # define ERROR_INTR WSAEINTR #else # define ERROR_INTR EINTR #endif #define MAX_IP_LENGTH 256 typedef struct __cf_socket_ctx__ { int sock; int timeout; char ip[MAX_IP_LENGTH]; unsigned short port; } CF_SOCKET_CONTEXT; static CF_BOOL gInitialized = CF_FALSE; static void CF_Socket_Local_SetNonBlocking (const int sock, CF_BOOL boolean) { #if defined(_WIN32) || defined(_WIN64) unsigned long mode = (boolean) ? 1 : 0; ioctlsocket (sock, FIONBIO, &mode); #else int flags = fcntl (sock, F_GETFL, 0); if (boolean) flags |= O_NONBLOCK; else flags &= ~O_NONBLOCK; fcntl (sock, F_SETFL, flags); #endif } static int CF_Socket_Local_SetOption (const int sock, const int optname, const void * optval, const size_t optlen) { int result = 0; ASSERT_SOCKET (sock); result = setsockopt (sock, SOL_SOCKET, optname, #if defined(_WIN32) || defined(_WIN64) (char *) optval, #else optval, #endif (socklen_t) optlen); if (result < 0) return CF_ERROR_SOCKET_SET_OPTION; return CF_OK; } static int CF_Socket_Local_GetOption (const int sock, const int optname, void * optval, size_t * optlen) { int result = 0; ASSERT_SOCKET (sock); result = getsockopt (sock, SOL_SOCKET, optname, #if defined(_WIN32) || defined(_WIN64) (char *) optval, #else optval, #endif (socklen_t *) optlen); if (result < 0) return CF_ERROR_SOCKET_GET_OPTION; return CF_OK; } static int CF_Socket_Local_SetLinger (const int sock) { struct linger linger; linger.l_onoff = 1; linger.l_linger = 0; return CF_Socket_Local_SetOption (sock, SO_LINGER, &linger, sizeof (linger)); } static int CF_Socket_Local_SetReUseAddr (const int sock) { int reuseaddr = 1; return CF_Socket_Local_SetOption (sock, SO_REUSEADDR, &reuseaddr, sizeof (reuseaddr)); } static int CF_Socket_Local_CheckTimeout (const int sock, const int timeout) { int result = 0; fd_set readfds; struct timeval tv; int error; if (timeout <= CF_SOCKET_NO_TIMEOUT) return CF_OK; tv.tv_sec = timeout; tv.tv_usec = 0; FD_ZERO (&readfds); FD_SET (sock, &readfds); while ((result = select (sock + 1, &readfds, NULL, NULL, &tv)) < 0) { error = CF_Util_GetSystemError (); if (error != ERROR_INTR) { result = CF_ERROR_SOCKET_TIMEOUT; return result; } } if (FD_ISSET (sock, &readfds) == 0) return CF_ERROR_SOCKET_CHECK_DESC_SET; return CF_OK; } /** * 소켓의 초기화 상태 확인 * * \return 초기화 된 경우, CF_TRUE; 그렇지 않은 경우, CF_FALSE */ static CF_BOOL CF_Socket_IsInitialized (void) { return gInitialized; } /** * 소켓 초기화 * * \return 성공 시, CF_OK; 실패 시, 오류 코드 */ int CF_Socket_Initialize (void) { if (!CF_Socket_IsInitialized ()) { #if defined(_WIN32) || defined(_WIN64) WSADATA winsockData; if (WSAStartup (MAKEWORD (2, 0), &winsockData)) return CF_ERROR_SOCKET_INITIALIZE; #endif gInitialized = CF_TRUE; } return CF_OK; } /** * 소켓 해제 * * \return 성공 시, CF_OK; 실패 시, 오류 코드 */ int CF_Socket_Finalize (void) { ASSERT_INIT (); #if defined(_WIN32) || defined(_WIN64) if (WSACleanup ()) return CF_ERROR_SOCKET_FINALIZE; #endif return CF_OK; } /** * 소켓 컨텍스트 생성 * * \return 성공 시, CF_OK; 실패 시, 오류 코드 * * \param ctx 소켓 컨텍스트 */ int CF_Socket_Create (cf_ctx * ctx) { int result = 0; CF_SOCKET_CONTEXT * context = NULL; ASSERT_CTX (ctx); TRY { context = NEWCTX (CF_SOCKET_CONTEXT); if (context == NULL) { result = CF_ERROR_SOCKET_CREATE_CTX; TRY_BREAK; } context->sock = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP); if (context->sock < 0) { result = CF_ERROR_SOCKET_CREATE; TRY_BREAK; } context->timeout = CF_SOCKET_NO_TIMEOUT; *ctx = (CF_SOCKET_CONTEXT *) context; } CATCH_IF (result < 0) { CF_Socket_Close (context); } return result; } /** * 소켓 닫기 * * \return 성공 시, CF_OK; 실패 시, 오류 코드 * * \param ctx 소켓 컨텍스트 */ int CF_Socket_Close (cf_ctx ctx) { int result = 0; int sock = 0; CF_SOCKET_CONTEXT * context = (CF_SOCKET_CONTEXT *) ctx; ASSERT_CTX (context); ASSERT_SOCKET (context->sock); sock = context->sock; free (context); result = close (sock); if (result != 0) return CF_ERROR_SOCKET_CLOSE; return CF_OK; } /** * 소켓 연결 * * \return 성공 시, CF_OK; 실패 시, 오류 코드 * * \param ctx 소켓 컨텍스트 * \param ip 연결할 호스트의 주소 (도메인 이름 가능) * \param port 연결할 호스트의 포트번호 */ int CF_Socket_Connect (cf_ctx ctx, const char * ip, const unsigned short port) { int result = 0; int sock = 0; int timeout = 0; struct sockaddr_in address; struct hostent * hostEnt; int retval = 0; size_t length = 0; struct timeval tv; fd_set readfds; fd_set writefds; CF_SOCKET_CONTEXT * context = (CF_SOCKET_CONTEXT *) ctx; ASSERT_CTX (ctx); TRY_INIT (); /* 1. get and set socket info. */ sock = context->sock; snprintf (context->ip, sizeof (context->ip), "%s", ip); context->port = port; timeout = context->timeout; /* 2. set data */ address.sin_family = AF_INET; address.sin_port = htons (port); address.sin_addr.s_addr = inet_addr (ip); TRY { /* 3. get ip from hostname if inet_addr() is failed */ if (address.sin_addr.s_addr == (unsigned int)-1) { hostEnt = gethostbyname (ip); if (hostEnt == NULL) { result = CF_ERROR_SOCKET_GET_HOST; TRY_BREAK; } address.sin_family = (sa_family_t) hostEnt->h_addrtype; memcpy (&(address.sin_addr.s_addr), hostEnt->h_addr, (size_t) hostEnt->h_length); } /* 4. set options */ if (CF_Socket_Local_SetLinger (sock) < 0 || CF_Socket_Local_SetReUseAddr (sock) < 0 ) { result = CF_ERROR_SOCKET_SET_OPTION; TRY_BREAK; } if (timeout > CF_SOCKET_NO_TIMEOUT) CF_Socket_Local_SetNonBlocking (sock, CF_TRUE); /* 5. connect */ result = connect (sock, (struct sockaddr *) &address, sizeof (address)); if (result == 0) { result = CF_OK; TRY_BREAK; } /* 6. if connect() is returned, check timeout */ if (timeout == CF_SOCKET_NO_TIMEOUT) { result = CF_ERROR_SOCKET_TIMEOUT; TRY_BREAK; } FD_ZERO (&readfds); FD_SET ((unsigned int)sock, &readfds); writefds = readfds; tv.tv_sec = timeout; tv.tv_usec = 0; result = select (sock + 1, &readfds, &writefds, NULL, &tv); if (result == 0) { result = CF_ERROR_SOCKET_TIMEOUT; TRY_BREAK; } if (FD_ISSET (sock, &readfds) || FD_ISSET (sock, &writefds)) { length = sizeof (retval); retval = 0; result = CF_Socket_GetOption (ctx, SO_ERROR, &retval, &length); if (result < 0) { result = CF_ERROR_SOCKET_GET_OPTION; TRY_BREAK; } else if (retval) { result = CF_ERROR_SOCKET_CONNECT; TRY_BREAK; } } else { result = CF_ERROR_SOCKET_CONNECT; TRY_BREAK; } } CATCH_IF (result < 0) { CF_Socket_Close (ctx); return result; } if (timeout > CF_SOCKET_NO_TIMEOUT) CF_Socket_Local_SetNonBlocking (sock, CF_FALSE); return result; } /** * 서버 열기 * * \return 성공 시, CF_OK; 실패 시, 오류 코드 * * \param ctx 소켓 컨텍스트 * \param port 서버 포트 * \param backlog listen 시의 backlog 수 */ int CF_Socket_Server (cf_ctx ctx, const unsigned short port, const int backlog) { int result = 0; int sock = 0; struct sockaddr_in address; CF_SOCKET_CONTEXT * context = (CF_SOCKET_CONTEXT *) ctx; ASSERT_CTX (ctx); TRY_INIT (); sock = context->sock; context->port = port; address.sin_family = AF_INET; address.sin_addr.s_addr = htonl (INADDR_ANY); address.sin_port = htons (port); TRY { if (CF_Socket_Local_SetLinger (sock) < 0 || CF_Socket_Local_SetReUseAddr (sock) < 0 ) { result = CF_ERROR_SOCKET_SET_OPTION; TRY_BREAK; } result = bind (sock, (struct sockaddr *) &address, sizeof (struct sockaddr)); if (result < 0) { result = CF_ERROR_SOCKET_BIND; TRY_BREAK; } snprintf (context->ip, sizeof (context->ip), "%s", inet_ntoa(address.sin_addr)); result = listen (sock, backlog); if (result < 0) { result = CF_ERROR_SOCKET_LISTEN; TRY_BREAK; } } CATCH_IF (result < 0) { CF_Socket_Close (ctx); } return result; } /** * 소켓 연결 받기 * * \return 성공 시, CF_OK; 실패 시, 오류 코드 * * \param ctx 소켓 컨텍스트 * \param client 연결을 수락할 client 소켓 컨텍스트 */ int CF_Socket_Accept (const cf_ctx ctx, cf_ctx * client) { int result = 0; struct sockaddr_in address; socklen_t len = sizeof (address); CF_SOCKET_CONTEXT * clntctx = NULL; CF_SOCKET_CONTEXT * context = (CF_SOCKET_CONTEXT *) ctx; ASSERT_CTX (context); ASSERT_CTX (client); ASSERT_SOCKET (context->sock); result = CF_Socket_Create ((cf_ctx *)&clntctx); if (result < 0) return result; close (clntctx->sock); clntctx->sock = accept (context->sock, (struct sockaddr *) &address, &len); if (clntctx->sock < 0) { CF_Socket_Close ((cf_ctx *)clntctx); return CF_ERROR_SOCKET_ACCEPT; } *client = (cf_ctx) clntctx; return CF_OK; } /** * 데이터 송신 * * \return 성공 시, CF_OK; 실패 시, 오류 코드 * * \param ctx 소켓 컨텍스트 * \param buf 송신할 데이터 * \param len 송신할 데이터의 길이 */ int CF_Socket_Send (const cf_ctx ctx, const void * buf, const size_t len) { int result = 0; CF_SOCKET_CONTEXT * context = (CF_SOCKET_CONTEXT *) ctx; ASSERT_CTX (context); ASSERT_SOCKET (context->sock); result = (int) send (context->sock, buf, len, 0); if ((size_t) result != len) return CF_ERROR_SOCKET_SEND; return CF_OK; } /** * 데이터 수신 * * \return 성공 시, 수신한 데이터의 길이; 실패 시, 오류 코드 * * \param ctx 소켓 컨텍스트 * \param buf 데이터를 수신할 버퍼 * \param len 데이터를 수신할 버퍼의 최대 크기 */ int CF_Socket_Recv (const cf_ctx ctx, void * buf, const size_t len) { int result = 0; CF_SOCKET_CONTEXT * context = (CF_SOCKET_CONTEXT *) ctx; ASSERT_CTX (context); ASSERT_SOCKET (context->sock); result = CF_Socket_Local_CheckTimeout (context->sock, context->timeout); if (result < 0) return result; result = (int) recv (context->sock, buf, len, 0); if (result < 0) return CF_ERROR_SOCKET_RECV; return result; } /** * 소켓 옵션 설정 * * \return 성공 시 CF_OK; 실패 시, 오류 코드 * * \param ctx 소켓 컨텍스트 * \param optname 옵션 이름 * \param optval 설정할 옵션 값의 메모리 * \param optlen 설정할 옵션의 길이 */ int CF_Socket_SetOption (const cf_ctx ctx, const int optname, const void * optval, const size_t optlen) { CF_SOCKET_CONTEXT * context = (CF_SOCKET_CONTEXT *) ctx; ASSERT_CTX (context); return CF_Socket_Local_SetOption (context->sock, optname, optval, optlen); } /** * 소켓 옵션 얻기 * * \return 성공 시 CF_OK; 실패 시, 오류 코드 * * \param ctx 소켓 컨텍스트 * \param optname 옵션 이름 * \param optval 옵션 값을 가져올 메모리 * \param optlen 옵션 길이를 가져올 메모리 */ int CF_Socket_GetOption (const cf_ctx ctx, const int optname, void * optval, size_t * optlen) { CF_SOCKET_CONTEXT * context = (CF_SOCKET_CONTEXT *) ctx; ASSERT_CTX (context); return CF_Socket_Local_GetOption (context->sock, optname, optval, optlen); } /** * 소켓 컨텍스트에 타임아웃 설정 * * \return CF_OK 반환 * * \param ctx 소켓 컨텍스트 * \param timeout 설정할 타임아웃 시간 */ int CF_Socket_SetTimeOut (cf_ctx ctx, int timeout) { CF_SOCKET_CONTEXT * context = (CF_SOCKET_CONTEXT *) ctx; ASSERT_CTX (context); context->timeout = timeout; return CF_OK; } /** * 소켓 컨텍스트에 타임아웃 설정 * * \return 성공 시 CF_OK; 실패 시, 오류 코드 * * \param ctx 소켓 컨텍스트 * \param ip ip 주소를 저장할 충분한 공간의 메모리 */ int CF_Socket_GetIP (const cf_ctx ctx, char * ip) { CF_SOCKET_CONTEXT * context = (CF_SOCKET_CONTEXT *) ctx; ASSERT_CTX (context); ASSERT_ARGS (ip == NULL); snprintf (ip, sizeof (context->ip), "%s", context->ip); return CF_OK; } /** * 소켓 컨텍스트에 타임아웃 설정 * * \return 성공 시 CF_OK; 실패 시, 오류 코드 * * \param ctx 소켓 컨텍스트 * \param port 포트 번호 저장할 포인터 */ int CF_Socket_GetPort (const cf_ctx ctx, unsigned short * port) { CF_SOCKET_CONTEXT * context = (CF_SOCKET_CONTEXT *) ctx; ASSERT_ARGS (port == NULL); *port = context->port; return CF_OK; }