view C++/modules/Socket/Socket.h @ 348:c4381c345f2d

Socket: various compiler fixes
author David Demelier <markand@malikania.fr>
date Sat, 04 Apr 2015 19:58:48 +0200
parents d235553e47a9
children b909db6ca4ab e63a251515fe
line wrap: on
line source

/*
 * Socket.h -- portable C++ socket wrappers
 *
 * 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_NG_H_
#define _SOCKET_NG_H_

/**
 * @file Socket.h
 * @brief Portable socket abstraction
 *
 * User may set the following variables before compiling these files:
 *
 * SOCKET_NO_WSA_INIT	- (bool) Set to false if you don't want Socket class to
 *			  automatically calls WSAStartup() when creating sockets.
 *
 *			  Otherwise, you will need to call Socket::init,
 *			  Socket::finish yourself.
 *
 * SOCKET_NO_SSL_INIT	- (bool) Set to false if you don't want OpenSSL to be
 *			  initialized when the first SocketSsl object is created.
 *
 * SOCKET_HAVE_POLL	- (bool) Set to true if poll(2) function is available.
 *
 *			  Note: on Windows, this is automatically set if the
 *			  _WIN32_WINNT variable is greater or equal to 0x0600.
 */

#include <cstdlib>
#include <cstring>
#include <exception>
#include <string>

#if defined(_WIN32)
#  include <atomic>
#  include <cstdlib>
#  include <mutex>

#  include <WinSock2.h>
#  include <WS2tcpip.h>
#else
#  include <cerrno>

#  include <sys/ioctl.h>
#  include <sys/socket.h>
#  include <sys/types.h>

#  include <arpa/inet.h>

#  include <netinet/in.h>

#  include <fcntl.h>
#  include <netdb.h>
#  include <unistd.h>
#endif

class SocketAddress;

/**
 * @class SocketError
 * @brief Base class for sockets error
 */
class SocketError : public std::exception {
public:
	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
	};

	Code m_code;
	std::string m_function;
	std::string m_error;

	/**
	 * Constructor that use the last system error.
	 *
	 * @param code which kind of error
	 * @param function the function name
	 */
	SocketError(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
	 */
	SocketError(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
	 */
	SocketError(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();
	}
};

/**
 * @enum SocketState
 * @brief Category of error
 */
enum class SocketState {
	Opened,				///!< Socket is opened
	Closed,				///!< Socket has been closed
	Bound,				///!< Socket is bound to address
	Connected,			///!< Socket is connected to an end point
	Disconnected,			///!< Socket is disconnected
	Timeout				///!< Timeout has occured in a waiting operation
};

/**
 * @class Socket
 * @brief Base socket class for socket operations
 */
class Socket {
public:
	/* {{{ Portable types */

	/*
	 * The following types are defined differently between Unix
	 * and Windows.
	 */
#if defined(_WIN32)
	using Handle	= SOCKET;
	using ConstArg	= const char *;
	using Arg	= char *;
#else
	using Handle	= int;
	using ConstArg	= const void *;
	using Arg	= void *;
#endif

	/* }}} */

	/* {{{ Portable constants */

	/*
	 * The following constants are defined differently from Unix
	 * to Windows.
	 */
#if defined(_WIN32)
	static constexpr const Handle Invalid	= INVALID_SOCKET;
	static constexpr const int Error	= SOCKET_ERROR;
#else
	static constexpr const int Invalid	= -1;
	static constexpr const int Error	= -1;
#endif

	/* }}} */

	/* {{{ Portable initialization */

	/*
	 * Initialization stuff.
	 *
	 * The function init and finish are threadsafe.
	 */
#if defined(_WIN32)
private:
	static std::mutex s_mutex;
	static std::atomic<bool> s_initialized;

public:
	static inline void finish() noexcept
	{
		WSACleanup();
	}

	static inline void initialize() noexcept
	{
		std::lock_guard<std::mutex> 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_WSA_NO_INIT)
			atexit(finish);
#endif
		}
	}
#else
public:
	/**
	 * no-op.
	 */
	static inline void initialize() noexcept {}

	/**
	 * no-op.
	 */
	static inline void finish() noexcept {}
#endif

	/* }}} */

protected:
	Handle m_handle;
	SocketState m_state{SocketState::Opened};

public:
	/**
	 * Get the last socket system error. The error is set from errno or from
	 * WSAGetLastError on Windows.
	 *
	 * @return a string message
	 */
	static std::string syserror();

	/**
	 * Get the last system error.
	 *
	 * @param errn the error number (errno or WSAGetLastError)
	 * @return the error
	 */
	static std::string syserror(int errn);

	/**
	 * Construct a socket with an already created descriptor.
	 *
	 * @param handle the native descriptor
	 */
	inline Socket(Handle handle)
		: m_handle(handle)
		, m_state(SocketState::Opened)
	{
	}

	/**
	 * Create a socket handle.
	 *
	 * @param domain the domain AF_*
	 * @param type the type SOCK_*
	 * @param protocol the protocol
	 * @throw SocketError on failures
	 */
	Socket(int domain, int type, int protocol);

	/**
	 * Default destructor.
	 */
	virtual ~Socket() = default;

	/**
	 * Get the local name. This is a wrapper of getsockname().
	 *
	 * @return the address
	 * @throw SocketError on failures
	 */
	SocketAddress address() const;

	/**
	 * Set an option for the socket.
	 *
	 * @param level the setting level
	 * @param name the name
	 * @param arg the value
	 * @throw SocketError on error
	 */
	template <typename Argument>
	inline void set(int level, int name, const Argument &arg)
	{
#if defined(_WIN32)
		if (setsockopt(m_handle, level, name, (Socket::ConstArg)&arg, sizeof (arg)) == Error)
#else
		if (setsockopt(m_handle, level, name, (Socket::ConstArg)&arg, sizeof (arg)) < 0)
#endif
			throw SocketError(SocketError::System, "set");
	}

	/**
	 * Get an option for the socket.
	 *
	 * @param level the setting level
	 * @param name the name
	 * @throw SocketError on error
	 */
	template <typename Argument>
	inline Argument get(int level, int name)
	{
		Argument desired, result{};
		socklen_t size = sizeof (result);

#if defined(_WIN32)
		if (getsockopt(m_handle, level, name, (Socket::Arg)&desired, &size) == Error)
#else
		if (getsockopt(m_handle, level, name, (Socket::Arg)&desired, &size) < 0)
#endif
			throw SocketError(SocketError::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;
	}

	/**
	 * Get the socket state.
	 *
	 * @return
	 */
	inline SocketState state() const noexcept
	{
		return m_state;
	}

	/**
	 * Bind to an address.
	 *
	 * @param address the address
	 * @throw SocketError on any error
	 */
	void bind(const SocketAddress &address);

	/**
	 * 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 SocketError on any error
	 */
	void setBlockMode(bool block);

	/**
	 * Close the socket.
	 */
	virtual void close();
};

/**
 * Compare two sockets.
 *
 * @param s1 the first socket
 * @param s2 the second socket
 * @return true if they equals
 */
bool operator==(const Socket &s1, const Socket &s2);

/**
 * Compare two sockets, ideal for putting in a std::map.
 *
 * @param s1 the first socket
 * @param s2 the second socket
 * @return true if s1 < s2
 */
bool operator<(const Socket &s1, const Socket &s2);

#endif // !_SOCKET_NG_H_