From 99aba5667d0d7ba6657f9c175a9342126bc4b0f2 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 22 Feb 2014 21:42:24 +0100 Subject: Connection to servers does not block the process anymore --- src/network/poller.cpp | 12 ++++++--- src/network/socket_handler.cpp | 57 ++++++++++++++++++++++++++++++++++++------ src/network/socket_handler.hpp | 23 ++++++++++++++++- 3 files changed, 80 insertions(+), 12 deletions(-) (limited to 'src/network') diff --git a/src/network/poller.cpp b/src/network/poller.cpp index 010dd58..dbea856 100644 --- a/src/network/poller.cpp +++ b/src/network/poller.cpp @@ -155,8 +155,7 @@ int Poller::poll(const std::chrono::milliseconds& timeout) else if (this->fds[i].revents & POLLIN) { auto socket_handler = this->socket_handlers.at(this->fds[i].fd); - if (socket_handler->is_connected()) - socket_handler->on_recv(); + socket_handler->on_recv(); nb_events--; } else if (this->fds[i].revents & POLLOUT) @@ -164,6 +163,8 @@ int Poller::poll(const std::chrono::milliseconds& timeout) auto socket_handler = this->socket_handlers.at(this->fds[i].fd); if (socket_handler->is_connected()) socket_handler->on_send(); + else + socket_handler->connect(); nb_events--; } } @@ -185,7 +186,12 @@ int Poller::poll(const std::chrono::milliseconds& timeout) if (revents[i].events & EPOLLIN) socket_handler->on_recv(); if (revents[i].events & EPOLLOUT) - socket_handler->on_send(); + { + if (socket_handler->is_connected()) + socket_handler->on_send(); + else + socket_handler->connect(); + } } return nb_events; #endif diff --git a/src/network/socket_handler.cpp b/src/network/socket_handler.cpp index 7faa9fd..fc0a359 100644 --- a/src/network/socket_handler.cpp +++ b/src/network/socket_handler.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -17,18 +18,38 @@ SocketHandler::SocketHandler(): poller(nullptr), - connected(false) + connected(false), + connecting(false) +{ + this->init_socket(); +} + +void SocketHandler::init_socket() { if ((this->socket = ::socket(AF_INET, SOCK_STREAM, 0)) == -1) throw std::runtime_error("Could not create socket"); int optval = 1; if (::setsockopt(this->socket, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval)) == -1) log_warning("Failed to enable TCP keepalive on socket: " << strerror(errno)); + // Set the socket on non-blocking mode. This is useful to receive a EAGAIN + // error when connect() would block, to not block the whole process if a + // remote is not responsive. + const int existing_flags = ::fcntl(this->socket, F_GETFL, 0); + if ((existing_flags == -1) || + (::fcntl(this->socket, F_SETFL, existing_flags | O_NONBLOCK) == -1)) + throw std::runtime_error(std::string("Could not initialize socket: ") + strerror(errno)); } -std::pair SocketHandler::connect(const std::string& address, const std::string& port) +void SocketHandler::connect(const std::string& address, const std::string& port) { - log_info("Trying to connect to " << address << ":" << port); + if (!this->connecting) + { + log_info("Trying to connect to " << address << ":" << port); + } + this->connecting = true; + this->address = address; + this->port = port; + struct addrinfo hints; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_flags = 0; @@ -43,7 +64,8 @@ std::pair SocketHandler::connect(const std::string& address, { log_warning(std::string("getaddrinfo failed: ") + gai_strerror(res)); this->close(); - return std::make_pair(false, gai_strerror(res)); + this->on_connection_failed(gai_strerror(res)); + return ; } // Make sure the alloced structure is always freed at the end of the @@ -56,14 +78,28 @@ std::pair SocketHandler::connect(const std::string& address, { log_info("Connection success."); this->connected = true; + this->connecting = false; this->on_connected(); - return std::make_pair(true, ""); + return ; + } + else if (errno == EINPROGRESS || errno == EALREADY) + { // retry this process later, when the socket + // is ready to be written on. + log_debug("Need to retry connecting later..." << strerror(errno)); + this->poller->watch_send_events(this); + return ; } log_info("Connection failed:" << strerror(errno)); } log_error("All connection attempts failed."); this->close(); - return std::make_pair(false, ""); + this->on_connection_failed(strerror(errno)); + return ; +} + +void SocketHandler::connect() +{ + this->connect(this->address, this->port); } void SocketHandler::set_poller(Poller* poller) @@ -114,11 +150,11 @@ void SocketHandler::on_send() void SocketHandler::close() { this->connected = false; + this->connecting = false; this->poller->remove_socket_handler(this->get_socket()); ::close(this->socket); // recreate the socket for a potential future usage - if ((this->socket = ::socket(AF_INET, SOCK_STREAM, 0)) == -1) - throw std::runtime_error("Could not create socket"); + this->init_socket(); } socket_t SocketHandler::get_socket() const @@ -139,3 +175,8 @@ bool SocketHandler::is_connected() const { return this->connected; } + +bool SocketHandler::is_connecting() const +{ + return this->connecting; +} diff --git a/src/network/socket_handler.hpp b/src/network/socket_handler.hpp index c27d44c..cb09e4e 100644 --- a/src/network/socket_handler.hpp +++ b/src/network/socket_handler.hpp @@ -19,10 +19,15 @@ class SocketHandler public: explicit SocketHandler(); virtual ~SocketHandler() {} + /** + * (re-)Initialize the socket + */ + void init_socket(); /** * Connect to the remote server, and call on_connected() if this succeeds */ - std::pair connect(const std::string& address, const std::string& port); + void connect(const std::string& address, const std::string& port); + void connect(); /** * Set the pointer to the given Poller, to communicate with it. */ @@ -53,6 +58,11 @@ public: * Called when the connection is successful. */ virtual void on_connected() = 0; + /** + * Called when the connection fails. Not when it is closed later, just at + * the connect() call. + */ + virtual void on_connection_failed(const std::string& reason) = 0; /** * Called when we detect a disconnection from the remote host. */ @@ -63,6 +73,7 @@ public: */ virtual void parse_in_buffer() = 0; bool is_connected() const; + bool is_connecting() const; protected: socket_t socket; @@ -86,7 +97,17 @@ protected: * (actually it is sharing our ownership with a Bridge). */ Poller* poller; + /** + * Hostname we are connected/connecting to + */ + std::string address; + /** + * Port we are connected/connecting to + */ + std::string port; + bool connected; + bool connecting; private: SocketHandler(const SocketHandler&) = delete; -- cgit v1.2.3