view C++/modules/Socket/SocketListener.h @ 347:b8d6b7f0bec4

Socket: disable temporarily Kqueue and Epoll since they are not implemented
author David Demelier <markand@malikania.fr>
date Thu, 02 Apr 2015 19:06:53 +0200
parents d235553e47a9
children 5a1ec6603230
line wrap: on
line source

/*
 * SocketListener.h -- 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.
 */

#ifndef _SOCKET_LISTENER_NG_H_
#define _SOCKET_LISTENER_NG_H_

/**
 * 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(2)                |
 * | *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 backend::Poll
#  else
#    define SOCKET_DEFAULT_BACKEND backend::Select
#  endif
#elif defined(__linux__)
   // TODO NOT READY YET
   //#  define SOCKET_DEFAULT_BACKEND backend::Epoll
#  define SOCKET_DEFAULT_BACKEND backend::Poll
#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
   // TODO NOT READY YET
   //#  define SOCKET_DEFAULT_BACKEND backend::Kqueue
#  define SOCKET_DEFAULT_BACKEND backend::Poll
#else
#  define SOCKET_DEFAULT_BACKEND backend::Select
#endif

#include <chrono>
#include <functional>
#include <initializer_list>
#include <map>
#include <memory>
#include <utility>
#include <vector>

#include "Socket.h"

#if defined(SOCKET_HAVE_POLL)
#  include <poll.h>
#endif

/**
 * @struct SocketStatus
 * @brief The SocketStatus struct
 *
 * Result of a select call, returns the first ready socket found with its
 * flags.
 */
class SocketStatus {
public:
	Socket	&socket;		//!< which socket is ready
	int	 flags;			//!< the flags
};

namespace backend {

/**
 * @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 {
private:
	std::map<Socket::Handle, std::pair<std::reference_wrapper<Socket>, int>> m_table;

public:
	void set(Socket &s, int direction);
	void unset(Socket &s, int direction);
	void remove(Socket &sc);
	void clear();
	SocketStatus wait(int ms);
	std::vector<SocketStatus> waitMultiple(int ms);
};

#if defined(SOCKET_HAVE_POLL)

class Poll {
private:
	std::vector<pollfd> m_fds;
	std::map<Socket::Handle, std::reference_wrapper<Socket>> m_lookup;

	short topoll(int direction) const noexcept;
	int todirection(short event) const noexcept;

public:
	void set(Socket &s, int direction);
	void unset(Socket &s, int direction);
	void remove(Socket &sc);
	void clear();
	SocketStatus wait(int ms);
	std::vector<SocketStatus> waitMultiple(int ms);
};

#endif

#if defined(SOCKET_HAVE_EPOLL)

class Epoll {
	// TODO
};

#endif

#if defined(SOCKET_HAVE_KQUEUE)

class Kqueue {
	// TODO
};

#endif

} // !backend

/**
 * @class SocketListenerBase
 * @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.
 *
 * Currently, poll and select() are available.
 *
 * This wrappers takes abstract sockets as non-const reference but it does not
 * own them so you must take care that sockets are still alive until the
 * SocketListener is destroyed.
 */
template <typename Backend = SOCKET_DEFAULT_BACKEND>
class SocketListenerBase final {
public:
	static const int Read;
	static const int Write;

	using Map = std::map<std::reference_wrapper<Socket>, int>;

private:
	Backend m_backend;
	Map m_map;

public:
#if 0
	/**
	 * Move constructor.
	 *
	 * @param other the other object
	 */
	SocketListener(SocketListener &&other) = default;

	/**
	 * Move operator.
	 *
	 * @param other the other object
	 * @return this
	 */
	SocketListener &operator=(SocketListener &&other) = default;
#endif
	inline SocketListenerBase()
	{
	}

	/**
	 * Create a listener from a list of sockets.
	 *
	 * @param list the list
	 */
	inline SocketListenerBase(std::initializer_list<std::pair<std::reference_wrapper<Socket>, int>> list)
	{
		for (const auto &p : list) {
			set(p.first, p.second);
		}
	}

	/**
	 * Return an iterator to the beginning.
	 *
	 * @return the iterator
	 */
	inline auto begin() noexcept
	{
		return m_map.begin();
	}

	/**
	 * Overloaded function.
	 *
	 * @return the iterator
	 */
	inline auto begin() const noexcept
	{
		return m_map.begin();
	}

	/**
	 * Overloaded function.
	 *
	 * @return the iterator
	 */
	inline auto cbegin() const noexcept
	{
		return m_map.cbegin();
	}

	/**
	 * Return an iterator to the end.
	 *
	 * @return the iterator
	 */
	inline auto end() noexcept
	{
		return m_map.end();
	}

	/**
	 * Overloaded function.
	 *
	 * @return the iterator
	 */
	inline auto end() const noexcept
	{
		return m_map.end();
	}

	/**
	 * Overloaded function.
	 *
	 * @return the iterator
	 */
	inline auto cend() const noexcept
	{
		return m_map.cend();
	}

	/**
	 * Add a socket to the listener.
	 *
	 * @param sc the socket
	 * @param flags (may be OR'ed)
	 */
	void set(Socket &sc, int flags)
	{
		if (m_map.count(sc) > 0) {
			m_map[sc] |= flags;
			m_backend.set(sc, flags);
		} else {
			m_map.insert({sc, flags});
			m_backend.set(sc, flags);
		}
	}

	/**
	 * Unset a socket from the listener, only the direction is removed
	 * unless the two directions are requested.
	 *
	 * For example, if you added a socket for both reading and writing,
	 * unsetting the write direction will keep the socket for reading.
	 *
	 * @param sc the socket
	 * @param flags the flags (may be OR'ed)
	 * @see remove
	 */
	void unset(Socket &sc, int flags) noexcept
	{
		if (m_map.count(sc) > 0) {
			m_map[sc] &= ~(flags);
			m_backend.unset(sc, flags);

			// No more flags, remove it
			if (m_map[sc] == 0) {
				m_map.erase(sc);
			}
		}
	}

	/**
	 * Remove completely the socket from the listener.
	 *
	 * @param sc the socket
	 */
	inline void remove(Socket &sc) noexcept
	{
		m_map.erase(sc);
		m_backend.remove(sc);
	}

	/**
	 * Remove all sockets.
	 */
	inline void clear() noexcept
	{
		m_map.clear();
		m_backend.clear();
	}

	/**
	 * Get the number of sockets in the listener.
	 */
	unsigned size() const noexcept
	{
		return m_map.size();
	}

	/**
	 * Select a socket. Waits for a specific amount of time specified as the duration.
	 *
	 * @param duration the duration
	 * @return the socket ready
	 */
	template <typename Rep, typename Ratio>
	inline SocketStatus wait(const std::chrono::duration<Rep, Ratio> &duration)
	{
		auto cvt = std::chrono::duration_cast<std::chrono::milliseconds>(duration);

		return m_backend.wait(cvt.count());
	}

	/**
	 * Overload with milliseconds.
	 *
	 * @param timeout the optional timeout in milliseconds
	 * @return the socket ready
	 */
	inline SocketStatus wait(int timeout = -1)
	{
		return m_backend.wait(timeout);
	}

	/**
	 * Select multiple sockets.
	 *
	 * @param duration the duration
	 * @return the socket ready
	 */
	template <typename Rep, typename Ratio>
	inline std::vector<SocketStatus> waitMultiple(const std::chrono::duration<Rep, Ratio> &duration)
	{
		auto cvt = std::chrono::duration_cast<std::chrono::milliseconds>(duration);

		return m_backend.waitMultiple(cvt.count());
	}

	/**
	 * Overload with milliseconds.
	 *
	 * @return the socket ready
	 */
	inline std::vector<SocketStatus> waitMultiple(int timeout = -1)
	{
		return m_backend.waitMultiple(timeout);
	}
};

/**
 * Helper to use the default.
 */
using SocketListener = SocketListenerBase<>;

template <typename Backend>
const int SocketListenerBase<Backend>::Read{1 << 0};

template <typename Backend>
const int SocketListenerBase<Backend>::Write{1 << 1};

#endif // !_SOCKET_LISTENER_NG_H_