#pragma once /** @file @brief tiny socket class @author MITSUNARI Shigeo(@herumi) @author MITSUNARI Shigeo @remark mingw requires -lws2_32 option */ #include #include #include #ifdef _WIN32 #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #include #include // for socklen_t #ifdef _MSC_VER #pragma comment(lib, "ws2_32.lib") #pragma comment(lib, "iphlpapi.lib") #pragma warning(push) #pragma warning(disable : 4127) // constant condition #endif #else #include #include #include #include #include #include #include #include #endif #ifndef NDEBUG #include #endif #include #include #include #include #ifdef __linux__ // #define CYBOZU_SOCKET_USE_EPOLL #include #endif namespace cybozu { #ifdef _MSC_VER struct NetErrorNo : public cybozu::ErrorNo { NetErrorNo(NativeErrorNo err) : cybozu::ErrorNo(err) { } NetErrorNo() : cybozu::ErrorNo(WSAGetLastError()) { } }; #else typedef cybozu::ErrorNo NetErrorNo; #endif #ifdef CYBOZU_SOCKET_USE_EPOLL namespace experimental { struct EpollEvent { struct epoll_event ev_; EpollEvent() { memset(&ev_, 0, sizeof(ev_)); } void set(int fd, uint32_t events = EPOLLIN) { ev_.events = events; ev_.data.fd = fd; } int getFd() const { return ev_.data.fd; } }; class Epoll { int efd_; bool verify(const char *msg, int ret, int *err) const { if (ret >= 0) return true; if (err == 0) throw cybozu::Exception(msg) << cybozu::NetErrorNo(); *err = errno; return false; } public: Epoll() : efd_(-1) {} bool init(int *err = 0) { efd_ = ::epoll_create1(0); return verify("Epoll:init", efd_, err); } ~Epoll() { if (efd_ >= 0) ::close(efd_); } /* throw if err == NULL */ bool ctrl(int op, int fd, EpollEvent *ev, int *err = 0) { int ret = ::epoll_ctl(efd_, op, fd, &ev->ev_); return verify("Epoll:ctrl", ret, err); } bool add(int fd, uint32_t events = EPOLLIN, int *err = 0) { EpollEvent ev; ev.set(fd, events); return ctrl(EPOLL_CTL_ADD, fd, &ev, err); } bool del(int fd, int *err = 0) { return ctrl(EPOLL_CTL_DEL, fd, NULL, err); } /* msec : 0 : block */ int wait(EpollEvent *ev, int maxEv, int msec = 0) { /* 0 : return immediately -1 : block indefinitely */ if (msec == 0) { msec = -1; } else if (msec == -1) { msec = 0; } int ret = ::epoll_wait(efd_, &ev->ev_, maxEv, msec); if (ret == 0) return 0; // timeout if (ret < 0) return -errno; return ret; } }; struct AutoLock { Epoll& ep_; int fd_; AutoLock(Epoll& ep, int fd, int events = EPOLLIN) : ep_(ep) , fd_(fd) { ep_.add(fd, events); } ~AutoLock() { int err; ep_.del(fd_, &err); } }; } // cybozu::experimental #endif namespace ssl { class ClientSocket; }; namespace socket_local { #ifdef _WIN32 typedef SOCKET SocketHandle; #else typedef int SocketHandle; #endif struct InitTerm { /** call once for init */ InitTerm() { #ifdef _WIN32 WSADATA data; int err = ::WSAStartup(MAKEWORD(2, 2), &data); if (err) { fprintf(stderr, "WSAStartup failed : %d\n", err); exit(1); } #else ::signal(SIGPIPE, SIG_IGN); #endif } /** call once for term */ ~InitTerm() { #ifdef _WIN32 ::WSACleanup(); #endif } void dummyCall() { } }; template struct InstanceIsHere { static InitTerm it_; }; template InitTerm InstanceIsHere::it_; struct DummyCall { DummyCall() { InstanceIsHere<>::it_.dummyCall(); } }; } // cybozu::socket_local class SocketAddr { union { // http://www.coins.tsukuba.ac.jp/~syspro/2010/No6_files/sockaddr.html struct sockaddr sa; /* 16byte */ struct sockaddr_in v4; /* 16byte */ struct sockaddr_in6 v6; } addr_; socklen_t addrlen_; int family_; friend class Socket; void verify() // call in only Socket::accept { if (addrlen_ == sizeof(addr_.v4) && addr_.sa.sa_family == AF_INET) { family_ = AF_INET; return; } if (addrlen_ == sizeof(addr_.v6) && addr_.sa.sa_family == AF_INET6) { family_ = AF_INET6; return; } throw cybozu::Exception("cybozu:SocketAddr:verify") << addrlen_; } public: SocketAddr() : addrlen_(0) , family_(0) { } SocketAddr(const std::string& address, uint16_t port, bool forceIpV6 = false) { set(address, port, forceIpV6); } void set(const std::string& address, uint16_t port, bool forceIpV6 = false) { char portStr[16]; CYBOZU_SNPRINTF(portStr, sizeof(portStr), "%d", port); memset(&addr_, 0, sizeof(addr_)); addrlen_ = 0; family_ = 0; struct addrinfo *result = 0; struct addrinfo hints; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; hints.ai_flags = AI_NUMERICSERV; // AI_PASSIVE; const int s = getaddrinfo(address.c_str(), portStr, &hints, &result); // s == EAI_AGAIN if (s || forceIpV6) { hints.ai_family = AF_INET6; hints.ai_flags |= AI_V4MAPPED; if (getaddrinfo(address.c_str(), portStr, &hints, &result)) { goto ERR_EXIT; } } { bool found = false; for (const struct addrinfo *p = result; p; p = p->ai_next) { const int family = p->ai_family; if (family == hints.ai_family) { if (p->ai_addrlen > sizeof(addr_)) { break; } memcpy(&addr_, p->ai_addr, p->ai_addrlen); addrlen_ = (socklen_t)p->ai_addrlen; family_ = family; found = true; break; } } freeaddrinfo(result); if (found) return; } ERR_EXIT: throw cybozu::Exception("SocketAddr:set") << address << port << cybozu::NetErrorNo(); } socklen_t getSize() const { return addrlen_; } int getFamily() const { return family_; } const struct sockaddr *get() const { return &addr_.sa; } uint16_t getPort() const { if (family_ == AF_INET) { return ntohs(addr_.v4.sin_port); } else if (family_ == AF_INET6) { return ntohs(addr_.v6.sin6_port); } throw cybozu::Exception("SocketAddr:getPort:bad family") << family_; } // compare addr without port bool hasSameAddr(const SocketAddr& rhs) const { const uint8_t *v4 = 0; const uint8_t *v6 = 0; if (family_ == AF_INET) { if (rhs.family_ == AF_INET) return memcmp(&addr_.v4.sin_addr, &rhs.addr_.v4.sin_addr, sizeof(in_addr)) == 0; if (rhs.family_ != AF_INET6) return false; v4 = (const uint8_t*)&addr_.v4.sin_addr; v6 = (const uint8_t*)&rhs.addr_.v6.sin6_addr; } else if (family_ != AF_INET6) { return false; } else { if (rhs.family_ == AF_INET6) return memcmp(&addr_.v6.sin6_addr, &rhs.addr_.v6.sin6_addr, sizeof(in6_addr)) == 0; if (rhs.family_ != AF_INET) return false; v4 = (const uint8_t*)&rhs.addr_.v4.sin_addr; v6 = (const uint8_t*)&addr_.v6.sin6_addr; } // Ipv6-mapped? const uint8_t header[12] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff }; return memcmp(v6, header, 12) == 0 && memcmp(v6 + 12, v4, 4) == 0; } std::string toStr() const { if (family_ == AF_INET || family_ == AF_INET6) { char buf[INET6_ADDRSTRLEN]; assert(INET_ADDRSTRLEN <= INET6_ADDRSTRLEN); const bool isIPv4 = family_ == AF_INET; const void *pa = isIPv4 ? (const void*)&addr_.v4.sin_addr : (const void*)&addr_.v6.sin6_addr; // not "const void*" because of vc const char *p = inet_ntop(family_, const_cast(pa), buf, sizeof(buf)); if (!p) throw cybozu::Exception("cybozu:SocketAddr:toStr") << cybozu::NetErrorNo(); if (isIPv4) return std::string(p) + ':' + cybozu::itoa(getPort()); return std::string("[") + p + "]:" + cybozu::itoa(getPort()); } throw cybozu::Exception("cybozu:SocketAddr:toStr:bad family_") << family_; } }; /* socket class @note ower is moved if copied */ class Socket { friend class cybozu::ssl::ClientSocket; private: cybozu::socket_local::SocketHandle sd_; Socket(const Socket&); void operator=(const Socket&); #ifdef WIN32 void setTimeout(int type, int msec) { setSocketOption(type, msec); } /* return msec */ int getTimeout(int type) const { return getSocketOption(type); } #else void setTimeout(int type, int msec) { struct timeval t; t.tv_sec = msec / 1000; t.tv_usec = (msec % 1000) * 1000; setSocketOption(type, t); } /* return msec */ int getTimeout(int type) const { struct timeval t; getSocketOption(type, &t); return t.tv_sec * 1000 + t.tv_usec / 1000; /* msec */ } #endif void setBlocking(bool isBlocking) { #ifdef _WIN32 u_long val = isBlocking ? 0 : 1; int ret = ::ioctlsocket(sd_, FIONBIO, &val); #else int val = isBlocking ? 0 : 1; int ret = ::ioctl(sd_, FIONBIO, &val); #endif if (ret < 0) throw cybozu::Exception("Socket:setBlocking") << cybozu::NetErrorNo() << isBlocking; } public: #ifndef _WIN32 static const int INVALID_SOCKET = -1; #endif Socket() : sd_(INVALID_SOCKET) { } bool isValid() const { return sd_ != INVALID_SOCKET; } // move #if CYBOZU_CPP_VERSION >= CYBOZU_CPP_VERSION_CPP11 Socket(Socket&& rhs) : sd_(INVALID_SOCKET) { sd_ = cybozu::AtomicExchange(&rhs.sd_, sd_); } #endif // close and move void moveFrom(Socket& rhs) { close(); sd_ = cybozu::AtomicExchange(&rhs.sd_, INVALID_SOCKET); } #if CYBOZU_CPP_VERSION >= CYBOZU_CPP_VERSION_CPP11 void operator=(Socket&& rhs) #else void operator=(Socket& rhs) #endif { moveFrom(rhs); } ~Socket() { close(cybozu::DontThrow); } bool close(bool dontThrow = false) { cybozu::socket_local::SocketHandle sd = cybozu::AtomicExchange(&sd_, INVALID_SOCKET); if (sd == INVALID_SOCKET) return true; #ifdef _WIN32 // ::shutdown(sd, SD_SEND); // shutdown is called in closesocket bool isOK = ::closesocket(sd) == 0; #else bool isOK = ::close(sd) == 0; #endif if (!dontThrow && !isOK) throw cybozu::Exception("Socket:close") << cybozu::NetErrorNo(); return isOK; } /* how 0 : SHUTRD ; disallow read 1 : SHUT_WR ; disallow write 2 : SHUT_RDWR ; disallow read/write */ bool shutdown(int how, bool dontThrow = false) { bool isOK = ::shutdown(sd_, how) == 0; if (!dontThrow && !isOK) throw cybozu::Exception("Socket:waitForClose:shutdown") << cybozu::NetErrorNo(); return isOK; } /* send FIN and wait for remote's close(). this function is used for the following situation. sock.write() sock.waitForClose() sock.close() */ void waitForClose() { if (sd_ == INVALID_SOCKET) return; // send FIN and this socket can't write any data. shutdown(1); // wait for FIN from the peer. char buf[1]; ssize_t readSize = readSome(buf, sizeof(buf)); if (readSize != 0) { throw cybozu::Exception("Socket:waitForClose:readSome:bad size") << readSize; } } /*! receive data @param buf [out] receive buffer @param bufSize [in] receive buffer size(byte) @note return read size */ size_t readSome(void *buf, size_t bufSize) { int size = (int)(std::min)((size_t)0x7fffffff, bufSize); #ifdef _WIN32 int readSize = ::recv(sd_, (char *)buf, size, 0); #else RETRY: ssize_t readSize = ::read(sd_, buf, size); if (readSize < 0 && errno == EINTR) goto RETRY; #endif if (readSize < 0) throw cybozu::Exception("Socket:readSome") << cybozu::NetErrorNo() << bufSize; return readSize; } /*! receive all data unless timeout @param buf [out] receive buffer @param bufSize [in] receive buffer size(byte) */ void read(void *buf, size_t bufSize) { char *p = (char *)buf; while (bufSize > 0) { size_t readSize = readSome(p, bufSize); if (readSize == 0) throw cybozu::Exception("Socket:read:readSize is zero"); p += readSize; bufSize -= readSize; } } /*! write all data @param buf [out] send buffer @param bufSize [in] send buffer size(byte) */ void write(bool *pb, const void *buf, size_t bufSize) { const char *p = (const char *)buf; while (bufSize > 0) { int size = (int)(std::min)(size_t(0x7fffffff), bufSize); #ifdef _WIN32 int writeSize = ::send(sd_, p, size, 0); #else int writeSize = ::write(sd_, p, size); if (writeSize < 0 && errno == EINTR) continue; #endif if (writeSize < 0) { *pb = false; return; } p += writeSize; bufSize -= writeSize; } *pb = true; } void write(const void *buf, size_t bufSize) { bool b; write(&b, buf, bufSize); if (!b) throw cybozu::Exception("Socket:write") << cybozu::NetErrorNo() << bufSize; } /** connect to address:port @param address [in] address @param port [in] port @param msec: 0 : block */ void connect(const std::string& address, uint16_t port, int msec = 0) { SocketAddr addr; addr.set(address, port); connect(addr, msec); } /** connect to resolved socket addr */ void connect(const cybozu::SocketAddr& addr, int msec = 0) { if (isValid()) throw cybozu::Exception("Socket:connect:already connect"); sd_ = ::socket(addr.getFamily(), SOCK_STREAM, IPPROTO_TCP); if (!isValid()) { throw cybozu::Exception("Socket:connect:socket") << cybozu::NetErrorNo(); } if (msec == 0) { if (::connect(sd_, addr.get(), addr.getSize()) < 0) { throw cybozu::Exception("Socket:connect") << cybozu::NetErrorNo() << addr.toStr(); } } else { setBlocking(false); if (::connect(sd_, addr.get(), addr.getSize()) < 0) { #ifdef _WIN32 bool inProgress = WSAGetLastError() == WSAEWOULDBLOCK; #else bool inProgress = errno == EINPROGRESS; #endif if (!inProgress) throw cybozu::Exception("Socket:connect:not in progress") << cybozu::NetErrorNo() << addr.toStr(); if (!queryAccept(msec, false)) throw cybozu::Exception("Socket:connect:timeout") << addr.toStr(); int err = getSocketOption(SO_ERROR); if (err != 0) throw cybozu::Exception("Socket::connect:bad socket") << cybozu::NetErrorNo(err); } setBlocking(true); } } static const int allowIPv4 = 1; static const int allowIPv6 = 2; /** init for server @param port [in] port number */ void bind(uint16_t port, int mode = allowIPv4 | allowIPv6) { const int family = (mode & allowIPv6) ? AF_INET6 : AF_INET; sd_ = ::socket(family, SOCK_STREAM, IPPROTO_TCP); if (!isValid()) { throw cybozu::Exception("Socket:bind:socket") << cybozu::NetErrorNo(); } setSocketOption(SO_REUSEADDR, 1); struct sockaddr_in6 addr6; struct sockaddr_in addr4; struct sockaddr *addr; socklen_t addrLen; if (mode & allowIPv6) { setSocketOption(IPV6_V6ONLY, (mode & allowIPv4) ? 0 : 1, IPPROTO_IPV6); memset(&addr6, 0, sizeof(addr6)); addr6.sin6_family = AF_INET6; addr6.sin6_port = htons(port); addr = (struct sockaddr*)&addr6; addrLen = sizeof(addr6); } else { memset(&addr4, 0, sizeof(addr4)); addr4.sin_family = AF_INET; addr4.sin_port = htons(port); addr = (struct sockaddr*)&addr4; addrLen = sizeof(addr4); } if (::bind(sd_, addr, addrLen) == 0) { if (::listen(sd_, SOMAXCONN) == 0) { return; } } cybozu::NetErrorNo keep; close(cybozu::DontThrow); throw cybozu::Exception("Socket:bind") << keep; } /** return positive if accepted return zero if timeout return negative(-errno) if error */ int queryAcceptNoThrow(int msec = 1000, bool checkWrite = true) { if (sd_ == INVALID_SOCKET) return -EBADF; #ifdef CYBOZU_SOCKET_USE_EPOLL int err; experimental::Epoll ep; if (!ep.init(&err)) return -err; uint32_t events = checkWrite ? EPOLLIN : EPOLLOUT; experimental::AutoLock al(ep, sd_, events); experimental::EpollEvent ev; int ret = ep.wait(&ev, 1, msec); if (ret != 1) return ret; assert(ev.getFd() == sd_); return ret; #else #ifndef _WIN32 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms739169.aspx if (sd_ >= FD_SETSIZE) return -EMFILE; #endif struct timeval timeout; timeout.tv_sec = msec / 1000; timeout.tv_usec = (msec % 1000) * 1000; fd_set fds; FD_ZERO(&fds); FD_SET((unsigned)sd_, &fds); int fdNum; if (checkWrite) { fdNum = ::select((int)sd_ + 1, &fds, 0, 0, &timeout); } else { fdNum = ::select((int)sd_ + 1, 0, &fds, 0, &timeout); } if (fdNum < 0) return -errno; return fdNum; #endif } /** return true if acceptable, otherwise false return false if one second passed while (!server.queryAccept()) { } client.accept(server); */ bool queryAccept(int msec = 1000, bool checkWrite = true) { int ret = queryAcceptNoThrow(msec, checkWrite); if (ret < 0) throw cybozu::Exception("Socket:queryAccept") << cybozu::NetErrorNo(-ret); return ret > 0; } /** accept for server */ void accept(Socket& client, SocketAddr *paddr = 0) const { if (paddr) { struct sockaddr *psa = &paddr->addr_.sa; paddr->addrlen_ = sizeof(paddr->addr_); client.sd_ = ::accept(sd_, psa, &paddr->addrlen_); paddr->verify(); } else { client.sd_ = ::accept(sd_, 0, 0); } if (!client.isValid()) throw cybozu::Exception("Socket:accept") << cybozu::NetErrorNo(); } template void setSocketOption(int optname, const T& value, int level = SOL_SOCKET) { bool isOK = setsockopt(sd_, level, optname, cybozu::cast(&value), sizeof(T)) == 0; if (!isOK) throw cybozu::Exception("Socket:setSocketOption") << cybozu::NetErrorNo(); } template void getSocketOption(int optname, T* value, int level = SOL_SOCKET) const { socklen_t len = (socklen_t)sizeof(T); bool isOK = getsockopt(sd_, level, optname, cybozu::cast(value), &len) == 0; if (!isOK) throw cybozu::Exception("Socket:getSocketOption") << cybozu::NetErrorNo(); } int getSocketOption(int optname) const { int ret; getSocketOption(optname, &ret); return ret; } /** setup linger */ void setLinger(uint16_t l_onoff, uint16_t l_linger) { struct linger linger; linger.l_onoff = l_onoff; linger.l_linger = l_linger; setSocketOption(SO_LINGER, &linger); } /** get receive buffer size @retval positive buffer size(byte) @retval -1 error */ int getReceiveBufferSize() const { return getSocketOption(SO_RCVBUF); } /** set receive buffer size @param size [in] buffer size(byte) */ void setReceiveBufferSize(int size) { setSocketOption(SO_RCVBUF, size); } /** get send buffer size @retval positive buffer size(byte) @retval -1 error */ int getSendBufferSize() const { return getSocketOption(SO_SNDBUF); } /** sed send buffer size @param size [in] buffer size(byte) */ void setSendBufferSize(int size) { setSocketOption(SO_SNDBUF, size); } /** set send timeout @param msec [in] msec */ void setSendTimeout(int msec) { setTimeout(SO_SNDTIMEO, msec); } /** set receive timeout @param msec [in] msec */ void setReceiveTimeout(int msec) { setTimeout(SO_RCVTIMEO, msec); } /** get send timeout(msec) */ int getSendTimeout() const { return getTimeout(SO_SNDTIMEO); } /** get receive timeout(msec) */ int getReceiveTimeout() const { return getTimeout(SO_RCVTIMEO); } }; } // cybozu #ifdef _MSC_VER #pragma warning(pop) #endif