# HG changeset patch # User David Demelier # Date 1445536851 -7200 # Node ID 2dbaf2fb03ef431b4779f130c6e0dd8868d6c0c7 # Parent b9c8b179288942bfadd9aa893a5659ca56f9b272 Socket: add forgotten Sockets.* diff -r b9c8b1792889 -r 2dbaf2fb03ef C++/modules/Socket/Sockets.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/modules/Socket/Sockets.cpp Thu Oct 22 20:00:51 2015 +0200 @@ -0,0 +1,611 @@ +/* + * Sockets.cpp -- portable C++ socket wrappers + * + * Copyright (c) 2013-2015 David Demelier + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#if defined(_WIN32) +# include +# include +#endif + +#include +#include + +#include "Sockets.h" + +namespace net { + +/* + * Portable constants + * ------------------------------------------------------------------ + */ + +/* {{{ Constants */ + +#if defined(_WIN32) + +const Handle Invalid{INVALID_SOCKET}; +const int Failure{SOCKET_ERROR}; + +#else + +const Handle Invalid{-1}; +const int Failure{-1}; + +#endif + +/* }}} */ + +/* + * Portable functions + * ------------------------------------------------------------------ + */ + +/* {{{ Functions */ + +#if defined(_WIN32) + +static std::mutex s_mutex; +static std::atomic s_initialized{false}; + +#endif // !_WIN32 + +void init() noexcept +{ +#if defined(_WIN32) + std::lock_guard lock(s_mutex); + + if (!s_initialized) { + s_initialized = true; + + WSADATA wsa; + WSAStartup(MAKEWORD(2, 2), &wsa); + + /* + * If SOCKET_WSA_NO_INIT is not set then the user + * must also call finish himself. + */ +#if !defined(SOCKET_NO_AUTO_INIT) + atexit(finish); +#endif + } +#endif +} + +void finish() noexcept +{ +#if defined(_WIN32) + WSACleanup(); +#endif +} + +std::string error(int errn) +{ +#if defined(_WIN32) + LPSTR str = nullptr; + std::string errmsg = "Unknown error"; + + FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + errn, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&str, 0, NULL); + + + if (str) { + errmsg = std::string(str); + LocalFree(str); + } + + return errmsg; +#else + return strerror(errn); +#endif +} + +std::string error() +{ +#if defined(_WIN32) + return error(WSAGetLastError()); +#else + return error(errno); +#endif +} + +/* }}} */ + +/* + * Error class + * ------------------------------------------------------------------ + */ + +/* {{{ Error */ + +Error::Error(Code code, std::string function) + : m_code{code} + , m_function{std::move(function)} + , m_error{error()} +{ +} + +Error::Error(Code code, std::string function, int n) + : m_code{code} + , m_function{std::move(function)} + , m_error{error(n)} +{ +} + +Error::Error(Code code, std::string function, std::string error) + : m_code{code} + , m_function{std::move(function)} + , m_error{std::move(error)} +{ +} + +/* }}} */ + +/* + * Predefine addressed to be used + * ------------------------------------------------------------------ + */ + +/* {{{ Addresses */ + +Ip::Ip(int domain) noexcept + : m_domain{domain} +{ + if (m_domain == AF_INET6) { + std::memset(&m_sin6, 0, sizeof (sockaddr_in6)); + } else { + std::memset(&m_sin, 0, sizeof (sockaddr_in)); + } +} + +Ip::Ip(const std::string &host, int port, int domain) + : m_domain{domain} +{ + if (host == "*") { + if (m_domain == AF_INET6) { + std::memset(&m_sin6, 0, sizeof (sockaddr_in6)); + + m_length = sizeof (sockaddr_in6); + m_sin6.sin6_addr = in6addr_any; + m_sin6.sin6_family = AF_INET6; + m_sin6.sin6_port = htons(port); + } else { + std::memset(&m_sin, 0, sizeof (sockaddr_in)); + + m_length = sizeof (sockaddr_in); + m_sin.sin_addr.s_addr = INADDR_ANY; + m_sin.sin_family = AF_INET; + m_sin.sin_port = htons(port); + } + } else { + addrinfo hints, *res; + + std::memset(&hints, 0, sizeof (addrinfo)); + hints.ai_family = domain; + + auto error = getaddrinfo(host.c_str(), std::to_string(port).c_str(), &hints, &res); + if (error != 0) { + throw Error{Error::System, "getaddrinfo", gai_strerror(error)}; + } + + if (m_domain == AF_INET6) { + std::memcpy(&m_sin6, res->ai_addr, res->ai_addrlen); + } else { + std::memcpy(&m_sin, res->ai_addr, res->ai_addrlen); + } + + m_length = res->ai_addrlen; + freeaddrinfo(res); + } +} + +Ip::Ip(const struct sockaddr_storage *ss, socklen_t length) + : m_length{length} +{ + if (ss->ss_family == AF_INET6) { + std::memcpy(&m_sin6, ss, length); + } else if (ss->ss_family == AF_INET) { + std::memcpy(&m_sin, ss, length); + } else { + throw std::invalid_argument{"invalid domain for Ip constructor"}; + } +} + +#if !defined(_WIN32) + +Local::Local(std::string path, bool rm) + : m_path{std::move(path)} +{ + /* Silently remove the file even if it fails */ + if (rm) { + ::remove(m_path.c_str()); + } + + /* Copy the path */ + std::memset(m_sun.sun_path, 0, sizeof (m_sun.sun_path)); + std::strncpy(m_sun.sun_path, m_path.c_str(), sizeof (m_sun.sun_path) - 1); + + /* Set the parameters */ + m_sun.sun_family = AF_UNIX; +} + +Local::Local(const sockaddr_storage &ss, socklen_t length) +{ + if (ss.ss_family == AF_UNIX) { + std::memcpy(&m_sun, &ss, length); + m_path = reinterpret_cast(m_sun).sun_path; + } else { + throw std::invalid_argument{"invalid domain for local constructor"}; + } +} + +#endif // !_WIN32 + +/* }}} */ + +const int FlagRead{1 << 0}; +const int FlagWrite{1 << 1}; + +std::vector Select::wait(const ListenerTable &table, int ms) +{ + timeval maxwait, *towait; + fd_set readset; + fd_set writeset; + + FD_ZERO(&readset); + FD_ZERO(&writeset); + + Handle max = 0; + + for (const auto &pair : table) { + if (pair.second & FlagRead) { + FD_SET(pair.first, &readset); + } + if (pair.second & FlagWrite) { + FD_SET(pair.first, &writeset); + } + + if (pair.first > max) { + max = pair.first; + } + } + + maxwait.tv_sec = 0; + maxwait.tv_usec = ms * 1000; + + // Set to nullptr for infinite timeout. + towait = (ms < 0) ? nullptr : &maxwait; + + auto error = ::select(max + 1, &readset, &writeset, nullptr, towait); + if (error == Failure) { + throw Error{Error::System, "select"}; + } + if (error == 0) { + throw Error{Error::Timeout, "select", "Timeout while listening"}; + } + + std::vector sockets; + + for (const auto &pair : table) { + if (FD_ISSET(pair.first, &readset)) { + sockets.push_back(ListenerStatus{pair.first, FlagRead}); + } + if (FD_ISSET(pair.first, &writeset)) { + sockets.push_back(ListenerStatus{pair.first, FlagWrite}); + } + } + + return sockets; +} + +/* -------------------------------------------------------- + * Poll implementation + * -------------------------------------------------------- */ + +#if defined(SOCKET_HAVE_POLL) + +#if defined(_WIN32) +# define poll WSAPoll +#endif + +short Poll::topoll(int flags) const noexcept +{ + short result(0); + + if (flags & FlagRead) { + result |= POLLIN; + } + if (flags & FlagWrite) { + result |= POLLOUT; + } + + return result; +} + +int Poll::toflags(short &event) const noexcept +{ + int flags = 0; + + /* + * Poll implementations mark the socket differently regarding + * the disconnection of a socket. + * + * At least, even if POLLHUP or POLLIN is set, recv() always + * return 0 so we mark the socket as readable. + */ + if ((event & POLLIN) || (event & POLLHUP)) { + flags |= FlagRead; + } + if (event & POLLOUT) { + flags |= FlagWrite; + } + + /* Reset event for safety */ + event = 0; + + return flags; +} + +void Poll::set(const ListenerTable &, Handle h, int flags, bool add) +{ + if (add) { + m_fds.push_back(pollfd{h, topoll(flags), 0}); + } else { + auto it = std::find_if(m_fds.begin(), m_fds.end(), [&] (const struct pollfd &pfd) { + return pfd.fd == h; + }); + + it->events |= topoll(flags); + } +} + +void Poll::unset(const ListenerTable &, Handle h, int flags, bool remove) +{ + auto it = std::find_if(m_fds.begin(), m_fds.end(), [&] (const struct pollfd &pfd) { + return pfd.fd == h; + }); + + if (remove) { + m_fds.erase(it); + } else { + it->events &= ~(topoll(flags)); + } +} + +std::vector Poll::wait(const ListenerTable &, int ms) +{ + auto result = poll(m_fds.data(), m_fds.size(), ms); + if (result == 0) { + throw Error{Error::Timeout, "select", "Timeout while listening"}; + } + if (result < 0) { + throw Error{Error::System, "poll"}; + } + + std::vector sockets; + for (auto &fd : m_fds) { + if (fd.revents != 0) { + sockets.push_back(ListenerStatus{fd.fd, toflags(fd.revents)}); + } + } + + return sockets; +} + +#endif // !SOCKET_HAVE_POLL + +/* -------------------------------------------------------- + * Epoll implementation + * -------------------------------------------------------- */ + +#if defined(SOCKET_HAVE_EPOLL) + +uint32_t Epoll::toepoll(int flags) const noexcept +{ + uint32_t events = 0; + + if (flags & FlagRead) { + events |= EPOLLIN; + } + if (flags & FlagWrite) { + events |= EPOLLOUT; + } + + return events; +} + +int Epoll::toflags(uint32_t events) const noexcept +{ + int flags = 0; + + if ((events & EPOLLIN) || (events & EPOLLHUP)) { + flags |= FlagRead; + } + if (events & EPOLLOUT) { + flags |= FlagWrite; + } + + return flags; +} + +void Epoll::update(Handle h, int op, int flags) +{ + struct epoll_event ev; + + std::memset(&ev, 0, sizeof (struct epoll_event)); + + ev.events = flags; + ev.data.fd = h; + + if (epoll_ctl(m_handle, op, h, &ev) < 0) { + throw Error{Error::System, "epoll_ctl"}; + } +} + +Epoll::Epoll() + : m_handle(epoll_create1(0)) +{ + if (m_handle < 0) { + throw Error{Error::System, "epoll_create"}; + } +} + +Epoll::~Epoll() +{ + close(m_handle); +} + +/* + * Add a new epoll_event or just update it. + */ +void Epoll::set(const ListenerTable &, Handle h, int flags, bool add) +{ + update(h, add ? EPOLL_CTL_ADD : EPOLL_CTL_MOD, toepoll(flags)); + + if (add) { + m_events.resize(m_events.size() + 1); + } +} + +/* + * Unset is a bit complicated case because SocketListener tells us which + * flag to remove but to update epoll descriptor we need to pass + * the effective flags that we want to be applied. + * + * So we put the same flags that are currently effective and remove the + * requested one. + */ +void Epoll::unset(const ListenerTable &table, Handle sc, int flags, bool remove) +{ + if (remove) { + update(sc, EPOLL_CTL_DEL, 0); + m_events.resize(m_events.size() - 1); + } else { + update(sc, EPOLL_CTL_MOD, table.at(sc) & ~(toepoll(flags))); + } +} + +std::vector Epoll::wait(const ListenerTable &, int ms) +{ + int ret = epoll_wait(m_handle, m_events.data(), m_events.size(), ms); + std::vector result; + + if (ret == 0) { + throw Error{Error::Timeout, "epoll_wait"}; + } + if (ret < 0) { + throw Error{Error::System, "epoll_wait"}; + } + + for (int i = 0; i < ret; ++i) { + result.push_back(ListenerStatus{m_events[i].data.fd, toflags(m_events[i].events)}); + } + + return result; +} + +#endif // !SOCKET_HAVE_EPOLL + +/* -------------------------------------------------------- + * Kqueue implementation + * -------------------------------------------------------- */ + +#if defined(SOCKET_HAVE_KQUEUE) + +Kqueue::Kqueue() + : m_handle(kqueue()) +{ + if (m_handle < 0) { + throw Error{Error::System, "kqueue"}; + } +} + +Kqueue::~Kqueue() +{ + close(m_handle); +} + +void Kqueue::update(Handle h, int filter, int flags) +{ + struct kevent ev; + + EV_SET(&ev, h, filter, flags, 0, 0, nullptr); + + if (kevent(m_handle, &ev, 1, nullptr, 0, nullptr) < 0) { + throw Error{Error::System, "kevent"}; + } +} + +void Kqueue::set(const ListenerTable &, Handle h, int flags, bool add) +{ + if (flags & FlagRead) { + update(h, EVFILT_READ, EV_ADD | EV_ENABLE); + } + if (flags & FlagWrite) { + update(h, EVFILT_WRITE, EV_ADD | EV_ENABLE); + } + + if (add) { + m_result.resize(m_result.size() + 1); + } +} + +void Kqueue::unset(const ListenerTable &, Handle h, int flags, bool remove) +{ + if (flags & FlagRead) { + update(h, EVFILT_READ, EV_DELETE); + } + if (flags & FlagWrite) { + update(h, EVFILT_WRITE, EV_DELETE); + } + + if (remove) { + m_result.resize(m_result.size() - 1); + } +} + +std::vector Kqueue::wait(const ListenerTable &table, int ms) +{ + std::vector sockets; + timespec ts = { 0, 0 }; + timespec *pts = (ms <= 0) ? nullptr : &ts; + + ts.tv_sec = ms / 1000; + ts.tv_nsec = (ms % 1000) * 1000000; + + int nevents = kevent(m_handle, nullptr, 0, &m_result[0], m_result.capacity(), pts); + + if (nevents == 0) { + throw Error{Error::Timeout, "kevent"}; + } + if (nevents < 0) { + throw Error{Error::System, "kevent"}; + } + + for (int i = 0; i < nevents; ++i) { + sockets.push_back(ListenerStatus{m_result[i].ident, m_result[i].filter == EVFILT_READ ? FlagRead : FlagWrite}); + } + + return sockets; +} + +#endif // !SOCKET_HAVE_KQUEUE + +} // !net diff -r b9c8b1792889 -r 2dbaf2fb03ef C++/modules/Socket/Sockets.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/modules/Socket/Sockets.h Thu Oct 22 20:00:51 2015 +0200 @@ -0,0 +1,1875 @@ +/* + * Sockets.h -- portable C++ socket wrappers + * + * Copyright (c) 2013-2015 David Demelier + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _SOCKETS_H_ +#define _SOCKETS_H_ + +/** + * @file Sockets.h + * @brief Portable socket abstraction + * + * User may set the following variables before compiling these files: + * + * - **SOCKET_NO_AUTO_INIT**:(bool) Set to false if you don't want Socket class to + * automatically calls net::init function and net::finish functions. + * + * # For Listener objects + * + * Feature detection, multiple implementations may be avaible, for example, + * Linux has poll, select and epoll. + * + * We assume that select(2) is always available. + * + * Of course, you can set the variables yourself if you test it with your + * build system. + * + * - **SOCKET_HAVE_POLL**: Defined on all BSD, Linux. Also defined on Windows + * if _WIN32_WINNT is set to 0x0600 or greater. + * - **SOCKET_HAVE_KQUEUE**: Defined on all BSD and Apple. + * - **SOCKET_HAVE_EPOLL**: Defined on Linux only. + */ + +#if defined(_WIN32) +# if _WIN32_WINNT >= 0x0600 +# define SOCKET_HAVE_POLL +# endif +#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) +# define SOCKET_HAVE_KQUEUE +# define SOCKET_HAVE_POLL +#elif defined(__linux__) +# define SOCKET_HAVE_EPOLL +# define SOCKET_HAVE_POLL +#endif + +/** + * This sets the default backend to use depending on the system. The following + * table summaries. + * + * The preference priority is ordered from left to right. + * + * | System | Backend | + * |---------------|-------------------------| + * | Linux | epoll(7) | + * | *BSD | kqueue(2) | + * | Windows | poll(2), select(2) | + * | Mac OS X | kqueue(2) | + */ + +#if defined(_WIN32) +# if defined(SOCKET_HAVE_POLL) +# define SOCKET_DEFAULT_BACKEND Poll +# else +# define SOCKET_DEFAULT_BACKEND Select +# endif +#elif defined(__linux__) +# include + +# define SOCKET_DEFAULT_BACKEND Epoll +#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__APPLE__) +# include +# include +# include + +# define SOCKET_DEFAULT_BACKEND Kqueue +#else +# define SOCKET_DEFAULT_BACKEND Select +#endif + +#if defined(SOCKET_HAVE_POLL) && !defined(_WIN32) +# include +#endif + +#if defined(_WIN32) +# include + +# include +# include +#else +# include + +# include +# include +# include +# include + +# include + +# include + +# include +# include +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace net { + +/* + * Portable constants + * ------------------------------------------------------------------ + * + * These constants are needed to check functions return codes, they are rarely needed in end user code. + */ + +/* {{{ Constants */ + +/* + * The following constants are defined differently from Unix + * to Windows. + */ +#if defined(_WIN32) + +/** + * Socket creation failure or invalidation. + */ +extern const Handle Invalid; + +/** + * Socket operation failure. + */ +extern const int Failure; + +#else + +/** + * Socket creation failure or invalidation. + */ +extern const int Invalid; + +/** + * Socket operation failure. + */ +extern const int Failure; + +#endif + +/* }}} */ + +/* + * Portable functions + * ------------------------------------------------------------------ + * + * The following free functions can be used to initialize the library or to get the last system error. + */ + +/* {{{ Functions */ + +/** + * Initialize the socket library. Except if you defined SOCKET_NO_AUTO_INIT, you don't need to call this + * function manually. + */ +void init() noexcept; + +/** + * Close the socket library. + */ +void finish() noexcept; + +/** + * Get the last socket system error. The error is set from errno or from + * WSAGetLastError on Windows. + * + * @return a string message + */ +std::string error(); + +/** + * Get the last system error. + * + * @param errn the error number (errno or WSAGetLastError) + * @return the error + */ +std::string error(int errn); + +/* }}} */ + +/* + * Portables types + * ------------------------------------------------------------------ + * + * The following types are defined differently between Unix and Windows. + */ + +/* {{{ Types */ + +#if defined(_WIN32) + +/** + * Socket type, SOCKET. + */ +using Handle = SOCKET; + +/** + * Argument to pass to set. + */ +using ConstArg = const char *; + +/** + * Argument to pass to get. + */ +using Arg = char *; + +#else + +/** + * Socket type, int. + */ +using Handle = int; + +/** + * Argument to pass to set. + */ +using ConstArg = const void *; + +/** + * Argument to pass to get. + */ +using Arg = void *; + +#endif + +/* }}} */ + +/* + * Error class + * ------------------------------------------------------------------ + * + * This is the main exception thrown on socket operations. + */ + +/* {{{ Error */ + +/** + * @class Error + * @brief Base class for sockets error + */ +class Error : public std::exception { +public: + /** + * @enum Code + * @brief Which kind of error + */ + enum Code { + WouldBlockRead, ///!< The operation would block for reading + WouldBlockWrite, ///!< The operation would block for writing + Timeout, ///!< The action did timeout + System ///!< There is a system error + }; + +private: + Code m_code; + std::string m_function; + std::string m_error; + +public: + /** + * Constructor that use the last system error. + * + * @param code which kind of error + * @param function the function name + */ + Error(Code code, std::string function); + + /** + * Constructor that use the system error set by the user. + * + * @param code which kind of error + * @param function the function name + * @param error the error + */ + Error(Code code, std::string function, int error); + + /** + * Constructor that set the error specified by the user. + * + * @param code which kind of error + * @param function the function name + * @param error the error + */ + Error(Code code, std::string function, std::string error); + + /** + * Get which function has triggered the error. + * + * @return the function name (e.g connect) + */ + inline const std::string &function() const noexcept + { + return m_function; + } + + /** + * The error code. + * + * @return the code + */ + inline Code code() const noexcept + { + return m_code; + } + + /** + * Get the error (only the error content). + * + * @return the error + */ + const char *what() const noexcept + { + return m_error.c_str(); + } +}; + +/* }}} */ + +/* + * Base Socket class + * ------------------------------------------------------------------ + * + * This base class has operations that are common to all types of sockets but you usually instanciate + * a SocketTcp or SocketUdp + */ + +/* {{{ Socket */ + +/** + * @class Socket + * @brief Base socket class for socket operations + */ +template +class Socket { +private: + Type m_type; + +protected: + /** + * The native handle. + */ + Handle m_handle{Invalid}; + +public: + /** + * This tries to create a socket. + */ + inline Socket() noexcept + : Socket{Address::domain(), Type::type(), 0} + { + } + + /** + * Construct a socket with an already created descriptor. + * + * @param handle the native descriptor + */ + explicit inline Socket(Handle handle) noexcept + : m_handle{handle} + { + } + + /** + * Create a socket handle. + * + * @param domain the domain AF_* + * @param type the type SOCK_* + * @param protocol the protocol + * @throw Error on failures + */ + Socket(int domain, int type, int protocol) + { +#if !defined(SOCKET_NO_AUTO_INIT) + init(); +#endif + m_handle = ::socket(domain, type, protocol); + + if (m_handle == Invalid) { + throw Error{Error::System, "socket"}; + } + } + + /** + * Copy constructor deleted. + */ + Socket(const Socket &) = delete; + + /** + * Transfer ownership from other to this. + * + * @param other the other socket + */ + inline Socket(Socket &&other) noexcept + : m_handle{other.m_handle} + { + /* Invalidate other */ + other.m_handle = -1; + } + + /** + * Default destructor. + */ + virtual ~Socket() + { + close(); + } + + /** + * Set an option for the socket. + * + * @param level the setting level + * @param name the name + * @param arg the value + * @throw Error on error + */ + template + inline void set(int level, int name, const Argument &arg) + { +#if defined(_WIN32) + if (setsockopt(m_handle, level, name, (ConstArg)&arg, sizeof (arg)) == Error) +#else + if (setsockopt(m_handle, level, name, (ConstArg)&arg, sizeof (arg)) < 0) +#endif + throw Error{Error::System, "set"}; + } + + /** + * Get an option for the socket. + * + * @param level the setting level + * @param name the name + * @throw Error on error + */ + template + inline Argument get(int level, int name) + { + Argument desired, result{}; + socklen_t size = sizeof (result); + +#if defined(_WIN32) + if (getsockopt(m_handle, level, name, (Arg)&desired, &size) == Error) +#else + if (getsockopt(m_handle, level, name, (Arg)&desired, &size) < 0) +#endif + throw Error{Error::System, "get"}; + + std::memcpy(&result, &desired, size); + + return result; + } + + /** + * Get the native handle. + * + * @return the handle + * @warning Not portable + */ + inline Handle handle() const noexcept + { + return m_handle; + } + + /** + * Set the blocking mode, if set to false, the socket will be marked + * **non-blocking**. + * + * @param block set to false to mark **non-blocking** + * @throw Error on any error + */ + void setBlockMode(bool block) + { +#if defined(O_NONBLOCK) && !defined(_WIN32) + int flags; + + if ((flags = fcntl(m_handle, F_GETFL, 0)) == -1) { + flags = 0; + } + + if (block) { + flags &= ~(O_NONBLOCK); + } else { + flags |= O_NONBLOCK; + } + + if (fcntl(m_handle, F_SETFL, flags) == Failure) { + throw Error{Error::System, "setBlockMode"}; + } +#else + unsigned long flags = (block) ? 0 : 1; + + if (ioctlsocket(m_handle, FIONBIO, &flags) == Failure) { + throw Error{Error::System, "setBlockMode"}; + } +#endif + } + + /** + * Bind using a native address. + * + * @param addr the address + * @param size the size + */ + inline void bind(const sockaddr *addr, socklen_t size) + { + if (::bind(m_handle, addr, size) == Failure) { + throw Error{Error::System, "bind"}; + } + } + + /** + * Overload that takes an address. + * + * @param address the address + * @throw Error on errors + */ + inline void bind(const Address &address) + { + bind(address.address(), address.length()); + } + + /** + * Listen for pending connection. + * + * @param max the maximum number + */ + inline void listen(int max = 128) + { + if (::listen(this->m_handle, max) == Failure) { + throw Error{Error::System, "listen"}; + } + } + + /** + * Connect to an address. + * + * @param address the address + * @throw Error on errors + */ + inline void connect(const Address &address) + { + m_type.connect(*this, address); + } + + /** + * Accept a pending connection. + * + * @param info the client address + * @return the new socket + * @throw Error on errors + */ + inline Socket accept(Address &info) + { + return m_type.accept(*this, info); + } + + /** + * Overloaded function without information. + * + * @return the new socket + * @throw Error on errors + */ + inline Socket accept() + { + Address dummy; + + return accept(dummy); + } + + /** + * Get the local name. This is a wrapper of getsockname(). + * + * @return the address + * @throw Error on failures + */ + Address address() const + { + // TODO: to reimplement + return {}; + } + + /** + * Receive some data. + * + * @param data the destination buffer + * @param length the buffer length + * @throw Error on error + */ + inline unsigned recv(void *data, unsigned length) + { + return m_type.recv(*this, data, length); + } + + /** + * Overloaded function. + * + * @param count the number of bytes to receive + * @return the string + * @throw Error on error + */ + inline std::string recv(unsigned count) + { + std::string result; + + result.resize(count); + auto n = recv(const_cast(result.data()), count); + result.resize(n); + + return result; + } + + /** + * Send some data. + * + * @param data the data buffer + * @param length the buffer length + * @throw Error on error + */ + inline unsigned send(const void *data, unsigned length) + { + return m_type.send(*this, data, length); + } + + /** + * Overloaded function. + * + * @param data the string to send + * @return the number of bytes sent + * @throw Error on error + */ + inline unsigned send(const std::string &data) + { + return send(data.c_str(), data.size()); + } + + /** + * Send data to an end point. + * + * @param data the buffer + * @param length the buffer length + * @param address the client address + * @return the number of bytes sent + * @throw Error on error + */ + inline unsigned sendto(const void *data, unsigned length, const Address &address) + { + return m_type.sendto(*this, data, length, address); + } + + /** + * Overloaded function. + * + * @param data the data + * @param address the address + * @return the number of bytes sent + * @throw Error on error + */ + inline unsigned sendto(const std::string &data, const Address &address) + { + return sendto(data.c_str(), data.length(), address); + } + + /** + * Receive data from an end point. + * + * @param data the destination buffer + * @param length the buffer length + * @param info the client information + * @return the number of bytes received + * @throw Error on error + */ + unsigned recvfrom(void *data, unsigned length, Address &info) + { + return m_type.recvfrom(*this, data, length, info); + } + + /** + * Overloaded function. + * + * @param count the maximum number of bytes to receive + * @param info the client information + * @return the string + * @throw Error on error + */ + inline std::string recvfrom(unsigned count, Address &info) + { + std::string result; + + result.resize(count); + auto n = recvfrom(const_cast(result.data()), count, info); + result.resize(n); + + return result; + } + + /** + * Overloaded function. + * + * @param count the number of bytes to read + * @return the string + * @throw Error on errors + */ + inline std::string recvfrom(unsigned count) + { + Address dummy; + + return recvfrom(count, dummy); + } + + /** + * Close the socket. + * + * Automatically called from the destructor. + */ + virtual void close() + { + if (m_handle != Invalid) { +#if defined(_WIN32) + ::closesocket(m_handle); +#else + ::close(m_handle); +#endif + m_handle = Invalid; + } + } + + /** + * Assignment operator forbidden. + * + * @return *this + */ + Socket &operator=(const Socket &) = delete; + + /** + * Transfer ownership from other to this. The other socket is left + * invalid and will not be closed. + * + * @param other the other socket + * @return this + */ + Socket &operator=(Socket &&other) noexcept + { + m_handle = other.m_handle; + + /* Invalidate other */ + other.m_handle = Invalid; + + return *this; + } +}; + +/** + * Compare two sockets. + * + * @param s1 the first socket + * @param s2 the second socket + * @return true if they equals + */ +template +bool operator==(const Socket &s1, const Socket &s2) +{ + return s1.handle() == s2.handle(); +} + +/** + * Compare two sockets. + * + * @param s1 the first socket + * @param s2 the second socket + * @return true if they are different + */ +template +bool operator!=(const Socket &s1, const Socket &s2) +{ + return s1.handle() != s2.handle(); +} + +/** + * Compare two sockets. + * + * @param s1 the first socket + * @param s2 the second socket + * @return true if s1 < s2 + */ +template +bool operator<(const Socket &s1, const Socket &s2) +{ + return s1.handle() < s2.handle(); +} + +/** + * Compare two sockets. + * + * @param s1 the first socket + * @param s2 the second socket + * @return true if s1 > s2 + */ +template +bool operator>(const Socket &s1, const Socket &s2) +{ + return s1.handle() > s2.handle(); +} + +/** + * Compare two sockets. + * + * @param s1 the first socket + * @param s2 the second socket + * @return true if s1 <= s2 + */ +template +bool operator<=(const Socket &s1, const Socket &s2) +{ + return s1.handle() <= s2.handle(); +} + +/** + * Compare two sockets. + * + * @param s1 the first socket + * @param s2 the second socket + * @return true if s1 >= s2 + */ +template +bool operator>=(const Socket &s1, const Socket &s2) +{ + return s1.handle() >= s2.handle(); +} + +/* }}} */ + +/* + * Predefine addressed to be used + * ------------------------------------------------------------------ + * + * - Ipv6 + * - Ipv4 + * - Local + */ + +/* {{{ Addresses */ + +/** + * @class Ip + * @brief Base class for IPv6 and IPv4, don't use it directly + */ +class Ip { +private: + friend class Ipv6; + friend class Ipv4; + + union { + sockaddr_in m_sin; + sockaddr_in6 m_sin6; + }; + + socklen_t m_length{0}; + int m_domain{AF_INET}; + + Ip(int domain) noexcept; + + Ip(const std::string &host, int port, int domain); + + Ip(const struct sockaddr_storage *ss, socklen_t length); + +public: + /** + * Return the underlying address, either sockaddr_in6 or sockaddr_in. + * + * @return the address + */ + inline const sockaddr *address() const noexcept + { + if (m_domain == AF_INET6) { + return reinterpret_cast(&m_sin6); + } + + return reinterpret_cast(&m_sin); + } + + /** + * Return the underlying address length. + * + * @return the length + */ + inline socklen_t length() const noexcept + { + return m_length; + } +}; + +/** + * @class Ipv6 + * @brief Use IPv6 address + */ +class Ipv6 : public Ip { +public: + /** + * Get the domain AF_INET6. + * + * @return AF_INET6 + */ + static inline int domain() noexcept + { + return AF_INET6; + } + + /** + * Construct an empty address. + */ + inline Ipv6() noexcept + : Ip{AF_INET6} + { + } + + /** + * Construct an address on a specific host. + * + * @param host the host ("*" for any) + * @param port the port + * @throw Error on errors + */ + inline Ipv6(const std::string &host, int port) + : Ip{host, port, AF_INET6} + { + } + + /** + * Construct an address from a storage. + * + * @param ss the storage + * @param length the length + */ + inline Ipv6(const struct sockaddr_storage *ss, socklen_t length) + : Ip{ss, length} + { + } +}; + +/** + * @class Ipv4 + * @brief Use IPv4 address + */ +class Ipv4 : public Ip { +public: + /** + * Get the domain AF_INET. + * + * @return AF_INET + */ + static inline int domain() noexcept + { + return AF_INET; + } + + /** + * Construct an empty address. + */ + inline Ipv4() noexcept + : Ip{AF_INET} + { + } + + /** + * Construct an address on a specific host. + * + * @param host the host ("*" for any) + * @param port the port + * @throw Error on errors + */ + inline Ipv4(const std::string &host, int port) + : Ip{host, port, AF_INET} + { + } + + /** + * Construct an address from a storage. + * + * @param ss the storage + * @param length the length + */ + inline Ipv4(const struct sockaddr_storage *ss, socklen_t length) + : Ip{ss, length} + { + } +}; + +#if !defined(_WIN32) + +/** + * @class Local + * @brief unix family sockets + * + * Create an address to a specific path. Only available on Unix. + */ +class Local { +private: + sockaddr_un m_sun; + std::string m_path; + +public: + /** + * Get the domain AF_LOCAL. + * + * @return AF_LOCAL + */ + static inline int domain() noexcept + { + return AF_LOCAL; + } + + /** + * Default constructor. + */ + Local() = default; + + /** + * Construct an address to a path. + * + * @param path the path + * @param rm remove the file before (default: false) + */ + Local(std::string path, bool rm = false); + + /** + * Construct an unix address from a storage address. + * + * @param ss the storage + * @param length the length + */ + Local(const sockaddr_storage &ss, socklen_t length); + + /** + * Get the sockaddr_un. + * + * @return the address + */ + inline const sockaddr *address() const noexcept + { + return reinterpret_cast(&m_sun); + } + + /** + * Get the address length. + * + * @return the length + */ + inline socklen_t length() const noexcept + { +#if defined(SOCKET_HAVE_SUN_LEN) + return SUN_LEN(&m_sun); +#else + return sizeof (m_sun); +#endif + } +}; + +#endif // !_WIN32 + +/* }}} */ + +/* + * Predefined types + * ------------------------------------------------------------------ + * + * - Tcp, for standard stream connections + * - Udp, for standard datagram connection + */ + +/* {{{ Types */ + +/* {{{ Tcp */ + +/** + * @class Tcp + * @brief Clear TCP implementation. + */ +class Tcp { +public: + /** + * Socket type. + * + * @return SOCK_STREAM + */ + static inline int type() noexcept + { + return SOCK_STREAM; + } + + /** + * Accept a clear client. Wrapper of accept(2). + * + * @param sc the socket + * @param address the address destination + * @return the socket + * @throw Error on errors + */ + template + Socket accept(Socket &sc, Address &address) + { + Handle handle; + sockaddr_storage storage; + socklen_t addrlen = sizeof (sockaddr_storage); + + handle = ::accept(sc.handle(), reinterpret_cast(&storage), &addrlen); + + if (handle == Invalid) { +#if defined(_WIN32) + int error = WSAGetLastError(); + + if (error == WSAEWOULDBLOCK) { + throw Error{Error::WouldBlockRead, "accept", error}; + } + + throw Error{Error::System, "accept", error}; +#else + if (errno == EAGAIN || errno == EWOULDBLOCK) { + throw Error{Error::WouldBlockRead, "accept"}; + } + + throw Error{Error::System, "accept"}; +#endif + } + + address = Address{&storage, addrlen}; + + return Socket{handle}; + } + + /** + * Connect to the end point. Wrapper for connect(2). + * + * @param sc the socket + * @param address the address + * @throw Error on errors + */ + template + void connect(Socket &sc, const Address &address) + { + if (::connect(sc.handle(), address.address(), address.length()) == Failure) { + /* + * Determine if the error comes from a non-blocking connect that cannot be + * accomplished yet. + */ +#if defined(_WIN32) + int error = WSAGetLastError(); + + if (error == WSAEWOULDBLOCK) { + throw Error{Error::WouldBlockWrite, "connect", error}; + } + + throw Error{Error::System, "connect", error}; +#else + if (errno == EINPROGRESS) { + throw Error{Error::WouldBlockWrite, "connect"}; + } + + throw Error{Error::System, "connect"}; + } +#endif + } + + /** + * Receive data. Wrapper of recv(2). + * + * @param sc the socket + * @param data the destination + * @param length the destination length + * @return the number of bytes read + * @throw Error on errors + */ + template + unsigned recv(Socket &sc, void *data, unsigned length) + { + int nbread; + + nbread = ::recv(sc.handle(), (Arg)data, length, 0); + if (nbread == Failure) { +#if defined(_WIN32) + int error = WSAGetLastError(); + + if (error == WSAEWOULDBLOCK) { + throw Error{Error::WouldBlockRead, "recv", error}; + } + + throw Error{Error::System, "recv", error}; +#else + if (errno == EAGAIN || errno == EWOULDBLOCK) { + throw Error{Error::WouldBlockRead, "recv"}; + } + + throw Error{Error::System, "recv"}; +#endif + } + + return static_cast(nbread); + } + + /** + * Send some data. Wrapper for send(2). + * + * @param sc the socket + * @param data the buffer to send + * @param length the buffer length + * @return the number of bytes sent + * @throw Error on errors + */ + template + unsigned send(Socket &sc, const void *data, unsigned length) + { + int nbsent; + + nbsent = ::send(sc.handle(), (ConstArg)data, length, 0); + if (nbsent == Failure) { +#if defined(_WIN32) + int error = WSAGetLastError(); + + if (error == WSAEWOULDBLOCK) { + throw Error{Error::WouldBlockWrite, "send", error}; + } + + throw Error{Error::System, "send", error}; +#else + if (errno == EAGAIN || errno == EWOULDBLOCK) { + throw Error{Error::WouldBlockWrite, "send"}; + } + + throw Error{Error::System, "send"}; +#endif + } + + return static_cast(nbsent); + } +}; + +/* }}} */ + +/* {{{ Udp */ + +/** + * @class Udp + * @brief Clear UDP type. + * + * This class is the basic implementation of UDP sockets. + */ +class Udp { +public: + /** + * Socket type. + * + * @return SOCK_DGRAM + */ + static inline int type() noexcept + { + return SOCK_DGRAM; + } + + /** + * Receive data from an end point. + * + * @param sc the socket + * @param data the destination buffer + * @param length the buffer length + * @param info the client information + * @return the number of bytes received + * @throw Error on error + */ + template + unsigned recvfrom(Socket &sc, void *data, unsigned length, Address &info) + { + int nbread; + + /* Store information */ + sockaddr_storage address; + socklen_t addrlen = sizeof (struct sockaddr_storage); + + nbread = ::recvfrom(sc.handle(), (Arg)data, length, 0, reinterpret_cast(&address), &addrlen); + info = Address{&address, addrlen}; + + if (nbread == Failure) { +#if defined(_WIN32) + int error = WSAGetLastError(); + + if (error == WSAEWOULDBLOCK) { + throw Error{Error::WouldBlockRead, "recvfrom", error}; + } + + throw Error{Error::System, "recvfrom", error}; +#else + if (errno == EAGAIN || errno == EWOULDBLOCK) { + throw Error{Error::WouldBlockRead, "recvfrom"}; + } + + throw Error{Error::System, "recvfrom"}; +#endif + } + + return static_cast(nbread); + } + + /** + * Send data to an end point. + * + * @param sc the socket + * @param data the buffer + * @param length the buffer length + * @param address the client address + * @return the number of bytes sent + * @throw Error on error + */ + template + unsigned sendto(Socket &sc, const void *data, unsigned length, const Address &address) + { + int nbsent; + + nbsent = ::sendto(sc.handle(), (ConstArg)data, length, 0, address.address(), address.length()); + if (nbsent == Failure) { +#if defined(_WIN32) + int error = WSAGetLastError(); + + if (error == WSAEWOULDBLOCK) { + throw Error{Error::WouldBlockWrite, "sendto", error}; + } + + throw Error{Error::System, "sendto", error}; +#else + if (errno == EAGAIN || errno == EWOULDBLOCK) { + throw Error{Error::WouldBlockWrite, "sendto"}; + } + + throw Error{Error::System, "sendto"}; +#endif + } + + return static_cast(nbsent); + } +}; + +/* }}} */ + +/* }}} */ + +/* + * Convenient helpers + * ------------------------------------------------------------------ + * + * - SocketTcp
, for TCP sockets, + * - SocketUdp
, for UDP sockets + */ + +/* {{{ Helpers */ + +/** + * Helper to create TCP sockets. + */ +template +using SocketTcp = Socket; + +/** + * Helper to create UDP sockets. + */ +template +using SocketUdp = Socket; + +/* }}} */ + +/* + * Select wrapper + * ------------------------------------------------------------------ + * + * Wrapper for select(2) and other various implementations. + */ + +/* {{{ Listener */ + +/** + * @struct ListenerStatus + * @brief Result of polling + * + * Result of a select call, returns the first ready socket found with its + * flags. + */ +class ListenerStatus { +public: + Handle socket; //!< which socket is ready + int flags; //!< the flags +}; + +/** + * Table used in the socket listener to store which sockets have been + * set in which directions. + */ +using ListenerTable = std::map; + +/** + * @class Select + * @brief Implements select(2) + * + * This class is the fallback of any other method, it is not preferred at all for many reasons. + */ +class Select { +public: + /** + * Backend identifier + */ + inline const char *name() const noexcept + { + return "select"; + } + + /** + * No-op, uses the ListenerTable directly. + */ + inline void set(const ListenerTable &, Handle, int, bool) noexcept {} + + /** + * No-op, uses the ListenerTable directly. + */ + inline void unset(const ListenerTable &, Handle, int, bool) noexcept {} + + /** + * Return the sockets + */ + std::vector wait(const ListenerTable &table, int ms); +}; + +#if defined(SOCKET_HAVE_POLL) + +/** + * @class Poll + * @brief Implements poll(2) + * + * Poll is widely supported and is better than select(2). It is still not the + * best option as selecting the sockets is O(n). + */ +class Poll { +private: + std::vector m_fds; + + short topoll(int flags) const noexcept; + int toflags(short &event) const noexcept; + +public: + void set(const ListenerTable &, Handle sc, int flags, bool add); + void unset(const ListenerTable &, Handle sc, int flags, bool remove); + std::vector wait(const ListenerTable &, int ms); + + /** + * Backend identifier + */ + inline const char *name() const noexcept + { + return "poll"; + } +}; + +#endif + +#if defined(SOCKET_HAVE_EPOLL) + +class Epoll { +private: + int m_handle; + std::vector m_events; + + Epoll(const Epoll &) = delete; + Epoll &operator=(const Epoll &) = delete; + Epoll(const Epoll &&) = delete; + Epoll &operator=(const Epoll &&) = delete; + + uint32_t toepoll(int flags) const noexcept; + int toflags(uint32_t events) const noexcept; + void update(Handle sc, int op, int flags); + +public: + Epoll(); + ~Epoll(); + void set(const ListenerTable &, Handle sc, int flags, bool add); + void unset(const ListenerTable &, Handle sc, int flags, bool remove); + std::vector wait(const ListenerTable &table, int ms); + + /** + * Backend identifier + */ + inline const char *name() const noexcept + { + return "epoll"; + } +}; + +#endif + +#if defined(SOCKET_HAVE_KQUEUE) + +/** + * @class Kqueue + * @brief Implements kqueue(2) + * + * This implementation is available on all BSD and Mac OS X. It is better than + * poll(2) because it's O(1), however it's a bit more memory consuming. + */ +class Kqueue { +private: + std::vector m_result; + int m_handle; + + Kqueue(const Kqueue &) = delete; + Kqueue &operator=(const Kqueue &) = delete; + Kqueue(Kqueue &&) = delete; + Kqueue &operator=(Kqueue &&) = delete; + + void update(Handle sc, int filter, int flags); + +public: + Kqueue(); + ~Kqueue(); + + void set(const ListenerTable &, Handle sc, int flags, bool add); + void unset(const ListenerTable &, Handle sc, int flags, bool remove); + std::vector wait(const ListenerTable &, int ms); + + /** + * Backend identifier + */ + inline const char *name() const noexcept + { + return "kqueue"; + } +}; + +#endif + +/** + * Mark the socket for read operation. + */ +extern const int FlagRead; + +/** + * Mark the socket for write operation. + */ +extern const int FlagWrite; + +/** + * @class Listener + * @brief Synchronous multiplexing + * + * Convenient wrapper around the select() system call. + * + * This class is implemented using a bridge pattern to allow different uses + * of listener implementation. + * + * You should not reinstanciate a new SocketListener at each iteartion of your + * main loop as it can be extremely costly. Instead use the same listener that + * you can safely modify on the fly. + * + * Currently, poll, epoll, select and kqueue are available. + * + * To implement the backend, the following functions must be available: + * + * # Set + * + * @code + * void set(const ListenerTable &, const Handle &sc, int flags, bool add); + * @endcode + * + * This function, takes the socket to be added and the flags. The flags are + * always guaranteed to be correct and the function will never be called twice + * even if the user tries to set the same flag again. + * + * An optional add argument is added for backends which needs to do different + * operation depending if the socket was already set before or if it is the + * first time (e.g EPOLL_CTL_ADD vs EPOLL_CTL_MOD for epoll(7). + * + * # Unset + * + * @code + * void unset(const ListenerTable &, const Handle &sc, int flags, bool remove); + * @endcode + * + * Like set, this function is only called if the flags are actually set and will + * not be called multiple times. + * + * Also like set, an optional remove argument is set if the socket is being + * completely removed (e.g no more flags are set for this socket). + * + * # Wait + * + * @code + * std::vector wait(const ListenerTable &, int ms); + * @encode + * + * Wait for the sockets to be ready with the specified milliseconds. Must return a list of ListenerStatus, + * may throw any exceptions. + * + * # Name + * + * @code + * inline const char *name() const noexcept + * @endcode + * + * Returns the backend name. Usually the class in lower case. + */ +template +class Listener { +public: + +private: + Backend m_backend; + ListenerTable m_table; + +public: + /** + * Construct an empty listener. + */ + Listener() = default; + + /** + * Get the backend. + * + * @return the backend + */ + inline const Backend &backend() const noexcept + { + return m_backend; + } + + /** + * Get the non-modifiable table. + * + * @return the table + */ + inline const ListenerTable &table() const noexcept + { + return m_table; + } + + /** + * Overloaded function. + * + * @return the iterator + */ + inline ListenerTable::const_iterator begin() const noexcept + { + return m_table.begin(); + } + + /** + * Overloaded function. + * + * @return the iterator + */ + inline ListenerTable::const_iterator cbegin() const noexcept + { + return m_table.cbegin(); + } + + /** + * Overloaded function. + * + * @return the iterator + */ + inline ListenerTable::const_iterator end() const noexcept + { + return m_table.end(); + } + + /** + * Overloaded function. + * + * @return the iterator + */ + inline ListenerTable::const_iterator cend() const noexcept + { + return m_table.cend(); + } + + /** + * Add or update a socket to the listener. + * + * If the socket is already placed with the appropriate flags, the + * function is a no-op. + * + * If incorrect flags are passed, the function does nothing. + * + * @param sc the socket + * @param flags (may be OR'ed) + * @throw SocketError if the backend failed to set + */ + void set(Handle sc, int flags) + { + /* Invalid or useless flags */ + if (flags == 0 || flags > 0x3) + return; + + auto it = m_table.find(sc); + + /* + * Do not update the table if the backend failed to add + * or update. + */ + if (it == m_table.end()) { + m_backend.set(m_table, sc, flags, true); + m_table.emplace(sc, flags); + } else { + if ((flags & FlagRead) && (it->second & FlagRead)) { + flags &= ~(FlagRead); + } + if ((flags & FlagWrite) && (it->second & FlagWrite)) { + flags &= ~(FlagWrite); + } + + /* Still need a call? */ + if (flags != 0) { + m_backend.set(m_table, sc, flags, false); + it->second |= flags; + } + } + } + + /** + * Unset a socket from the listener, only the flags is removed + * unless the two flagss are requested. + * + * For example, if you added a socket for both reading and writing, + * unsetting the write flags will keep the socket for reading. + * + * @param sc the socket + * @param flags the flags (may be OR'ed) + * @see remove + */ + void unset(Handle sc, int flags) + { + auto it = m_table.find(sc); + + /* Invalid or useless flags */ + if (flags == 0 || flags > 0x3 || it == m_table.end()) + return; + + /* + * Like set, do not update if the socket is already at the appropriate + * state. + */ + if ((flags & FlagRead) && !(it->second & FlagRead)) { + flags &= ~(FlagRead); + } + if ((flags & FlagWrite) && !(it->second & FlagWrite)) { + flags &= ~(FlagWrite); + } + + if (flags != 0) { + /* Determine if it's a complete removal */ + bool removal = ((it->second) & ~(flags)) == 0; + + m_backend.unset(m_table, sc, flags, removal); + + if (removal) { + m_table.erase(it); + } else { + it->second.second &= ~(flags); + } + } + } + + /** + * Remove completely the socket from the listener. + * + * It is a shorthand for unset(sc, FlagRead | FlagWrite); + * + * @param sc the socket + */ + inline void remove(Handle sc) + { + unset(sc, FlagRead | FlagWrite); + } + + /** + * Remove all sockets. + */ + inline void clear() + { + while (!m_table.empty()) { + remove(m_table.begin()->second.first); + } + } + + /** + * Get the number of sockets in the listener. + */ + inline ListenerTable::size_type size() const noexcept + { + return m_table.size(); + } + + /** + * Select a socket. Waits for a specific amount of time specified as the duration. + * + * @param duration the duration + * @return the socket ready + */ + template + inline ListenerStatus wait(const std::chrono::duration &duration) + { + auto cvt = std::chrono::duration_cast(duration); + + return m_backend.wait(m_table, cvt.count())[0]; + } + + /** + * Overload with milliseconds. + * + * @param timeout the optional timeout in milliseconds + * @return the socket ready + */ + inline ListenerStatus wait(int timeout = -1) + { + return wait(std::chrono::milliseconds(timeout)); + } + + /** + * Select multiple sockets. + * + * @param duration the duration + * @return the socket ready + */ + template + inline std::vector waitMultiple(const std::chrono::duration &duration) + { + auto cvt = std::chrono::duration_cast(duration); + + return m_backend.wait(m_table, cvt.count()); + } + + /** + * Overload with milliseconds. + * + * @return the socket ready + */ + inline std::vector waitMultiple(int timeout = -1) + { + return waitMultiple(std::chrono::milliseconds(timeout)); + } +}; + +/* }}} */ + +} // !net + +#endif // !_SOCKETS_H_