Mercurial > code
changeset 458:db738947f359
Socket:
- connect() now has a continuation function (no arguments),
- add non-blocking-connect.cpp as example.
author | David Demelier <markand@malikania.fr> |
---|---|
date | Tue, 03 Nov 2015 16:38:48 +0100 |
parents | 060acd5945a3 |
children | 819bccefce8e |
files | C++/examples/Socket/ElapsedTimer.cpp C++/examples/Socket/ElapsedTimer.h C++/examples/Socket/non-blocking-connect.cpp C++/modules/Socket/Sockets.h |
diffstat | 4 files changed, 273 insertions(+), 35 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/examples/Socket/ElapsedTimer.cpp Tue Nov 03 16:38:48 2015 +0100 @@ -0,0 +1,60 @@ +/* + * ElapsedTimer.cpp -- measure elapsed time + * + * Copyright (c) 2013-2015 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. + */ + +#include "ElapsedTimer.h" + +using std::chrono::duration_cast; +using std::chrono::high_resolution_clock; +using std::chrono::milliseconds; + +ElapsedTimer::ElapsedTimer() noexcept +{ + m_last = high_resolution_clock::now(); +} + +void ElapsedTimer::pause() noexcept +{ + /* + * When we put the timer on pause, do not forget to set the already + * elapsed time. + */ + (void)elapsed(); + m_paused = true; +} + +void ElapsedTimer::restart() noexcept +{ + m_paused = false; + m_last = high_resolution_clock::now(); +} + +void ElapsedTimer::reset() noexcept +{ + m_elapsed = 0; + m_last = high_resolution_clock::now(); +} + +unsigned ElapsedTimer::elapsed() noexcept +{ + if (!m_paused) { + m_elapsed += duration_cast<milliseconds>(high_resolution_clock::now() - m_last).count(); + m_last = high_resolution_clock::now(); + } + + return m_elapsed; +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/examples/Socket/ElapsedTimer.h Tue Nov 03 16:38:48 2015 +0100 @@ -0,0 +1,77 @@ +/* + * ElapsedTimer.h -- measure elapsed time + * + * Copyright (c) 2013-2015 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 _ELAPSED_TIMER_H_ +#define _ELAPSED_TIMER_H_ + +/** + * @file ElapsedTimer.h + * @brief Measure elapsed time + */ + +#include <chrono> + +/** + * @class ElapsedTimer + * @brief Measure elapsed time + * + * This class provides an abstraction to measure elapsed time since the + * construction of the object. + * + * It uses std::chrono::high_resolution_clock for more precision and uses + * milliseconds only. + */ +class ElapsedTimer { +public: + using TimePoint = std::chrono::time_point<std::chrono::high_resolution_clock>; + +private: + TimePoint m_last; + bool m_paused{false}; + unsigned m_elapsed{0}; + +public: + /** + * Construct the elapsed timer, start counting. + */ + ElapsedTimer() noexcept; + + /** + * Put the timer on pause, the already elapsed time is stored. + */ + void pause() noexcept; + + /** + * Restart the timer, does not reset it. + */ + void restart() noexcept; + + /** + * Reset the timer to 0. + */ + void reset() noexcept; + + /** + * Get the number of elapsed milliseconds. + * + * @return the milliseconds + */ + unsigned elapsed() noexcept; +}; + +#endif // !_ELAPSED_TIMER_H_
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/examples/Socket/non-blocking-connect.cpp Tue Nov 03 16:38:48 2015 +0100 @@ -0,0 +1,77 @@ +/* + * non-blocking-connect.cpp -- example of non blocking connect + * + * Options: + * - WITH_HOST (string literal), the host to try (default: "malikania.fr") + * - WITH_PORT (int), the port to use (default: 80) + * - WITH_TIMEOUT (int), number of seconds before giving up (default: 30) + * - WITH_SSL (bool), true to test with SSL (default: false) + */ + +#include <iostream> + +#if !defined(WITH_HOST) +# define WITH_HOST "malikania.fr" +#endif + +#if !defined(WITH_PORT) +# define WITH_PORT 80 +#endif + +#if !defined(WITH_TIMEOUT) +# define WITH_TIMEOUT 30 +#endif + +#include "Sockets.h" +#include "ElapsedTimer.h" + +int main() +{ +#if defined(WITH_SSL) + net::SocketTls<net::Ipv4> socket; +#else + net::SocketTcp<net::Ipv4> socket; +#endif + + net::Listener<> listener; + ElapsedTimer timer; + + socket.setBlockMode(false); + + while (socket.state() != net::State::Connected && timer.elapsed() < (WITH_TIMEOUT * 1000)) { + try { + /* + * If the socket is in state Open then no connection has been attempted, otherwise if it's Connecting, + * then we can just resume the connection process. + */ + if (socket.state() == net::State::Open) { + std::cout << "Trying to connect to " << WITH_HOST << ":" << WITH_PORT << std::endl; + socket.connect(net::Ipv4{WITH_HOST, WITH_PORT}); + } else { + socket.connect(); + } + } catch (const net::Error &error) { + listener.remove(socket.handle()); + + if (error.code() == net::Error::WouldBlockRead) { + listener.set(socket.handle(), net::FlagRead); + } else if (error.code() == net::Error::WouldBlockWrite) { + listener.set(socket.handle(), net::FlagWrite); + } else { + std::cerr << "error: " << error.what() << std::endl; + std::exit(1); + } + + try { + listener.wait(std::chrono::seconds(WITH_TIMEOUT)); + } catch (...) { + std::cerr << "timeout while connecting" << std::endl; + std::exit(1); + } + } + } + + std::cout << "Successfully connected!" << std::endl; + + return 0; +}
--- a/C++/modules/Socket/Sockets.h Tue Nov 03 14:07:10 2015 +0100 +++ b/C++/modules/Socket/Sockets.h Tue Nov 03 16:38:48 2015 +0100 @@ -674,16 +674,19 @@ } /** - * Connect or continue the connection to the specified address. + * Connect to the connection to the specified address. If the socket is marked non-blocking and the + * connection cannot be established, an error is thrown with WouldBlockWrite or WouldBlockRead as the code. The + * user is then responsible to wait that the socket is writable or readable using a listener and then call the + * overloaded connect() function which takes 0 argument. * * @param address the address * @param length the address length - * @pre state() must be State::Connecting or State::Open + * @pre state() must be State::Open * @throw Error on errors */ void connect(const sockaddr *address, socklen_t length) { - assert(m_state == State::Connecting || m_state == State::Open); + assert(m_state == State::Open); m_type.connect(*this, address, length); } @@ -701,6 +704,20 @@ } /** + * Continue the connection, only required with non-blocking sockets. Just like the initial connect() call, + * this function can still be in progress. + * + * @pre state() must be State::Connecting + * @throw Error on errors + */ + inline void connect() + { + assert(m_state == State::Connecting); + + m_type.connect(*this); + } + + /** * Accept a pending connection. * * @param info the address where to store client's information (optional) @@ -1304,7 +1321,7 @@ } /** - * Standard connect. Wrapper of connect(2) + * Standard connect. Wrapper of connect(2) * * @param sc the socket * @param address the address @@ -1313,44 +1330,52 @@ template <typename Address, typename Type> void connect(Socket<Address, Type> &sc, const sockaddr *address, socklen_t length) { - if (sc.state() == State::Open) { - if (::connect(sc.handle(), address, length) == Failure) { - /* - * Determine if the error comes from a non-blocking connect that cannot be - * accomplished yet. - */ + if (::connect(sc.handle(), address, length) == Failure) { + /* + * Determine if the error comes from a non-blocking connect that cannot be + * accomplished yet. + */ #if defined(_WIN32) - int error = WSAGetLastError(); - - if (error == WSAEWOULDBLOCK) { - sc.setState(State::Connecting); - throw Error{Error::WouldBlockWrite, "connect", error}; - } - - throw Error{Error::System, "connect", error}; + int error = WSAGetLastError(); + + if (error == WSAEWOULDBLOCK) { + sc.setState(State::Connecting); + throw Error{Error::WouldBlockWrite, "connect", error}; + } + + throw Error{Error::System, "connect", error}; #else - if (errno == EINPROGRESS) { - sc.setState(State::Connecting); - throw Error{Error::WouldBlockWrite, "connect"}; - } - - throw Error{Error::System, "connect"}; + if (errno == EINPROGRESS) { + sc.setState(State::Connecting); + throw Error{Error::WouldBlockWrite, "connect"}; + } + + throw Error{Error::System, "connect"}; #endif - } else { - sc.setState(State::Connected); - } - } else if (sc.state() == State::Connecting) { - int error = sc.template get<int>(SOL_SOCKET, SO_ERROR); - - if (error == Failure) { - throw Error{Error::System, "connect", error}; - } - + } else { sc.setState(State::Connected); } } /** + * Continue the connection. Just check for SOL_SOCKET/SO_ERROR. + * + * @param sc the socket + * @throw Error on errors + */ + template <typename Address, typename Type> + void connect(Socket<Address, Type> &sc) + { + int error = sc.template get<int>(SOL_SOCKET, SO_ERROR); + + if (error == Failure) { + throw Error{Error::System, "connect", error}; + } + + sc.setState(State::Connected); + } + + /** * Accept a clear client. Wrapper of accept(2). * * @param sc the socket @@ -1369,7 +1394,6 @@ return Socket<Address, Tcp>{handle}; } - /** * Receive data. Wrapper of recv(2). *