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).
 	 *