/** * @file network.cpp * @author myusgun@gmail.com * @brief network */ #include "cf/network.h" #include #include #include #ifdef _ON_WINDOWS # include # pragma comment(lib, "ws2_32.lib") #else # include # include # include # include # include # include # include # include # include # include #endif #ifdef _ON_WINDOWS typedef cf::int32_t socklen_t; # define sa_family_t cf::uint16_t # define ERROR_INTR WSAEINTR # define ERROR_CONNECTING WSAEWOULDBLOCK # define SOCKET_API_CALL __stdcall #else # define closesocket(__sock) ::close(__sock) # define ERROR_INTR EINTR # define ERROR_CONNECTING EINPROGRESS # define SOCKET_API_CALL #endif /*--------------------------------------------------------------*/ /** * do not include "cf/macaddr.[ch]pp" * just declare and call */ long GetMACAddress(cf::char_t *addr); static std::string convertAddressToString(struct sockaddr_in & addr) { cf::uint8_t * bytes = NULL; cf::char_t str[32] = {0x00}; bytes = reinterpret_cast(&addr.sin_addr.s_addr); snprintf(str, sizeof(str) - 1, "%u.%u.%u.%u", bytes[0], bytes[1], bytes[2], bytes[3]); return str; } static cf::void_t waitForTimeout(const cf::socket_t sock, const cf::int32_t timeout, const cf::bool_t checkWriteFD) throw (cf::exception) { cf::int32_t result = 0; fd_set rfds; fd_set wfds; fd_set * wfdsPtr = checkWriteFD ? &wfds : NULL; struct timeval tv; if (timeout <= 0) return; FD_ZERO(&rfds); FD_SET(sock, &rfds); FD_ZERO(&wfds); FD_SET(sock, &wfds); tv.tv_sec = timeout; tv.tv_usec = 0; result = select(sock + 1, &rfds, wfdsPtr, NULL, &tv); if (result < 0) THROW_EXCEPTION("select error"); else if (result == 0) THROW_EXCEPTION("socket timed out"); if (!FD_ISSET(sock, &rfds)) { if (!checkWriteFD) THROW_EXCEPTION("read fd is not set"); else if (!FD_ISSET(sock, &wfds)) THROW_EXCEPTION("write fd is not set"); } } static cf::void_t setReuseAddress(const cf::network::tcp & tcp) throw (cf::exception) { try { cf::int32_t reuseaddr = 1; tcp.setOption(SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr)); } catch (cf::exception & e) { FORWARD_EXCEPTION(e); } } typedef cf::int32_t (SOCKET_API_CALL * getSocketNameAPI)(cf::socket_t sock, struct sockaddr * addr, socklen_t *len); static cf::network::host getSocketNameFromFunction(const cf::socket_t sock, getSocketNameAPI api) throw (cf::exception) { cf::int32_t result = 0; struct sockaddr_in addr; socklen_t len = sizeof(struct sockaddr_in); result = api(sock, (struct sockaddr *)&addr, &len); if (result < 0) THROW_EXCEPTION("cannot get sockket or peer name (" << cf::exception::systemCode() << ")"); return cf::network::host(convertAddressToString(addr), addr.sin_port); } /*--------------------------------------------------------------*/ cf::network::host::host(const std::string & address, const cf::uint16_t port) : mAddress(address), mPort(port) { } std::string cf::network::host::address() const { return mAddress; } cf::uint16_t cf::network::host::port() const { return mPort; } cf::bool_t cf::network::host::empty() const { return (address().empty() || port() <= 0) ? true : false; } cf::network::tcp::tcp(const cf::socket_t attachedSocket) throw (cf::exception) : mSocket (attachedSocket), mTimeout(0) { static cf::bool_t isInitialized = false; /* initialize socket */ if (!isInitialized) { #if defined(_WIN32) || defined(_WIN64) WSADATA winsockData; cf::int32_t result = WSAStartup(MAKEWORD(2, 0), &winsockData); if (result) THROW_EXCEPTION("cannot start-up winsock"); #endif isInitialized = true; } if (mSocket == UNUSED_SOCKET) mSocket = ::socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); if (mSocket < 0) THROW_EXCEPTION("cannot create a socket"); } cf::network::tcp::~tcp() { close(); } cf::socket_t cf::network::tcp::descriptor() const { return mSocket; } cf::void_t cf::network::tcp::close() { if (mSocket == UNUSED_SOCKET) return; closesocket(mSocket); mSocket = UNUSED_SOCKET; } cf::void_t cf::network::tcp::connect(const cf::network::host & peer, const cf::int32_t timeout) throw (cf::exception) { if (peer.empty()) THROW_EXCEPTION("invalid host info"); cf::int32_t result = 0; cf::int32_t retval = 0; cf::int32_t length = 0; struct sockaddr_in addr; struct hostent * hostEnt; const cf::char_t * host = peer.address().c_str(); cf::uint16_t port = peer.port(); /* 1. set data */ addr.sin_family = AF_INET; addr.sin_port = htons(port); addr.sin_addr.s_addr = inet_addr(host); /* 2. get ip from hostname if inet_addr() is failed */ if (addr.sin_addr.s_addr == (unsigned int)-1) { hostEnt = gethostbyname(host); if (hostEnt == NULL) THROW_EXCEPTION("cannot get host by name"); addr.sin_family = (sa_family_t)hostEnt->h_addrtype; memcpy(&(addr.sin_addr.s_addr), hostEnt->h_addr, (size_t)hostEnt->h_length); } /* 3. set options */ try { setReuseAddress(*this); } catch (cf::exception & e) { FORWARD_EXCEPTION(e); } setTimeout(timeout); /* 4. connect */ result = ::connect(mSocket, (struct sockaddr *)&addr, sizeof(addr)); if (result < 0) { if (timeout > 0) { if (cf::exception::systemCode() != ERROR_CONNECTING) THROW_EXCEPTION("socket connect error"); try { length = sizeof(retval); waitForTimeout(mSocket, timeout, true); getOption(SO_ERROR, &retval, &length); } catch (cf::exception & e) { FORWARD_EXCEPTION(e); } if (retval) THROW_EXCEPTION("cannot get error from socket option"); } else { THROW_EXCEPTION("cannot connect to " << host << ":" << port); } } } cf::void_t cf::network::tcp::listen(const cf::uint16_t port, const cf::int32_t backlog) const throw (cf::exception) { if (!port) THROW_EXCEPTION("invalid port number"); cf::int32_t result = 0; struct sockaddr_in addr; /* 1. set data */ addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_ANY); addr.sin_port = htons(port); /* 2. set options */ try { setReuseAddress(*this); } catch (cf::exception & e) { FORWARD_EXCEPTION(e); } result = ::bind(mSocket, (struct sockaddr *)&addr, sizeof(struct sockaddr)); if (result < 0) THROW_EXCEPTION("cannot bind to " << port); result = ::listen(mSocket, backlog); if (result < 0) THROW_EXCEPTION("cannot listen"); } cf::network::tcp cf::network::tcp::accept() const throw (cf::exception) { cf::socket_t sock = 0; struct sockaddr_in addr; socklen_t len = sizeof(addr); sock = ::accept(mSocket, (struct sockaddr *)&addr, /* in/out */&len); if (sock < 0) THROW_EXCEPTION("cannot accept client"); return cf::network::tcp(sock).detach(); } cf::void_t cf::network::tcp::attach(const cf::socket_t sock) throw (exception) { if (sock == UNUSED_SOCKET) THROW_EXCEPTION("has invalid socket"); mSocket = sock; } cf::socket_t cf::network::tcp::detach() throw (exception) { if (mSocket == UNUSED_SOCKET) THROW_EXCEPTION("has invalid socket"); cf::socket_t sock = mSocket; mSocket = UNUSED_SOCKET; return sock; } cf::void_t cf::network::tcp::send(const cf::bin & in) const throw (cf::exception) { if (in.empty()) THROW_EXCEPTION("send data is null or zero-bytes"); cf::char_t * buf = reinterpret_cast(in.buffer()); cf::int32_t size = static_cast(in.size()); cf::int32_t sentSize = (cf::int32_t)::send (mSocket, buf, size, 0); if (sentSize != size) THROW_EXCEPTION("cannot send (" << cf::exception::systemCode() << ")"); } cf::void_t cf::network::tcp::receive(cf::bin & out) const throw (cf::exception) { if (out.size() == 0) THROW_EXCEPTION("binary buffer is not created"); try { waitForTimeout(mSocket, mTimeout, false); } catch (cf::exception & e) { FORWARD_EXCEPTION(e); } cf::char_t * buf = reinterpret_cast(out.buffer()); cf::int32_t size = static_cast(out.size()); cf::int32_t receivedSize = (cf::int32_t)::recv(mSocket, buf, size, 0); if (receivedSize < 0) THROW_EXCEPTION("cannot receive (" << cf::exception::systemCode() << ")"); else if (receivedSize == 0) out.clear(); else if (receivedSize < size) out.resize(receivedSize); } cf::bin cf::network::tcp::receive(const cf::int32_t size) const throw (cf::exception) { cf::bin out; out.resize(size); receive(out); return out; } cf::bin cf::network::tcp::receive() const throw (cf::exception) { const cf::size_t bufferSize = 1024; cf::bin buffer; cf::bin out; buffer.resize(bufferSize); do { receive(buffer); out.append(buffer); } while (buffer.size() == bufferSize); return out; } cf::void_t cf::network::tcp::getOption(const cf::int32_t optname, cf::void_t * optval, cf::int32_t * optlen) const throw (cf::exception) { cf::int32_t result = getsockopt(mSocket, SOL_SOCKET, optname, #ifdef _ON_WINDOWS (cf::char_t *)optval, #else optval, #endif (socklen_t *)optlen); if (result < 0) THROW_EXCEPTION("cannot get socket option (" << cf::exception::systemCode() << ")"); } cf::void_t cf::network::tcp::setOption(const cf::int32_t optname, const cf::void_t * optval, const cf::int32_t optlen) const throw (cf::exception) { cf::int32_t result = setsockopt(mSocket, SOL_SOCKET, optname, #ifdef _ON_WINDOWS (cf::char_t *)optval, #else optval, #endif (socklen_t)optlen); if (result < 0) THROW_EXCEPTION("cannot set socket option (" << cf::exception::systemCode() << ")"); } cf::void_t cf::network::tcp::setNonBlocking(const cf::bool_t flag) { #ifdef _ON_WINDOWS cf::ulong_t mode = flag ? 1 : 0; ioctlsocket(mSocket, FIONBIO, &mode); #else cf::int32_t mode = fcntl(mSocket, F_GETFL, 0); if (flag) mode |= O_NONBLOCK; else mode &= ~O_NONBLOCK; fcntl(mSocket, F_SETFL, mode); #endif } cf::void_t cf::network::tcp::setTimeout(const cf::int32_t seconds) { setNonBlocking(seconds > 0 /*? true : false*/); mTimeout = seconds; } cf::network::host cf::network::tcp::peer() const throw (cf::exception) { try { return getSocketNameFromFunction(mSocket, getpeername); } catch (cf::exception & e) { FORWARD_EXCEPTION(e); } } cf::network::host cf::network::tcp::local() const throw (cf::exception) { try { return getSocketNameFromFunction(mSocket, getsockname); } catch (cf::exception & e) { FORWARD_EXCEPTION(e); } } std::string cf::network::nic::getMACAddress() throw (cf::exception) { static const cf::int32_t macSize = 6; /* = 48-bits */ static cf::bool_t isDone = false; static cf::uint8_t bytes[macSize] = {0x00,}; static cf::char_t asciiz[macSize * 2 + 1] = {0x00,}; if (!isDone) { long result = 0; result = GetMACAddress(reinterpret_cast(bytes)); if (result) THROW_EXCEPTION("cannot get mac-address"); for (cf::int32_t iter = 0 ; iter < macSize ; iter++) snprintf(asciiz + (iter * 2), sizeof(asciiz) - 1, "%02x", bytes[iter]); isDone = true; } return std::string(asciiz); } cf::uint32_t cf::network::byteOrder::htonl(cf::uint32_t in) { return ::htonl(in); } cf::uint16_t cf::network::byteOrder::htons(cf::uint16_t in) { return ::htons(in); } cf::uint32_t cf::network::byteOrder::ntohl(cf::uint32_t in) { return ::ntohl(in); } cf::uint16_t cf::network::byteOrder::ntohs(cf::uint16_t in) { return ::ntohs(in); }