Mercurial > code
view C++/modules/Socket/SocketListener.cpp @ 346:d235553e47a9
Socket:
- SocketListener is now implemented as a template and use the backend as the
template parameter. No more virtual implementation,
- Improve backend detection and set the default backend implementation
automatically,
- Rename Socket::init to Socket::initialize for non-Windows systems,
- Rename SocketListener::(select|selectMultiple) to wait|waitMultiple
- Coming up soon: kqueue and epoll backends.
author | David Demelier <markand@malikania.fr> |
---|---|
date | Thu, 02 Apr 2015 17:32:51 +0200 |
parents | 486767e1d165 |
children | b8d6b7f0bec4 |
line wrap: on
line source
/* * SocketListener.cpp -- portable select() wrapper * * Copyright (c) 2013, 2014 David Demelier <markand@malikania.fr> * * 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. */ #include <algorithm> #include <map> #include <set> #include <utility> #include <vector> #include "SocketListener.h" /* -------------------------------------------------------- * Select implementation * -------------------------------------------------------- */ namespace backend { void Select::set(Socket &s, int direction) { if (m_table.count(s.handle()) > 0) { m_table.at(s.handle()).second |= direction; } else { m_table.insert({s.handle(), {s, direction}}); } } void Select::unset(Socket &s, int direction) { if (m_table.count(s.handle()) != 0) { m_table.at(s.handle()).second &= ~(direction); // If no read, no write is requested, remove it if (m_table.at(s.handle()).second == 0) { m_table.erase(s.handle()); } } } void Select::remove(Socket &sc) { m_table.erase(sc.handle()); } void Select::clear() { m_table.clear(); } SocketStatus Select::wait(int ms) { auto result = waitMultiple(ms); if (result.size() == 0) { throw SocketError(SocketError::System, "select", "No socket found"); } return result[0]; } std::vector<SocketStatus> Select::waitMultiple(int ms) { timeval maxwait, *towait; fd_set readset; fd_set writeset; FD_ZERO(&readset); FD_ZERO(&writeset); Socket::Handle max = 0; for (auto &s : m_table) { if (s.second.second & SocketListener::Read) { FD_SET(s.first, &readset); } if (s.second.second & SocketListener::Write) { FD_SET(s.first, &writeset); } if (s.first > max) { max = s.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 == Socket::Error) { throw SocketError(SocketError::System, "select"); } if (error == 0) { throw SocketError(SocketError::Timeout, "select", "Timeout while listening"); } std::vector<SocketStatus> sockets; for (auto &c : m_table) { if (FD_ISSET(c.first, &readset)) { sockets.push_back({ c.second.first, SocketListener::Read }); } if (FD_ISSET(c.first, &writeset)) { sockets.push_back({ c.second.first, SocketListener::Write }); } } return sockets; } /* -------------------------------------------------------- * Poll implementation * -------------------------------------------------------- */ #if defined(SOCKET_HAVE_POLL) #if defined(_WIN32) # define poll WSAPoll #endif short Poll::topoll(int direction) const noexcept { short result(0); if (direction & SocketListener::Read) { result |= POLLIN; } if (direction & SocketListener::Write) { result |= POLLOUT; } return result; } int Poll::todirection(short event) const noexcept { int direction{}; /* * 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)) { direction |= SocketListener::Read; } if (event & POLLOUT) { direction |= SocketListener::Write; } return direction; } void Poll::set(Socket &s, int direction) override { auto it = std::find_if(m_fds.begin(), m_fds.end(), [&] (const auto &pfd) { return pfd.fd == s.handle(); }); // If found, add the new direction, otherwise add a new socket if (it != m_fds.end()) { it->events |= topoll(direction); } else { m_lookup.insert({s.handle(), s}); m_fds.push_back({ s.handle(), topoll(direction), 0 }); } } void Poll::unset(Socket &s, int direction) override { for (auto i = m_fds.begin(); i != m_fds.end();) { if (i->fd == s.handle()) { i->events &= ~(topoll(direction)); if (i->events == 0) { m_lookup.erase(i->fd); i = m_fds.erase(i); } else { ++i; } } else { ++i; } } } void Poll::remove(Socket &s) override { auto it = std::find_if(m_fds.begin(), m_fds.end(), [&] (const auto &pfd) { return pfd.fd == s.handle(); }); if (it != m_fds.end()) { m_fds.erase(it); m_lookup.erase(s.handle()); } } void Poll::clear() override { m_fds.clear(); m_lookup.clear(); } SocketStatus Poll::wait(int ms) override { auto result = poll(m_fds.data(), m_fds.size(), ms); if (result == 0) { throw SocketError(SocketError::Timeout, "select", "Timeout while listening"); } if (result < 0) { throw SocketError(SocketError::System, "poll"); } for (auto &fd : m_fds) { if (fd.revents != 0) { return { m_lookup.at(fd.fd), todirection(fd.revents) }; } } throw SocketError(SocketError::System, "select", "No socket found"); } std::vector<SocketStatus> Poll::waitMultiple(int ms) override { auto result = poll(m_fds.data(), m_fds.size(), ms); if (result == 0) { throw SocketError(SocketError::Timeout, "select", "Timeout while listening"); } if (result < 0) { throw SocketError(SocketError::System, "poll"); } std::vector<SocketStatus> sockets; for (auto &fd : m_fds) { if (fd.revents != 0) { sockets.push_back({ m_lookup.at(fd.fd), todirection(fd.revents) }); } } return sockets; } #endif // !_SOCKET_HAVE_POLL } // !backend