/** * @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 #else # define closesocket(__sock) ::close(__sock) # define ERROR_INTR EINTR # define ERROR_CONNECTING EINPROGRESS #endif #define ALLOW_SOCKET_TIME_WAIT cf::socket_t gInvalidSocket = -1; /*--------------------------------------------------------------*/ /** * do not include "cf/macaddr.[ch]pp" * just declare and call */ long GetMACAddress(cf::char_t *addr); 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 setLinger(cf::network::tcp & tcp) throw (cf::exception) { #ifdef ALLOW_SOCKET_TIME_WAIT return; #else try { struct linger linger; linger.l_onoff = 1; linger.l_linger = 0; tcp.setOption(SO_LINGER, &linger, sizeof(linger)); } catch (cf::exception & e) { FORWARD_EXCEPTION(e); } #endif } static cf::void_t setReuseAddress(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); } } /*--------------------------------------------------------------*/ 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 == gInvalidSocket) mSocket = ::socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); if (mSocket < 0) THROW_EXCEPTION("cannot create a socket"); } cf::network::tcp::~tcp() { close(); } cf::void_t cf::network::tcp::close() { if (mSocket == gInvalidSocket) return; closesocket(mSocket); mSocket = gInvalidSocket; mHost = ""; mPort = 0; } cf::void_t cf::network::tcp::connect(const cf::char_t * host, const cf::uint16_t port, const cf::int32_t timeout) throw (cf::exception) { if (!host || !port) 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; /* 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 { setLinger(*this); setReuseAddress(*this); } catch (cf::exception & e) { FORWARD_EXCEPTION(e); } if (timeout > 0) setNonBlocking(true); /* 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); } } /* set */ mHost = host; mPort = port; mTimeout = timeout; } cf::void_t cf::network::tcp::listen(const cf::uint16_t port, const cf::int32_t backlog) 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 { setLinger(*this); 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() 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 == gInvalidSocket) THROW_EXCEPTION("has invalid socket"); close(); mSocket = sock; } cf::socket_t cf::network::tcp::detach() throw (exception) { if (mSocket == gInvalidSocket) THROW_EXCEPTION("has invalid socket"); cf::socket_t sock = mSocket; mSocket = gInvalidSocket; return sock; } cf::void_t cf::network::tcp::send(const cf::bin & in) 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) 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) throw (cf::exception) { cf::bin out; out.resize(size); receive(out); return out; } cf::bin cf::network::tcp::receive() 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) 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) 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) { mTimeout = seconds; } 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); }