Mercurial > code
changeset 469:bcfb05fa961c
Socket:
- Add new option system in namespace option,
- Do not throw on invalid Ip/Local constructs with storage.
author | David Demelier <markand@malikania.fr> |
---|---|
date | Thu, 05 Nov 2015 08:58:12 +0100 |
parents | becd06089e8f |
children | bc3a211c5e33 |
files | C++/modules/Socket/Sockets.cpp C++/modules/Socket/Sockets.h C++/tests/Socket/main.cpp |
diffstat | 3 files changed, 306 insertions(+), 17 deletions(-) [+] |
line wrap: on
line diff
--- a/C++/modules/Socket/Sockets.cpp Wed Nov 04 21:21:30 2015 +0100 +++ b/C++/modules/Socket/Sockets.cpp Thu Nov 05 08:58:12 2015 +0100 @@ -169,6 +169,8 @@ Ip::Ip(int domain) noexcept : m_domain{domain} { + assert(domain == AF_INET6 || domain == AF_INET); + if (m_domain == AF_INET6) { std::memset(&m_sin6, 0, sizeof (sockaddr_in6)); } else { @@ -179,6 +181,8 @@ Ip::Ip(int domain, const std::string &host, int port) : m_domain{domain} { + assert(domain == AF_INET6 || domain == AF_INET); + if (host == "*") { if (m_domain == AF_INET6) { std::memset(&m_sin6, 0, sizeof (sockaddr_in6)); @@ -217,22 +221,27 @@ } } -Ip::Ip(const sockaddr_storage *ss, socklen_t length) +Ip::Ip(const sockaddr_storage *ss, socklen_t length) noexcept : m_length{length} , m_domain{ss->ss_family} { + assert(ss->ss_family == AF_INET6 || ss->ss_family == AF_INET); + if (ss->ss_family == AF_INET6) { std::memcpy(&m_sin6, ss, length); } else if (ss->ss_family == AF_INET) { std::memcpy(&m_sin, ss, length); - } else { - throw std::invalid_argument{"invalid domain for Ip constructor"}; } } #if !defined(_WIN32) -Local::Local(std::string path, bool rm) +Local::Local() +{ + std::memset(m_sun, 0, sizeof (sockaddr_un)); +} + +Local::Local(std::string path, bool rm) noexcept : m_path{std::move(path)} { /* Silently remove the file even if it fails */ @@ -248,13 +257,13 @@ m_sun.sun_family = AF_LOCAL; } -Local::Local(const sockaddr_storage *ss, socklen_t length) +Local::Local(const sockaddr_storage *ss, socklen_t length) noexcept { + assert(ss->ss_family == AF_LOCAL); + if (ss->ss_family == AF_LOCAL) { std::memcpy(&m_sun, ss, length); m_path = reinterpret_cast<const sockaddr_un &>(m_sun).sun_path; - } else { - throw std::invalid_argument{"invalid domain for Local constructor"}; } }
--- a/C++/modules/Socket/Sockets.h Wed Nov 04 21:21:30 2015 +0100 +++ b/C++/modules/Socket/Sockets.h Thu Nov 05 08:58:12 2015 +0100 @@ -117,6 +117,7 @@ # include <arpa/inet.h> # include <netinet/in.h> +# include <netinet/tcp.h> # include <fcntl.h> # include <netdb.h> @@ -772,7 +773,7 @@ } /** - * Set an option for the socket. + * Set an option for the socket. Wrapper of setsockopt(2). * * @param level the setting level * @param name the name @@ -788,7 +789,22 @@ } /** - * Get an option for the socket. + * Object-oriented option setter. + * + * The object must have `set(Socket<Address, Protocol> &) const`. + * + * @param option the option + * @throw Error on errors + * @example socket.set(option::ReuseAddress{true}); + */ + template <typename Option> + inline void set(const Option &option) + { + option.set(*this); + } + + /** + * Get an option for the socket. Wrapper of getsockopt(2). * * @param level the setting level * @param name the name @@ -810,6 +826,22 @@ } /** + * Object-oriented option getter. + * + * The object must have `T get(Socket<Address, Protocol> &) const`, T can be any type and it is the value + * returned from this function. + * + * @return the same value as get() in the option + * @throw Error on errors + * @example socket.get<option::ReuseAddress>(); + */ + template <typename Option> + inline auto get() -> decltype(std::declval<Option>().get(*this)) + { + return Option{}.get(*this); + } + + /** * Get the native handle. * * @return the handle @@ -826,6 +858,7 @@ * * @param block set to false to mark **non-blocking** * @throw Error on any error + * @deprecated see option::BlockMode and set() */ void setBlockMode(bool block) { @@ -1337,7 +1370,200 @@ /* }}} */ /* - * Predefine addressed to be used + * Predefined options + * ------------------------------------------------------------------ + */ + +/* {{{ Options */ + +namespace option { + +/** + * @class BlockMode + * @brief Set or get the blocking-mode for a socket. + * @warning On Windows, it's not possible to check if the socket is blocking or not. + */ +class BlockMode { +public: + /** + * Set to false if you want non-blocking socket. + */ + bool value{false}; + + /** + * Set the option. + * + * @param sc the socket + * @throw Error on errors + */ + template <typename Address, typename Protocol> + void set(Socket<Address, Protocol> &sc) const + { +#if defined(O_NONBLOCK) && !defined(_WIN32) + int flags; + + if ((flags = fcntl(sc.handle(), F_GETFL, 0)) < 0) { + flags = 0; + } + + if (value) { + flags &= ~(O_NONBLOCK); + } else { + flags |= O_NONBLOCK; + } + + if (fcntl(sc.handle(), F_SETFL, flags) < 0) { + throw Error{Error::System, "fcntl"}; + } +#else + unsigned long flags = (value) ? 0 : 1; + + if (ioctlsocket(sc.handle(), FIONBIO, &flags) == Failure) { + throw Error{Error::System, "fcntl"}; + } +#endif + } + + /** + * Get the option. + * + * @return the value + * @throw Error on errors + */ + template <typename Address, typename Protocol> + bool get(Socket<Address, Protocol> &sc) const + { +#if defined(O_NONBLOCK) && !defined(_WIN32) + int flags = fcntl(sc.handle(), F_GETFL, 0); + + if (flags < 0) { + throw Error{Error::System, "fcntl"}; + } + + return !(flags & O_NONBLOCK); +#else + throw Error{Error::Other, "get", "Windows API cannot let you get the blocking status of a socket"}; +#endif + } +}; + +/** + * @class TcpNoDelay + * @brief Set this option if you want to disable nagle's algorithm. + */ +class TcpNoDelay { +public: + /** + * Set to true to set TCP_NODELAY option. + */ + bool value{true}; + + /** + * Set the option. + * + * @param sc the socket + * @throw Error on errors + */ + template <typename Address, typename Protocol> + inline void set(Socket<Address, Protocol> &sc) const + { + sc.set(IPPROTO_TCP, TCP_NODELAY, value ? 1 : 0); + } + + /** + * Get the option. + * + * @return the value + * @throw Error on errors + */ + template <typename Address, typename Protocol> + inline bool get(Socket<Address, Protocol> &sc) const + { + return static_cast<bool>(sc.get<int>(IPPROTO_TCP, TCP_NODELAY)); + } +}; + +/** + * @class ReuseAddress + * @brief Reuse address, must be used before calling Socket::bind + */ +class ReuseAddress { +public: + /** + * Set to true if you want to set the SOL_SOCKET/SO_REUSEADDR option. + */ + bool value{true}; + + /** + * Set the option. + * + * @param sc the socket + * @throw Error on errors + */ + template <typename Address, typename Protocol> + inline void set(Socket<Address, Protocol> &sc) const + { + sc.set(SOL_SOCKET, SO_REUSEADDR, value ? 1 : 0); + } + + /** + * Get the option. + * + * @return the value + * @throw Error on errors + */ + template <typename Address, typename Protocol> + inline bool get(Socket<Address, Protocol> &sc) const + { + return static_cast<bool>(sc.get<int>(SOL_SOCKET, SO_REUSEADDR)); + } +}; + +/** + * @class Ipv6Only + * @brief Control IPPROTO_IPV6/IPV6_V6ONLY + * + * Note: some systems may or not set this option by default so it's a good idea to set it in any case to either + * false or true if portability is a concern. + */ +class Ipv6Only { +public: + /** + * Set this to use only IPv6. + */ + bool value{true}; + + /** + * Set the option. + * + * @param sc the socket + * @throw Error on errors + */ + template <typename Address, typename Protocol> + inline void set(Socket<Address, Protocol> &sc) const + { + sc.set(IPPROTO_IPV6, IPV6_V6ONLY, value ? 1 : 0); + } + + /** + * Get the option. + * + * @return the value + * @throw Error on errors + */ + template <typename Address, typename Protocol> + inline bool get(Socket<Address, Protocol> &sc) const + { + return static_cast<bool>(sc.get<int>(IPPROTO_IPV6, IPV6_V6ONLY)); + } +}; + +} // !option + +/* }}} */ + +/* + * Predefined addressed to be used * ------------------------------------------------------------------ * * - Ipv6, @@ -1365,6 +1591,7 @@ /** * Default initialize the Ip domain. * + * @pre domain must be AF_INET or AF_INET6 only * @param domain the domain (AF_INET or AF_INET6) */ Ip(int domain = AF_INET6) noexcept; @@ -1372,6 +1599,7 @@ /** * Construct an address suitable for bind() or connect(). * + * @pre domain must be AF_INET or AF_INET6 only * @param domain the domain (AF_INET or AF_INET6) * @param host the host (* for any) * @param port the port number @@ -1382,10 +1610,11 @@ /** * Construct an address from a storage. * + * @pre storage's domain must be AF_INET or AF_INET6 only * @param ss the storage * @param length the length */ - Ip(const sockaddr_storage *ss, socklen_t length); + Ip(const sockaddr_storage *ss, socklen_t length) noexcept; /** * Get the domain (AF_INET or AF_INET6). @@ -1468,7 +1697,7 @@ * @param ss the storage * @param length the length */ - inline Ipv6(const sockaddr_storage *ss, socklen_t length) + inline Ipv6(const sockaddr_storage *ss, socklen_t length) noexcept : Ip{ss, length} { } @@ -1506,7 +1735,7 @@ * @param ss the storage * @param length the length */ - inline Ipv4(const sockaddr_storage *ss, socklen_t length) + inline Ipv4(const sockaddr_storage *ss, socklen_t length) noexcept : Ip{ss, length} { } @@ -1539,7 +1768,7 @@ /** * Default constructor. */ - Local() = default; + Local() noexcept; /** * Construct an address to a path. @@ -1547,15 +1776,16 @@ * @param path the path * @param rm remove the file before (default: false) */ - Local(std::string path, bool rm = false); + Local(std::string path, bool rm = false) noexcept; /** * Construct an unix address from a storage address. * + * @pre storage's domain must be AF_LOCAL * @param ss the storage * @param length the length */ - Local(const sockaddr_storage *ss, socklen_t length); + Local(const sockaddr_storage *ss, socklen_t length) noexcept; /** * Get the sockaddr_un. @@ -3219,7 +3449,7 @@ * @throw Error on errors */ StreamServer(const Address &address, Protocol protocol = {}, int max = 128) - : m_master{address, std::move(type)} + : m_master{protocol, std::move(address)} { // TODO: m_onError m_master.set(SOL_SOCKET, SO_REUSEADDR, 1);
--- a/C++/tests/Socket/main.cpp Wed Nov 04 21:21:30 2015 +0100 +++ b/C++/tests/Socket/main.cpp Thu Nov 05 08:58:12 2015 +0100 @@ -29,6 +29,56 @@ using namespace net; using namespace std::literals::chrono_literals; +/* + * Options + * ------------------------------------------------------------------ + */ + +TEST(Options, reuse) +{ + SocketTcpIp s; + + try { + s.set(option::ReuseAddress{true}); + ASSERT_TRUE(s.get<option::ReuseAddress>()); + + s.set(option::ReuseAddress{false}); + ASSERT_FALSE(s.get<option::ReuseAddress>()); + } catch (const std::exception &ex) { + FAIL() << ex.what(); + } +} + +TEST(Options, nodelay) +{ + SocketTcpIp s; + + try { + s.set(option::TcpNoDelay{true}); + ASSERT_TRUE(s.get<option::TcpNoDelay>()); + + s.set(option::TcpNoDelay{false}); + ASSERT_FALSE(s.get<option::TcpNoDelay>()); + } catch (const std::exception &ex) { + FAIL() << ex.what(); + } +} + +TEST(Options, v6only) +{ + SocketTcpIp s; + + try { + s.set(option::Ipv6Only{true}); + ASSERT_TRUE(s.get<option::Ipv6Only>()); + + s.set(option::Ipv6Only{false}); + ASSERT_FALSE(s.get<option::Ipv6Only>()); + } catch (const std::exception &ex) { + FAIL() << ex.what(); + } +} + /* -------------------------------------------------------- * TCP tests * -------------------------------------------------------- */