/** * @file cf_socket.c * @author myusgun */ #include "cf_socket.h" #include "cf_local.h" #include "cf_error.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 /* }}} for linux/unix */ #endif #define ASSERT_INIT() \ if (!CF_Socket_IsInitialized ()) \ 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 CHECK_SELECT(x) ((x) == SOCKET_ERROR) # define GET_SYSTEM_ERROR() WSAGetLastError () # define ERROR_INTR WSAEINTR #else # define CHECK_SELECT(x) ((x) < 0) # define GET_SYSTEM_ERROR() errno # define ERROR_INTR EINTR #endif 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 == CF_TRUE) ? 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_SetLinger (const int sock) { struct linger linger; linger.l_onoff = 1; linger.l_linger = 0; return CF_Socket_SetOption (sock, SO_LINGER, &linger, sizeof (linger)); } static int CF_Socket_Local_SetReUseAddr (const int sock) { int reuseaddr = 1; return CF_Socket_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 (CHECK_SELECT (result = select (sock + 1, &readfds, NULL, NULL, &tv))) { error = GET_SYSTEM_ERROR (); if (error != ERROR_INTR) { result = CF_ERROR_SOCKET_TIMEOUT; return result; } } if (FD_ISSET (sock, &readfds) == 0) { return CF_ERROR_SOCKET_INTERNAL; } return CF_OK; } /** * 소켓의 초기화 상태 확인 * * @return 초기화 된 경우, CF_TRUE; 그렇지 않은 경우, CF_FALSE */ CF_BOOL CF_Socket_IsInitialized (void) { return gInitialized; } /** * 소켓 초기화 * * @return 성공 시, CF_OK; 실패 시, 오류 코드 */ int CF_Socket_Initialize (void) { #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 sock 소켓 */ int CF_Socket_Close (const int sock) { int result = 0; ASSERT_SOCKET (sock); result = close (sock); if (result != 0) return CF_ERROR_SOCKET_CLOSE; return CF_OK; } /** * 소켓 옵션 설정 * * @return 성공 시 CF_OK; 실패 시, 오류 코드 * * @param sock 소켓 * @param optname 옵션 이름 * @param optval 설정할 옵션 값의 메모리 * @param optlen 설정할 옵션의 길이 */ int CF_Socket_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; } /** * 소켓 옵션 얻기 * * @return 성공 시 CF_OK; 실패 시, 오류 코드 * * @param sock 소켓 * @param optname 옵션 이름 * @param optval 옵션 값을 가져올 메모리 * @param optlen 옵션 길이를 가져올 메모리 */ int CF_Socket_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; } /** * 소켓 연결 * * @return 성공 시, 연결된 소켓; 실패 시, 오류 코드 * * @param ip 연결할 호스트의 주소 (도메인 이름 가능) * @param port 연결할 호스트의 포트번호 */ int CF_Socket_Connect (const char * ip, const unsigned short port) { return CF_Socket_ConnectTimeout (ip, port, CF_SOCKET_NO_TIMEOUT); } /** * 타임아웃 동안 소켓 연결 * * @return 성공 시, 연결된 소켓; 실패 시, 오류 코드 * * @param ip 연결할 호스트의 주소 (도메인 이름 가능) * @param port 연결할 호스트의 포트번호 * @param timeout 타임아웃 (초) * * @see CF_SOCKET_NO_TIMEOUT */ int CF_Socket_ConnectTimeout (const char * ip, const unsigned short port, const int timeout) { int result = 0; int sock = 0; struct sockaddr_in address; struct hostent * hostEnt; int retval = 0; size_t length = 0; struct timeval tv; fd_set readfds; fd_set writefds; ASSERT_INIT (); /* 1. create socket */ sock = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP); if (sock < 0) return CF_ERROR_SOCKET_CREATE; /* 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 (sock, 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 (sock); return result; } if (timeout > CF_SOCKET_NO_TIMEOUT) CF_Socket_Local_SetNonBlocking (sock, CF_FALSE); return sock; } /** * 서버 열기 * * @return 성공 시, 서버 소켓; 실패 시, 오류 코드 * * @param port 서버 포트 * @param backlog listen 시의 backlog 수 */ int CF_Socket_Server (const unsigned short port, const int backlog) { int result = 0; int sock = 0; struct sockaddr_in address; ASSERT_INIT (); sock = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP); if (sock < 0) return CF_ERROR_SOCKET_CREATE; 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; } result = listen (sock, backlog); if (result < 0) { result = CF_ERROR_SOCKET_LISTEN; TRY_BREAK; } } CATCH_IF (result < 0) { CF_Socket_Close (sock); return result; } return sock; } /** * 소켓 연결 * * @return 성공 시, 연결된 소켓; 실패 시, 오류 코드 * * @param sock 서버 소켓 * * @see CF_Socket_HostInfo */ int CF_Socket_Accept (const int sock) { int sockClient = 0; struct sockaddr_in address; socklen_t len = sizeof (address); ASSERT_SOCKET (sock); sockClient = accept (sock, (struct sockaddr *) &address, &len); if (sockClient < 0) return CF_ERROR_SOCKET_ACCEPT; return sockClient; } /** * 데이터 송신 * * @return 성공 시, CF_OK; 실패 시, 오류 코드 * * @param sock 소켓 * @param buf 송신할 데이터 * @param len 송신할 데이터의 길이 */ int CF_Socket_Send (const int sock, const void * buf, const int len) { return CF_Socket_SendTimeout (sock, buf, len, CF_SOCKET_NO_TIMEOUT); } /** * 타임아웃 동안 데이터 송신 * * @return 성공 시, CF_OK; 실패 시, 오류 코드 * * @param sock 소켓 * @param buf 송신할 데이터 * @param len 송신할 데이터의 길이 * @param timeout 타임아웃 (초) * * @see CF_SOCKET_NO_TIMEOUT */ int CF_Socket_SendTimeout (const int sock, const void * buf, const int len, const int timeout) { int result = 0; ASSERT_SOCKET (sock); result = (int) send (sock, buf, (size_t) len, 0); if (result != len) return CF_ERROR_SOCKET_SEND; return CF_OK; } /** * 데이터 수신 * * @return 성공 시, 수신한 데이터의 길이; 실패 시, 오류 코드 * * @param sock 소켓 * @param buf 데이터를 수신할 버퍼 * @param len 데이터를 수신할 버퍼의 최대 크기 */ int CF_Socket_Recv (const int sock, void * buf, const int len) { return CF_Socket_RecvTimeout (sock, buf, len, CF_SOCKET_NO_TIMEOUT); } /** * 타임아웃 동안 데이터 수신 * * @return 성공 시, 수신한 데이터의 길이; 실패 시, 오류 코드 * * @param sock 소켓 * @param buf 데이터를 수신할 버퍼 * @param len 데이터를 수신할 버퍼의 최대 크기 * @param timeout 타임아웃 (초) * * @see CF_SOCKET_NO_TIMEOUT */ int CF_Socket_RecvTimeout (const int sock, void * buf, const int len, const int timeout) { int result = 0; ASSERT_SOCKET (sock); TRY { result = CF_Socket_Local_CheckTimeout (sock, timeout); if (result < 0) { TRY_BREAK; } result = (int) recv (sock, buf, (size_t) len, 0); if (result < 0) { result = CF_ERROR_SOCKET_RECV; TRY_BREAK; } } CATCH_IF (result < 0) { return result; } return result; }