Mercurial > code
changeset 473:5a9671dabb15
Socket:
- Fix epoll which wrongly reset flags,
- Fix wrongly use of SOCKET_DEFAULT_BACKEND,
- Fix error in GCC using constexpr, switch to inline,
- Add more documentation about non-blocking sockets,
- Remove Socket::setBlockMode,
- Start updating all doxygen comments,
- Put back UDP.
author | David Demelier <markand@malikania.fr> |
---|---|
date | Thu, 05 Nov 2015 22:28:40 +0100 |
parents | f8594d3394ab |
children | fb3158aca358 |
files | C++/examples/Socket/blocking-accept.cpp C++/examples/Socket/blocking-connect.cpp C++/examples/Socket/non-blocking-accept.cpp C++/examples/Socket/non-blocking-connect.cpp C++/examples/Socket/stream-client.cpp C++/examples/Socket/stream-server.cpp C++/modules/Socket/Sockets.cpp C++/modules/Socket/Sockets.h C++/tests/Socket/main.cpp |
diffstat | 9 files changed, 456 insertions(+), 320 deletions(-) [+] |
line wrap: on
line diff
--- a/C++/examples/Socket/blocking-accept.cpp Thu Nov 05 16:42:23 2015 +0100 +++ b/C++/examples/Socket/blocking-accept.cpp Thu Nov 05 22:28:40 2015 +0100 @@ -22,20 +22,21 @@ int main() { #if defined(WITH_SSL) - net::SocketTls<net::Ipv4> master; - net::SocketTls<net::Ipv4> client{net::Invalid}; + net::SocketTls<net::address::Ip> master; + net::SocketTls<net::address::Ip> client{net::Invalid}; #else - net::SocketTcp<net::Ipv4> master; - net::SocketTcp<net::Ipv4> client{net::Invalid}; + net::SocketTcp<net::address::Ip> master; + net::SocketTcp<net::address::Ip> client{net::Invalid}; #endif net::Listener<> listener; try { - master.bind(net::Ipv4{"*", WITH_PORT}); + master.set(net::option::SockReuseAddress{true}); + master.bind(net::address::Ip{"*", WITH_PORT}); master.listen(); - listener.set(master.handle(), net::FlagRead); + listener.set(master.handle(), net::Condition::Readable); listener.wait(std::chrono::seconds(WITH_TIMEOUT)); client = master.accept(nullptr);
--- a/C++/examples/Socket/blocking-connect.cpp Thu Nov 05 16:42:23 2015 +0100 +++ b/C++/examples/Socket/blocking-connect.cpp Thu Nov 05 22:28:40 2015 +0100 @@ -28,14 +28,14 @@ int main() { #if defined(WITH_SSL) - net::SocketTls<net::Ipv4> socket; + net::SocketTls<net::address::Ip> socket; #else - net::SocketTcp<net::Ipv4> socket; + net::SocketTcp<net::address::Ip> socket; #endif try { std::cout << "Trying to connect to " << WITH_HOST << ":" << WITH_PORT << std::endl; - socket.connect(net::Ipv4{WITH_HOST, WITH_PORT}); + socket.connect(net::address::Ip{WITH_HOST, WITH_PORT}); } catch (const net::Error &error) { std::cerr << "error: " << error.what() << std::endl; std::exit(1);
--- a/C++/examples/Socket/non-blocking-accept.cpp Thu Nov 05 16:42:23 2015 +0100 +++ b/C++/examples/Socket/non-blocking-accept.cpp Thu Nov 05 22:28:40 2015 +0100 @@ -23,11 +23,11 @@ int main() { #if defined(WITH_SSL) - net::SocketTls<net::Ipv4> master; - net::SocketTls<net::Ipv4> client{net::Invalid}; + net::SocketTls<net::address::Ip> master; + net::SocketTls<net::address::Ip> client{net::Invalid}; #else - net::SocketTcp<net::Ipv4> master; - net::SocketTcp<net::Ipv4> client{net::Invalid}; + net::SocketTcp<net::address::Ip> master; + net::SocketTcp<net::address::Ip> client{net::Invalid}; #endif net::Listener<> listener; @@ -35,11 +35,10 @@ // 1. Create the master socket for listening. try { - master.bind(net::Ipv4{"*", WITH_PORT}); + master.set(net::option::SockReuseAddress{true}); + master.set(net::option::SockBlockMode{false}); + master.bind(net::address::Ip{"*", WITH_PORT}); master.listen(); - - // Usually never needed, but for the example put everything as non-blocking. - master.setBlockMode(false); } catch (const net::Error &error) { std::cerr << "error: " << error.what() << std::endl; std::exit(1); @@ -48,22 +47,16 @@ while (client.state() != net::State::Accepted && timer.elapsed() < (WITH_TIMEOUT * 1000)) { try { if (client.state() == net::State::Closed) { - // 1. Wait for a pre-accept process. - listener.set(master.handle(), net::FlagRead); + // 2. Wait for a pre-accept process. + listener.set(master.handle(), net::Condition::Readable); listener.wait(std::chrono::seconds(WITH_TIMEOUT)); client = master.accept(nullptr); - client.setBlockMode(false); + client.set(net::option::SockBlockMode{false}); listener.remove(master.handle()); } else { - // 2. Wait for the accept process to complete. + // 3. Wait for the accept process to complete. listener.remove(client.handle()); - - if (client.state() == net::State::AcceptingRead) { - listener.set(client.handle(), net::FlagRead); - } else if (client.state() == net::State::AcceptingWrite) { - listener.set(client.handle(), net::FlagWrite); - } - + listener.set(client.handle(), client.condition()); listener.wait(std::chrono::seconds(WITH_TIMEOUT)); client.accept(); }
--- a/C++/examples/Socket/non-blocking-connect.cpp Thu Nov 05 16:42:23 2015 +0100 +++ b/C++/examples/Socket/non-blocking-connect.cpp Thu Nov 05 22:28:40 2015 +0100 @@ -28,39 +28,33 @@ int main() { #if defined(WITH_SSL) - net::SocketTls<net::Ipv4> socket; + net::SocketTls<net::address::Ip> socket; #else - net::SocketTcp<net::Ipv4> socket; + net::SocketTcp<net::address::Ip> socket; #endif net::Listener<> listener; ElapsedTimer timer; - socket.setBlockMode(false); + // 1. Set to non-blocking. + socket.set(net::option::SockBlockMode{false}); try { std::cout << "Trying to connect to " << WITH_HOST << ":" << WITH_PORT << std::endl; - socket.connect(net::Ipv4{WITH_HOST, WITH_PORT}); + + // 2. Initial connection process. + socket.connect(net::address::Ip{WITH_HOST, WITH_PORT}); while (socket.state() != net::State::Connected) { listener.remove(socket.handle()); - if (socket.state() == net::State::ConnectingRead) { - listener.set(socket.handle(), net::FlagRead); - } else if (socket.state() == net::State::ConnectingWrite) { - listener.set(socket.handle(), net::FlagWrite); - } - + // 2. Now complete by waiting for the appropriate condition. + listener.set(socket.handle(), socket.condition()); listener.wait(std::chrono::seconds(WITH_TIMEOUT)); socket.connect(); } } catch (const net::Error &error) { - if (error.code() == net::Error::Timeout) { - std::cerr << "timeout while connecting" << std::endl; - } else { - std::cerr << "error: " << error.what() << std::endl; - } - + std::cerr << "error: " << error.what() << std::endl; std::exit(1); }
--- a/C++/examples/Socket/stream-client.cpp Thu Nov 05 16:42:23 2015 +0100 +++ b/C++/examples/Socket/stream-client.cpp Thu Nov 05 22:28:40 2015 +0100 @@ -20,9 +20,9 @@ #endif #if defined(WITH_SSL) -using Client = net::StreamClient<net::Ipv4, net::Tls>; +using Client = net::StreamClient<net::address::Ip, net::protocol::Tls>; #else -using Client = net::StreamClient<net::Ipv4, net::Tcp>; +using Client = net::StreamClient<net::address::Ip, net::protocol::Tcp>; #endif int main() @@ -54,7 +54,7 @@ std::cout << "client: sent: " << data << std::endl; }); - client.connect(net::Ipv4{WITH_HOST, WITH_PORT}); + client.connect(net::address::Ip{WITH_HOST, WITH_PORT}); while (connected) { client.poll(); @@ -63,4 +63,4 @@ std::cout << "client: exiting" << std::endl; return 0; -} \ No newline at end of file +}
--- a/C++/examples/Socket/stream-server.cpp Thu Nov 05 16:42:23 2015 +0100 +++ b/C++/examples/Socket/stream-server.cpp Thu Nov 05 22:28:40 2015 +0100 @@ -15,26 +15,26 @@ #endif #if defined(WITH_SSL) -using Server = net::StreamServer<net::Ipv4, net::Tls>; -using Connection = net::StreamConnection<net::Ipv4, net::Tls>; +using Server = net::StreamServer<net::address::Ip, net::protocol::Tls>; +using Connection = net::StreamConnection<net::address::Ip, net::protocol::Tls>; #else -using Server = net::StreamServer<net::Ipv4, net::Tcp>; -using Connection = net::StreamConnection<net::Ipv4, net::Tcp>; +using Server = net::StreamServer<net::address::Ip, net::protocol::Tcp>; +using Connection = net::StreamConnection<net::address::Ip, net::protocol::Tcp>; #endif int main() { #if defined(WITH_SSL) - net::Tls protocol; + net::protocol::Tls protocol; protocol.setVerify(false); protocol.setPrivateKey("test.key"); protocol.setCertificate("test.crt"); protocol.setMethod(net::ssl::Tlsv1); - Server server{std::move(protocol), net::Ipv4{"*", WITH_PORT}}; + Server server{std::move(protocol), net::address::Ip{"*", WITH_PORT}}; #else - Server server{net::Tcp{}, net::Ipv4{"*", WITH_PORT}}; + Server server{net::protocol::Tcp{}, net::address::Ip{"*", WITH_PORT}}; #endif server.setConnectionHandler([] (const std::shared_ptr<Connection> &client) { @@ -56,4 +56,4 @@ } return 0; -} \ No newline at end of file +}
--- a/C++/modules/Socket/Sockets.cpp Thu Nov 05 16:42:23 2015 +0100 +++ b/C++/modules/Socket/Sockets.cpp Thu Nov 05 22:28:40 2015 +0100 @@ -16,6 +16,8 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#define TIMEOUT_MSG "operation timeout" + #include <algorithm> #include <atomic> #include <cstring> @@ -130,6 +132,52 @@ /* }}} */ /* + * SSL stuff + * ------------------------------------------------------------------ + */ + +/* {{{ SSL initialization */ + +#if !defined(SOCKET_NO_SSL) + +namespace ssl { + +namespace { + +std::mutex mutex; +std::atomic<bool> initialized{false}; + +} // !namespace + +void finish() noexcept +{ + ERR_free_strings(); +} + +void init() noexcept +{ + std::lock_guard<std::mutex> lock{mutex}; + + if (!initialized) { + initialized = true; + + SSL_library_init(); + SSL_load_error_strings(); + OpenSSL_add_all_algorithms(); + +#if !defined(SOCKET_NO_AUTO_SSL_INIT) + atexit(finish); +#endif // SOCKET_NO_AUTO_SSL_INIT + } +} + +} // !ssl + +#endif // SOCKET_NO_SSL + +/* }}} */ + +/* * Error class * ------------------------------------------------------------------ */ @@ -166,6 +214,8 @@ /* {{{ Addresses */ +namespace address { + /* Default domain */ int Ip::m_default{AF_INET}; @@ -239,9 +289,9 @@ #if !defined(_WIN32) -Local::Local() +Local::Local() noexcept { - std::memset(m_sun, 0, sizeof (sockaddr_un)); + std::memset(&m_sun, 0, sizeof (sockaddr_un)); } Local::Local(std::string path, bool rm) noexcept @@ -272,6 +322,8 @@ #endif // !_WIN32 +} // !address + /* }}} */ /* @@ -316,7 +368,7 @@ throw Error{Error::System, "select"}; } if (error == 0) { - throw Error{Error::Timeout, "select", "Timeout while listening"}; + throw Error{Error::Timeout, "select", TIMEOUT_MSG}; } std::vector<ListenerStatus> sockets; @@ -335,6 +387,11 @@ /* }}} */ +/* + * Poll + * ------------------------------------------------------------------ + */ + /* {{{ Poll */ /* @@ -416,7 +473,7 @@ { auto result = poll(m_fds.data(), m_fds.size(), ms); if (result == 0) { - throw Error{Error::Timeout, "select", "Timeout while listening"}; + throw Error{Error::Timeout, "select", TIMEOUT_MSG}; } if (result < 0) { throw Error{Error::System, "poll"}; @@ -434,11 +491,15 @@ #endif // !SOCKET_HAVE_POLL +/* }}} */ + /* * Epoll implementation * ------------------------------------------------------------------ */ +/* {{{ Epoll */ + #if defined(SOCKET_HAVE_EPOLL) uint32_t Epoll::toEpoll(Condition condition) const noexcept @@ -497,14 +558,17 @@ } /* - * Add a new epoll_event or just update it. + * For set and unset, we need to apply the whole flags required, so if the socket + * was set to Connection::Readable and user add Connection::Writable, we must + * place both. */ -void Epoll::set(const ListenerTable &, Handle h, Condition condition, bool add) +void Epoll::set(const ListenerTable &table, Handle sc, Condition condition, bool add) { - update(h, add ? EPOLL_CTL_ADD : EPOLL_CTL_MOD, toEpoll(condition)); - if (add) { + update(sc, EPOLL_CTL_ADD, toEpoll(condition)); m_events.resize(m_events.size() + 1); + } else { + update(sc, EPOLL_CTL_MOD, toEpoll(table.at(sc) | condition)); } } @@ -522,7 +586,7 @@ update(sc, EPOLL_CTL_DEL, 0); m_events.resize(m_events.size() - 1); } else { - update(sc, EPOLL_CTL_MOD, toEpoll(table.at(sc)) & ~(toEpoll(condition))); + update(sc, EPOLL_CTL_MOD, toEpoll(table.at(sc) & ~(condition))); } } @@ -532,7 +596,7 @@ std::vector<ListenerStatus> result; if (ret == 0) { - throw Error{Error::Timeout, "epoll_wait"}; + throw Error{Error::Timeout, "epoll_wait", TIMEOUT_MSG}; } if (ret < 0) { throw Error{Error::System, "epoll_wait"}; @@ -547,9 +611,14 @@ #endif // !SOCKET_HAVE_EPOLL -/* -------------------------------------------------------- +/* }}} */ + +/* * Kqueue implementation - * -------------------------------------------------------- */ + * ------------------------------------------------------------------ + */ + +/* {{{ Kqueue */ #if defined(SOCKET_HAVE_KQUEUE) @@ -617,7 +686,7 @@ int nevents = kevent(m_handle, nullptr, 0, &m_result[0], m_result.capacity(), pts); if (nevents == 0) { - throw Error{Error::Timeout, "kevent"}; + throw Error{Error::Timeout, "kevent", TIMEOUT_MSG}; } if (nevents < 0) { throw Error{Error::System, "kevent"}; @@ -635,41 +704,6 @@ #endif // !SOCKET_HAVE_KQUEUE -#if !defined(SOCKET_NO_SSL) - -namespace ssl { - -namespace { - -std::mutex mutex; -std::atomic<bool> initialized{false}; - -} // !namespace - -void finish() noexcept -{ - ERR_free_strings(); -} - -void init() noexcept -{ - std::lock_guard<std::mutex> lock{mutex}; - - if (!initialized) { - initialized = true; - - SSL_library_init(); - SSL_load_error_strings(); - OpenSSL_add_all_algorithms(); - -#if !defined(SOCKET_NO_AUTO_SSL_INIT) - atexit(finish); -#endif // SOCKET_NO_AUTO_SSL_INIT - } -} - -} // !ssl - -#endif // SOCKET_NO_SSL +/* }}} */ } // !net
--- a/C++/modules/Socket/Sockets.h Thu Nov 05 16:42:23 2015 +0100 +++ b/C++/modules/Socket/Sockets.h Thu Nov 05 22:28:40 2015 +0100 @@ -93,24 +93,30 @@ * * @note Do not rely on the value shown in doxygen. */ -#if !defined(SOCKET_DEFAULT_BACKEND) -# if defined(_WIN32) +#if defined(_WIN32) +# if !defined(SOCKET_DEFAULT_BACKEND) # if defined(SOCKET_HAVE_POLL) # define SOCKET_DEFAULT_BACKEND Poll # else # define SOCKET_DEFAULT_BACKEND Select # endif -# elif defined(__linux__) -# include <sys/epoll.h> - +# endif +#elif defined(__linux__) +# include <sys/epoll.h> + +# if !defined(SOCKET_DEFAULT_BACKEND) # define SOCKET_DEFAULT_BACKEND Epoll -# elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__APPLE__) -# include <sys/types.h> -# include <sys/event.h> -# include <sys/time.h> - +# endif +#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__APPLE__) +# include <sys/types.h> +# include <sys/event.h> +# include <sys/time.h> + +# if !defined(SOCKET_DEFAULT_BACKEND) # define SOCKET_DEFAULT_BACKEND Kqueue -# else +# endif +#else +# if !defined(SOCKET_DEFAULT_BACKEND) # define SOCKET_DEFAULT_BACKEND Select # endif #endif @@ -255,6 +261,23 @@ #endif +#if !defined(SOCKET_NO_SSL) + +namespace ssl { + +/** + * @enum Method + * @brief Which OpenSSL method to use. + */ +enum Method { + Tlsv1, //!< TLS v1.2 (recommended) + Sslv3 //!< SSLv3 +}; + +} // !ssl + +#endif + /* }}} */ /* @@ -542,7 +565,7 @@ * @param v2 the second value * @return the new value */ -constexpr Condition &operator|=(Condition &v1, Condition v2) noexcept +inline Condition &operator|=(Condition &v1, Condition v2) noexcept { v1 = static_cast<Condition>(static_cast<int>(v1) | static_cast<int>(v2)); @@ -556,7 +579,7 @@ * @param v2 the second value * @return the new value */ -constexpr Condition &operator&=(Condition &v1, Condition v2) noexcept +inline Condition &operator&=(Condition &v1, Condition v2) noexcept { v1 = static_cast<Condition>(static_cast<int>(v1) & static_cast<int>(v2)); @@ -570,7 +593,7 @@ * @param v2 the second value * @return the new value */ -constexpr Condition &operator^=(Condition &v1, Condition v2) noexcept +inline Condition &operator^=(Condition &v1, Condition v2) noexcept { v1 = static_cast<Condition>(static_cast<int>(v1) ^ static_cast<int>(v2)); @@ -592,6 +615,13 @@ /** * @class Socket * @brief Base socket class for socket operations. + * + * **Important:** When using non-blocking sockets, some considerations must be taken. See the implementation of the + * underlying protocol for more details. + * + * @see protocol::Tls + * @see protocol::Tcp + * @see protocol::Udp */ template <typename Address, typename Protocol> class Socket { @@ -618,11 +648,11 @@ * @param type the type SOCK_* * @param protocol the protocol * @param iface the implementation - * @throw Error on failures + * @throw net::Error on errors * @post state is set to Open * @post handle is not set to Invalid */ - Socket(int domain, int type, int protocol, Protocol iface = Protocol{}) + Socket(int domain, int type, int protocol, Protocol iface = {}) : m_proto(std::move(iface)) { #if !defined(SOCKET_NO_AUTO_INIT) @@ -647,6 +677,7 @@ * * @param protocol the protocol * @param address which type of address + * @throw net::Error on errors */ explicit inline Socket(Protocol protocol = {}, const Address &address = {}) : Socket{address.domain(), protocol.type(), 0, std::move(protocol)} @@ -802,7 +833,7 @@ * @param level the setting level * @param name the name * @param arg the value - * @throw Error on error + * @throw net::Error on errors */ template <typename Argument> void set(int level, int name, const Argument &arg) @@ -818,7 +849,7 @@ * The object must have `set(Socket<Address, Protocol> &) const`. * * @param option the option - * @throw Error on errors + * @throw net::Error on errors */ template <typename Option> inline void set(const Option &option) @@ -831,7 +862,7 @@ * * @param level the setting level * @param name the name - * @throw Error on error + * @throw net::Error on errors */ template <typename Argument> Argument get(int level, int name) @@ -855,7 +886,7 @@ * returned from this function. * * @return the same value as get() in the option - * @throw Error on errors + * @throw net::Error on errors */ template <typename Option> inline auto get() -> decltype(std::declval<Option>().get(*this)) @@ -875,47 +906,12 @@ } /** - * 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 Error on any error - * @deprecated see option::BlockMode and set() - */ - void setBlockMode(bool block) - { -#if defined(O_NONBLOCK) && !defined(_WIN32) - int flags; - - if ((flags = fcntl(m_handle, F_GETFL, 0)) == -1) { - flags = 0; - } - - if (block) { - flags &= ~(O_NONBLOCK); - } else { - flags |= O_NONBLOCK; - } - - if (fcntl(m_handle, F_SETFL, flags) == Failure) { - throw Error{Error::System, "setBlockMode"}; - } -#else - unsigned long flags = (block) ? 0 : 1; - - if (ioctlsocket(m_handle, FIONBIO, &flags) == Failure) { - throw Error{Error::System, "setBlockMode"}; - } -#endif - } - - /** * Bind using a native address. * * @param address the address * @param length the size * @pre state must not be Bound - * @throw Error on errors + * @throw net::Error on errors */ void bind(const sockaddr *address, socklen_t length) { @@ -932,7 +928,7 @@ * Overload that takes an address. * * @param address the address - * @throw Error on errors + * @throw net::Error on errors */ inline void bind(const Address &address) { @@ -944,7 +940,7 @@ * * @param max the maximum number * @pre state must be Bound - * @throw Error on errors + * @throw net::Error on errors */ inline void listen(int max = 128) { @@ -958,17 +954,15 @@ /** * Connect to the address. * - * On non-blocking socket, if the connection cannot be established immediately then state is set to - * State::Connecting, action is set to Action::Connect and condition is set to the required condition. - * - * User is then responsible of waiting for the condition to be checked and call the connect() overload - * which takes 0 argument. + * If connection cannot be established immediately, connect with no argument must be called again. See + * the underlying protocol for more information. * * @pre state must be State::Open * @param address the address * @param length the the address length - * @post if state is Connecting, action and condition are defined - * @throw Error on errors + * @throw net::Error on errors + * @post state is set to State::Connecting or State::Connected + * @note For non-blocking sockets, see the underlying protocol function for more details */ void connect(const sockaddr *address, socklen_t length) { @@ -996,13 +990,10 @@ } /** - * Continue the connection, only required with non-blocking sockets. Just like the initial connect() call, - * this function can still be in progress. + * Continue the connection, only required with non-blocking sockets. * * @pre state must be State::Connecting - * @throw Error on errors - * @post if connection is completed, state is set to State::Connected - * @post if connection is still in progress, condition is set + * @throw net::Error on errors */ void connect() { @@ -1020,16 +1011,15 @@ /** * Accept a new client. If there are no pending connection, throws an error. * - * On non-blocking sockets, the client may not be completely accepted yet, if it's the case, the returned - * socket state is set to State::Accepting and user is responsible of calling accept() on it when the required - * condition is met. + * If the client cannot be accepted immediately, the client is returned and accept with no arguments + * must be called on it. See the underlying protocol for more information. * * @pre state must be State::Bound * @param info the address where to store client's information (optional) * @return the new socket * @throw Error on errors - * @post the client socket state is either set to State::Accepting or State::Accepted - * @post if the client state is set to State::Accepting, action and condition are defined + * @post returned client's state is set to State::Accepting or State::Accepted + * @note For non-blocking sockets, see the underlying protocol function for more details */ Socket<Address, Protocol> accept(Address *info) { @@ -1109,17 +1099,17 @@ /** * Receive some data. * - * If the operation cannot be complete immediately, 0 is returned, action() is set to Action::Receive and - * condition is set to Condition::Readableable or Condition::Writable, the user then must wait for the - * appropriate condition and repeat the recv() call. + * If the operation cannot be complete immediately, 0 is returned and user must call the function + * again when ready. See the underlying protocol for more information. * - * If action() is set to None and result is set to 0, disconnection occured. + * If action is set to Action::None and result is set to 0, disconnection occured. * * @param data the destination buffer * @param length the buffer length - * @pre action() must not be Action::Send + * @pre action must not be Action::Send * @return the number of bytes received or 0 * @throw Error on error + * @note For non-blocking sockets, see the underlying protocol function for more details */ unsigned recv(void *data, unsigned length) { @@ -1155,15 +1145,17 @@ } /** - * Send some data. Just like recv(), the operation may not succeed immediately and requires to be - * repeated. + * Send some data. + * + * If the operation cannot be complete immediately, 0 is returned and user must call the function + * again when ready. See the underlying protocol for more information. * * @param data the data buffer * @param length the buffer length * @return the number of bytes sent or 0 * @pre action() must not be Flag::Receive * @throw Error on error - * @see recv + * @note For non-blocking sockets, see the underlying protocol function for more details */ unsigned send(const void *data, unsigned length) { @@ -1192,20 +1184,37 @@ return send(data.c_str(), data.size()); } -#if 0 - /** * Send data to an end point. * + * If the operation cannot be complete immediately, 0 is returned and user must call the function + * again when ready. See the underlying protocol for more information. + * * @param data the buffer * @param length the buffer length * @param address the client address + * @param addrlen the address length * @return the number of bytes sent - * @throw Error on error + * @throw net::Error on errors + * @note For non-blocking sockets, see the underlying protocol function for more details + */ + inline unsigned sendto(const void *data, unsigned length, const sockaddr *address, socklen_t addrlen) + { + return m_proto.sendto(*this, data, length, address, addrlen); + } + + /** + * Overloaded function. + * + * @param data the buffer + * @param length the buffer length + * @param address the destination + * @return the number of bytes sent + * @throw net::Error on errors */ inline unsigned sendto(const void *data, unsigned length, const Address &address) { - return m_proto.sendto(*this, data, length, address); + return sendto(data, length, address.address(), address.length()); } /** @@ -1214,7 +1223,7 @@ * @param data the data * @param address the address * @return the number of bytes sent - * @throw Error on error + * @throw net:;Error on errors */ inline unsigned sendto(const std::string &data, const Address &address) { @@ -1224,17 +1233,43 @@ /** * Receive data from an end point. * + * If the operation cannot be complete immediately, 0 is returned and user must call the function + * again when ready. See the underlying protocol for more information. + * * @param data the destination buffer * @param length the buffer length - * @param info the client information + * @param address the address destination + * @param addrlen the address length (in/out) * @return the number of bytes received - * @throw Error on error + * @throw net::Error on errors + * @note For non-blocking sockets, see the underlying protocol function for more details + */ + inline unsigned recvfrom(void *data, unsigned length, sockaddr *address, socklen_t *addrlen) + { + return m_proto.recvfrom(*this, data, length, address, addrlen); + } + + /** + * Overloaded function. + * + * @param data the destination buffer + * @param length the buffer length + * @param info the address destination + * @return the number of bytes received + * @throw net::Error on errors */ inline unsigned recvfrom(void *data, unsigned length, Address *info = nullptr) { - Address dummy; - - return m_proto.recvfrom(*this, data, length, info == nullptr ? dummy : *info); + sockaddr_storage storage; + socklen_t addrlen = sizeof (sockaddr_storage); + + auto n = recvfrom(data, length, reinterpret_cast<sockaddr *>(&storage), &addrlen); + + if (info && n != 0) { + *info = Address{&storage, addrlen}; + } + + return n; } /** @@ -1243,9 +1278,9 @@ * @param count the maximum number of bytes to receive * @param info the client information * @return the string - * @throw Error on error + * @throw net::Error on errors */ - inline std::string recvfrom(unsigned count, Address *info = nullptr) + std::string recvfrom(unsigned count, Address *info = nullptr) { std::string result; @@ -1256,8 +1291,6 @@ return result; } -#endif - /** * Close the socket. * @@ -1403,12 +1436,19 @@ */ namespace option { +/* + * Options for socket + * ------------------------------------------------------------------ + */ + +/* {{{ Options for socket */ + /** - * @class BlockMode + * @class SockBlockMode * @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 { +class SockBlockMode { public: /** * Set to false if you want non-blocking socket. @@ -1473,6 +1513,116 @@ }; /** + * @class SockReuseAddress + * @brief Reuse address, must be used before calling Socket::bind + */ +class SockReuseAddress { +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.template get<int>(SOL_SOCKET, SO_REUSEADDR)); + } +}; + +/** + * @class SockSendBuffer + * @brief Set or get the output buffer. + */ +class SockSendBuffer { +public: + /** + * Set to the buffer size. + */ + int value{2048}; + + /** + * 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_SNDBUF, value); + } + + /** + * Get the option. + * + * @return the value + * @throw Error on errors + */ + template <typename Address, typename Protocol> + inline int get(Socket<Address, Protocol> &sc) const + { + return sc.template get<int>(SOL_SOCKET, SO_SNDBUF); + } +}; + +/** + * @class SockReceiveBuffer + * @brief Set or get the input buffer. + */ +class SockReceiveBuffer { +public: + /** + * Set to the buffer size. + */ + int value{2048}; + + /** + * 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_RCVBUF, value); + } + + /** + * Get the option. + * + * @return the value + * @throw Error on errors + */ + template <typename Address, typename Protocol> + inline int get(Socket<Address, Protocol> &sc) const + { + return sc.template get<int>(SOL_SOCKET, SO_RCVBUF); + } +}; + +/* }}} */ + +/** * @class TcpNoDelay * @brief Set this option if you want to disable nagle's algorithm. */ @@ -1504,43 +1654,7 @@ 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)); + return static_cast<bool>(sc.template get<int>(IPPROTO_TCP, TCP_NODELAY)); } }; @@ -1579,7 +1693,7 @@ template <typename Address, typename Protocol> inline bool get(Socket<Address, Protocol> &sc) const { - return static_cast<bool>(sc.get<int>(IPPROTO_IPV6, IPV6_V6ONLY)); + return static_cast<bool>(sc.template get<int>(IPPROTO_IPV6, IPV6_V6ONLY)); } }; @@ -1598,6 +1712,11 @@ /* {{{ Addresses */ /** + * Set of predefined addresses. + */ +namespace address { + +/** * @class Ip * @brief Base class for IPv6 and IPv4, you can use it if you don't know in advance if you'll use IPv6 or IPv4. */ @@ -1799,6 +1918,8 @@ #endif // !_WIN32 +} // !address + /* }}} */ /* @@ -1812,6 +1933,11 @@ /* {{{ Protocols */ +/** + * Set of predefined protocols. + */ +namespace protocol { + /* {{{ Tcp */ /** @@ -1997,6 +2123,7 @@ int error = WSAGetLastError(); if (error == WSAEWOULDBLOCK) { + nbread = 0; sc.setCondition(Condition::Readable); } else { sc.setState(State::Disconnected); @@ -2069,8 +2196,6 @@ /* {{{ Udp */ -#if 0 - /** * @class Udp * @brief Clear UDP type. @@ -2084,7 +2209,7 @@ * * @return SOCK_DGRAM */ - inline int type() noexcept + inline int type() const noexcept { return SOCK_DGRAM; } @@ -2101,40 +2226,43 @@ /** * Receive data from an end point. * + * If the socket is marked non-blocking and no data is available, 0 is returned and condition is set to + * Condition::Readable. + * + * If the socket is blocking, this functions blocks until some data is available or if an error occurs. + * * @param sc the socket * @param data the destination buffer * @param length the buffer length - * @param info the client information + * @param address the address + * @param addrlen the initial address length * @return the number of bytes received * @throw Error on error */ template <typename Address> - unsigned recvfrom(Socket<Address, Udp> &sc, void *data, unsigned length, Address &info) + unsigned recvfrom(Socket<Address, Udp> &sc, void *data, unsigned length, sockaddr *address, socklen_t *addrlen) { int nbread; - /* Store information */ - sockaddr_storage address; - socklen_t addrlen = sizeof (sockaddr_storage); - - nbread = ::recvfrom(sc.handle(), (Arg)data, length, 0, reinterpret_cast<sockaddr *>(&address), &addrlen); - info = Address{&address, addrlen}; + nbread = ::recvfrom(sc.handle(), (Arg)data, length, 0, address, addrlen); if (nbread == Failure) { #if defined(_WIN32) int error = WSAGetLastError(); if (error == WSAEWOULDBLOCK) { - throw Error{Error::WouldBlockRead, "recvfrom", error}; + nbread = 0; + sc.setCondition(Condition::Readable); + } else { + throw Error{Error::System, "recvfrom"}; } - - throw Error{Error::System, "recvfrom", error}; #else if (errno == EAGAIN || errno == EWOULDBLOCK) { - throw Error{Error::WouldBlockRead, "recvfrom"}; + nbread = 0; + sc.setCondition(Condition::Readable); + } else { + throw Error{Error::System, "recvfrom"}; } - - throw Error{Error::System, "recvfrom"}; #endif } @@ -2144,34 +2272,42 @@ /** * Send data to an end point. * + * If the socket is marked non-blocking and the operation would block, then 0 is returned and condition is set to + * Condition::Writable. + * + * If the socket is blocking, this functions blocks until the data has been sent. + * * @param sc the socket * @param data the buffer * @param length the buffer length * @param address the client address + * @param addrlen the adderss length * @return the number of bytes sent * @throw Error on error */ template <typename Address> - unsigned sendto(Socket<Address, Udp> &sc, const void *data, unsigned length, const Address &address) + unsigned sendto(Socket<Address, Udp> &sc, const void *data, unsigned length, const sockaddr *address, socklen_t addrlen) { int nbsent; - nbsent = ::sendto(sc.handle(), (ConstArg)data, length, 0, address.address(), address.length()); + nbsent = ::sendto(sc.handle(), (ConstArg)data, length, 0, address, addrlen); if (nbsent == Failure) { #if defined(_WIN32) int error = WSAGetLastError(); if (error == WSAEWOULDBLOCK) { - throw Error{Error::WouldBlockWrite, "sendto", error}; + nbsent = 0; + sc.setCondition(Condition::Writable); + } else { + throw Error{Error::System, "sendto", error}; } - - throw Error{Error::System, "sendto", error}; #else if (errno == EAGAIN || errno == EWOULDBLOCK) { - throw Error{Error::WouldBlockWrite, "sendto"}; + nbsent = 0; + sc.setCondition(Condition::Writable); + } else { + throw Error{Error::System, "sendto"}; } - - throw Error{Error::System, "sendto"}; #endif } @@ -2179,27 +2315,12 @@ } }; -#endif - /* }}} */ /* {{{ Tls */ #if !defined(SOCKET_NO_SSL) -namespace ssl { - -/** - * @enum Method - * @brief Which OpenSSL method to use. - */ -enum Method { - Tlsv1, //!< TLS v1.2 (recommended) - Sslv3 //!< SSLv3 -}; - -} // !ssl - /** * @class Tls * @brief OpenSSL secure layer for TCP. @@ -2329,7 +2450,7 @@ Tls() { #if !defined(SOCKET_NO_SSL_AUTO_INIT) - ssl::init(); + ::net::ssl::init(); #endif } @@ -2564,7 +2685,7 @@ auto no = SSL_get_error(m_ssl.get(), nbsent); if (no == SSL_ERROR_WANT_READ || no == SSL_ERROR_WANT_WRITE) { - nbread = 0 + nbsent = 0; updateStates(sc, sc.state(), Action::Send, no); } else { throw Error{Error::System, "send", error(no)}; @@ -2579,6 +2700,8 @@ /* }}} */ +} // !protocol + /* }}} */ /* @@ -2596,31 +2719,32 @@ * Helper to create TCP sockets. */ template <typename Address> -using SocketTcp = Socket<Address, Tcp>; +using SocketTcp = Socket<Address, protocol::Tcp>; /** * Helper to create TCP/IP sockets. */ -using SocketTcpIp = Socket<Ip, Tcp>; +using SocketTcpIp = Socket<address::Ip, protocol::Tcp>; #if !defined(_WIN32) /** * Helper to create TCP/Local sockets. */ -using SocketTcpLocal = Socket<Local, Tcp>; +using SocketTcpLocal = Socket<address::Local, protocol::Tcp>; #endif -#if 0 - /** * Helper to create UDP sockets. */ template <typename Address> -using SocketUdp = Socket<Address, Udp>; - -#endif +using SocketUdp = Socket<Address, protocol::Udp>; + +/** + * Helper to create UDP/IP sockets. + */ +using SocketUdpIp = Socket<address::Ip, protocol::Udp>; #if !defined(SOCKET_NO_SSL) @@ -2628,12 +2752,12 @@ * Helper to create OpenSSL TCP sockets. */ template <typename Address> -using SocketTls = Socket<Address, Tls>; +using SocketTls = Socket<Address, protocol::Tls>; /** * Helper to create OpenSSL TCP/Ip sockets. */ -using SocketTlsIp = Socket<Ip, Tls>; +using SocketTlsIp = Socket<address::Ip, protocol::Tls>; #endif // !SOCKET_NO_SSL @@ -3243,7 +3367,7 @@ StreamConnection(Socket<Address, Protocol> s) : m_socket{std::move(s)} { - m_socket.setBlockMode(false); + m_socket.set(net::option::SockBlockMode{false}); } /** @@ -3387,12 +3511,7 @@ assert(client->socket().action() != Action::None); m_listener.remove(client->socket().handle()); - - if (client->socket().condition() == Condition::Readable) { - m_listener.set(client->socket().handle(), Condition::Readable); - } else { - m_listener.set(client->socket().handle(), Condition::Writable); - } + m_listener.set(client->socket().handle(), client->socket().condition()); } /* @@ -3725,12 +3844,7 @@ assert(m_socket.action() != Action::None); m_listener.remove(m_socket.handle()); - - if (m_socket.condition() == Condition::Readable) { - m_listener.set(m_socket.handle(), Condition::Readable); - } else { - m_listener.set(m_socket.handle(), Condition::Writable); - } + m_listener.set(m_socket.handle(), m_socket.condition()); } /* @@ -3777,8 +3891,8 @@ * At this step, it is possible that we were completing a receive operation, in this * case the write flag may be removed, add it if required. */ - if (!m_output.empty()) { - m_listener.set(m_socket.handle(), Condition::Writable); + if (m_output.empty()) { + m_listener.unset(m_socket.handle(), Condition::Writable); } m_onRead(received); @@ -3841,7 +3955,7 @@ StreamClient(Protocol protocol = {}, const Address &address = {}) : m_socket{std::move(protocol), address} { - m_socket.setBlockMode(false); + m_socket.set(net::option::SockBlockMode{false}); m_listener.set(m_socket.handle(), Condition::Readable); }
--- a/C++/tests/Socket/main.cpp Thu Nov 05 16:42:23 2015 +0100 +++ b/C++/tests/Socket/main.cpp Thu Nov 05 22:28:40 2015 +0100 @@ -27,6 +27,10 @@ #include <Sockets.h> using namespace net; +using namespace net::address; +using namespace net::option; +using namespace net::protocol; + using namespace std::literals::chrono_literals; /* @@ -39,11 +43,11 @@ SocketTcpIp s; try { - s.set(option::ReuseAddress{true}); - ASSERT_TRUE(s.get<option::ReuseAddress>()); + s.set(option::SockReuseAddress{true}); + ASSERT_TRUE(s.get<option::SockReuseAddress>()); - s.set(option::ReuseAddress{false}); - ASSERT_FALSE(s.get<option::ReuseAddress>()); + s.set(option::SockReuseAddress{false}); + ASSERT_FALSE(s.get<option::SockReuseAddress>()); } catch (const std::exception &ex) { FAIL() << ex.what(); } @@ -66,7 +70,7 @@ TEST(Options, v6only) { - SocketTcpIp s; + SocketTcpIp s{Tcp{}, Ip{Ip::v6}}; try { s.set(option::Ipv6Only{true}); @@ -151,8 +155,6 @@ * UDP tests * -------------------------------------------------------- */ -#if 0 - class UdpServerTest : public testing::Test { protected: SocketUdp<Ip> m_server; @@ -204,8 +206,6 @@ }); } -#endif - /* -------------------------------------------------------- * Listener: set function * -------------------------------------------------------- */ @@ -533,7 +533,7 @@ public: NonBlockingConnectTest() { - m_client.setBlockMode(false); + m_client.set(SockBlockMode{false}); } ~NonBlockingConnectTest()