view C++/Socket.h @ 293:9b3270513f40

Socket: accept() can also block
author David Demelier <markand@malikania.fr>
date Thu, 13 Nov 2014 21:03:12 +0100
parents ea55a3886da0
children 836903141476 24085fae3162
line wrap: on
line source

/*
 * Socket.h -- portable C++ socket wrappers
 *
 * Copyright (c) 2013, 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_H_
#define _SOCKET_H_

#include <cstring>
#include <exception>
#include <memory>
#include <string>

#if defined(_WIN32)
#  include <WinSock2.h>
#  include <WS2tcpip.h>
#else
#  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>

#  define ioctlsocket(s, p, a)	::ioctl(s, p, a)
#  define closesocket(s)	::close(s)

#  define gai_strerrorA		gai_strerror

#  define INVALID_SOCKET	-1
#  define SOCKET_ERROR		-1
#endif

class Socket;
class SocketAddress;

namespace error {

/**
 * @class Timeout
 * @brief Describe a timeout expiration
 *
 * Usually thrown on timeout in SocketListener::select.
 */
class Timeout final : public std::exception {
private:
	std::string m_error;

public:
	Timeout(std::string func);
	const char *what() const noexcept override;
};

/**
 * @class InProgress
 * @brief Operation cannot be accomplished now
 *
 * Usually thrown in a non-blocking connect call.
 */
class InProgress final : public std::exception {
private:
	std::string m_error;

public:
	InProgress(std::string func);
	const char *what() const noexcept override;
};

/**
 * @class WouldBlock
 * @brief The operation would block
 *
 * Usually thrown in a non-blocking connect send or receive.
 */
class WouldBlock final : public std::exception {
private:
	std::string m_error;

public:
	WouldBlock(std::string func);
	const char *what() const noexcept override;
};

/**
 * @class Failure
 * @brief General socket failure
 *
 * An operation failed.
 */
class Failure final : public std::exception {
private:
	std::string m_error;

public:
	Failure(std::string func, std::string message);
	const char *what() const noexcept override;
};

} // !error

/**
 * @class SocketInterface
 * @brief Interface to implement
 *
 * This class implements the socket functions.
 */
class SocketInterface {
public:
	/**
	 * Bind the socket.
	 *
	 * @param s the socket
	 * @param address the address
	 * @throw SocketError error
	 */
	virtual void bind(Socket &s, const SocketAddress &address) = 0;

	/**
	 * Close the socket.
	 *
	 * @param s the socket
	 */
	virtual void close(Socket &s) = 0;

	/**
	 * Try to connect to the specific address
	 *
	 * @param s the socket
	 * @param addr the address
	 * @throw error::Failure on error
	 * @throw error::InProgress if the socket is marked non-blocking and connection cannot be established yet
	 */
	virtual void connect(Socket &s, const SocketAddress &address) = 0;

	/**
	 * Accept a client.
	 *
	 * @param s the socket
	 * @param info the optional client info
	 * @return a client ready to use
	 * @throw error::Failure on error
	 */
	virtual Socket accept(Socket &s, SocketAddress &info) = 0;

	/**
	 * Listen to a specific number of pending connections.
	 *
	 * @param s the socket
	 * @param max the max number of clients
	 * @throw error::Failure on error
	 */
	virtual void listen(Socket &s, int max) = 0;

	/**
	 * Receive some data.
	 *
	 * @param s the socket
	 * @param data the destination pointer
	 * @param dataLen max length to receive
	 * @return the number of bytes received
	 * @throw error::Failure on error
	 * @throw error::WouldBlock if the socket is marked non-blocking and the operation would block
	 */
	virtual unsigned recv(Socket &s, void *data, unsigned len) = 0;

	/**
	 * Receive from a connection-less socket and get the client
	 * information.
	 *
	 * @param s the socket
	 * @param data the destination pointer
	 * @param dataLen max length to receive
	 * @param info the client info
	 * @return the number of bytes received
	 * @throw error::Failure on error
	 * @throw error::WouldBlock if the socket is marked non-blocking and the operation would block
	 */
	virtual unsigned recvfrom(Socket &s, void *data, unsigned len, SocketAddress &info) = 0;

	/**
	 * Send some data.
	 *
	 * @param s the socket
	 * @param data the data to send
	 * @param dataLen the data length
	 * @return the number of bytes sent
	 * @throw error::Failure on error
	 * @throw error::WouldBlock if the socket is marked non-blocking and the operation would block
	 */
	virtual unsigned send(Socket &s, const void *data, unsigned len) = 0;

	/**
	 * Send some data to a connection-less socket.
	 *
	 * @param s the socket
	 * @param data the data to send
	 * @param dataLen the data length
	 * @param address the address
	 * @return the number of bytes sent
	 * @throw error::Failure on error
	 * @throw error::WouldBlock if the socket is marked non-blocking and the operation would block
	 */
	virtual unsigned sendto(Socket &s, const void *data, unsigned len, const SocketAddress &info) = 0;
};

/**
 * @class Socket
 * @brief socket abstraction
 *
 * This class is a big wrapper around sockets functions but portable,
 * there is some functions that helps for getting error reporting.
 *
 * This class is implemented as a PIMPL idiom, it is perfectly
 * safe to cast the object to any other derivate children.
 */
class Socket {
public:
#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

	using Iface	= std::shared_ptr<SocketInterface>;

protected:
	Iface	m_interface;				//!< the interface
	Handle	m_handle { INVALID_SOCKET };		//!< the socket shared pointer

public:
	/**
	 * To be called before any socket operation.
	 */
	static void init();

	/**
	 * 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);

	/**
	 * To be called before exiting.
	 */
	static void finish();

	/**
	 * Default constructor.
	 */
	Socket();

	/**
	 * Constructor to create a new socket.
	 *
	 * @param domain the domain
	 * @param type the type
	 * @param protocol the protocol
	 * @throw error::Failure on error
	 */
	Socket(int domain, int type, int protocol);

	/**
	 * Create a socket object with a already initialized socket.
	 *
	 * @param handle the handle
	 * @param interface the interface to use
	 */
	Socket(Handle handle, std::shared_ptr<SocketInterface> iface);

	/**
	 * Close the socket.
	 */
	virtual ~Socket() = default;

	/**
	 * Get the socket.
	 *
	 * @return the socket
	 */
	Handle handle() const;

	/**
	 * Set an option for the socket.
	 *
	 * @param level the setting level
	 * @param name the name
	 * @param arg the value
	 * @throw error::Failure on error
	 */
	template <typename Argument>
	void set(int level, int name, const Argument &arg)
	{
		if (setsockopt(m_handle, level, name, (Socket::ConstArg)&arg, sizeof (arg)) == SOCKET_ERROR)
			throw error::Failure("set", syserror());
	}

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

		if (getsockopt(m_handle, level, name, (Socket::Arg)&desired, &size) == SOCKET_ERROR)
			throw error::Failure("get", syserror());

		std::memcpy(&result, &desired, size);

		return result;
	}

	/**
	 * Enable or disable blocking mode.
	 *
	 * @param block the mode
	 * @throw error::Failure on error
	 */
	void blockMode(bool block = true);

	/**
	 * @copydoc SocketInterface::bind
	 */
	inline void bind(const SocketAddress &address)
	{
		m_interface->bind(*this, address);
	}

	/**
	 * @copydoc SocketInterface::close
	 */
	inline void close()
	{
		m_interface->close(*this);
	}

	/**
	 * @copydoc SocketInterface::connect
	 */
	inline void connect(const SocketAddress &address)
	{
		m_interface->connect(*this, address);
	}

	/**
	 * Accept a client without getting its info.
	 *
	 * @return a client ready to use
	 * @throw error::Failure on error
	 */
	Socket accept();

	/**
	 * @copydoc SocketInterface::accept
	 */
	inline Socket accept(SocketAddress &info)
	{
		return m_interface->accept(*this, info);
	}

	/**
	 * @copydoc SocketInterface::listen
	 */
	inline void listen(int max)
	{
		m_interface->listen(*this, max);
	}

	/**
	 * @copydoc SocketInterface::recv
	 */
	inline unsigned recv(void *data, unsigned dataLen)
	{
		return m_interface->recv(*this, data, dataLen);
	}

	/**
	 * Overload for char array.
	 *
	 * @param data the destination buffer
	 * @throw error::Failure on error
	 * @throw error::WouldBlock if the socket is marked non-blocking and the operation would block
	 */
	template <size_t Size>
	inline unsigned recv(char (&data)[Size])
	{
		return recv(data, sizeof (data));
	}

	/**
	 * Receive from a connection-less socket without getting
	 * client information.
	 *
	 * @param data the destination pointer
	 * @param dataLen max length to receive
	 * @return the number of bytes received
	 * @throw error::Failure on error
	 */
	unsigned recvfrom(void *data, unsigned dataLen);

	/**
	 * @copydoc SocketInterface::recvfrom
	 */
	inline unsigned recvfrom(void *data, unsigned dataLen, SocketAddress &info)
	{
		return m_interface->recvfrom(*this, data, dataLen, info);
	}

	/**
	 * Overload for char array.
	 *
	 * @param data the destination buffer
	 * @throw error::Failure on error
	 * @throw error::WouldBlock if the socket is marked non-blocking and the operation would block
	 */
	template <size_t Size>
	inline unsigned recvfrom(char (&data)[Size])
	{
		return recvfrom(data, sizeof (data));
	}

	/**
	 * Overload for char array.
	 *
	 * @param data the destination buffer
	 * @throw error::Failure on error
	 * @throw error::WouldBlock if the socket is marked non-blocking and the operation would block
	 */
	template <size_t Size>
	inline unsigned recvfrom(char (&data)[Size], SocketAddress &info)
	{
		return recvfrom(data, sizeof (data), info);
	}

	/**
	 * @copydoc SocketInterface::send
	 */
	inline unsigned send(const void *data, unsigned dataLen)
	{
		return m_interface->send(*this, data, dataLen);
	}

	/**
	 * Send a message as a string.
	 *
	 * @param message the message
	 * @return the number of bytes sent
	 * @throw SocketError on error
	 */
	unsigned send(const std::string &message);

	/**
	 * @copydoc SocketInterface::sendto
	 */
	inline unsigned sendto(const void *data, unsigned dataLen, const SocketAddress &info)
	{
		return m_interface->sendto(*this, data, dataLen, info);
	}

	/**
	 * Send a message to a connection-less socket.
	 *
	 * @param message the message
	 * @param address the address
	 * @return the number of bytes sent
	 * @throw SocketError on error
	 */
	unsigned sendto(const std::string &message, const SocketAddress &info);
};

bool operator==(const Socket &s1, const Socket &s2);

bool operator<(const Socket &s, const Socket &s2);

#endif // !_SOCKET_H_