Mercurial > code
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_