Skip to content

Commit

Permalink
Make tcp_server retry binding the socket (#176)
Browse files Browse the repository at this point in the history
It sometimes happens that binding the socket fails since it is already
in use (e.g. when restarting the driver too fast). This commit should
retry binding instead of simply throwing an exception
  • Loading branch information
fmauch authored Sep 6, 2023
1 parent b304e41 commit e2bebd6
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 12 deletions.
15 changes: 13 additions & 2 deletions include/ur_client_library/comm/tcp_server.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include <unistd.h>

#include <atomic>
#include <chrono>
#include <functional>
#include <thread>

Expand All @@ -60,7 +61,17 @@ class TCPServer
{
public:
TCPServer() = delete;
TCPServer(const int port);
/*!
* \brief Create a TCPServer object
*
* \param port Port on which to operate. The port will be bound to the process creating the
* object.
* \param max_num_tries If binding the socket fails, it will be retried this many times. If 0 is
* specified, binding the socket will be tried indefinitely.
* \param reconnection_time Wait time in between binding attempts.
*/
explicit TCPServer(const int port, const size_t max_num_tries = 0,
const std::chrono::milliseconds reconnection_time = std::chrono::seconds(1));
virtual ~TCPServer();

/*!
Expand Down Expand Up @@ -147,7 +158,7 @@ class TCPServer

private:
void init();
void bind();
void bind(const size_t max_num_tries, const std::chrono::milliseconds reconnection_time);
void startListen();

//! Handles connection events
Expand Down
36 changes: 27 additions & 9 deletions src/comm/tcp_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,11 @@ namespace urcl
{
namespace comm
{
TCPServer::TCPServer(const int port) : port_(port), maxfd_(0), max_clients_allowed_(0)
TCPServer::TCPServer(const int port, const size_t max_num_tries, const std::chrono::milliseconds reconnection_time)
: port_(port), maxfd_(0), max_clients_allowed_(0)
{
init();
bind();
bind(max_num_tries, reconnection_time);
startListen();
}

Expand Down Expand Up @@ -124,21 +125,38 @@ void TCPServer::shutdown()
}
}

void TCPServer::bind()
void TCPServer::bind(const size_t max_num_tries, const std::chrono::milliseconds reconnection_time)
{
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;

// INADDR_ANY is a special constant that signalizes "ANY IFACE",
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(port_);
int err = ::bind(listen_fd_, (struct sockaddr*)&server_addr, sizeof(server_addr));
if (err == -1)
int err = -1;
size_t connection_counter = 0;
do
{
std::ostringstream ss;
ss << "Failed to bind socket for port " << port_ << " to address. Reason: " << strerror(errno);
throw std::system_error(std::error_code(errno, std::generic_category()), ss.str());
}
err = ::bind(listen_fd_, (struct sockaddr*)&server_addr, sizeof(server_addr));
if (err == -1)
{
std::ostringstream ss;
ss << "Failed to bind socket for port " << port_ << " to address. Reason: " << strerror(errno);

if (connection_counter++ < max_num_tries || max_num_tries == 0)
{
std::this_thread::sleep_for(reconnection_time);
ss << "Retrying in " << std::chrono::duration_cast<std::chrono::duration<float>>(reconnection_time).count()
<< " seconds";
URCL_LOG_WARN("%s", ss.str().c_str());
}
else
{
throw std::system_error(std::error_code(errno, std::generic_category()), ss.str());
}
}
} while (err == -1 && (connection_counter <= max_num_tries || max_num_tries == 0));

URCL_LOG_DEBUG("Bound %d:%d to FD %d", server_addr.sin_addr.s_addr, port_, (int)listen_fd_);

FD_SET(listen_fd_, &masterfds_);
Expand Down
8 changes: 7 additions & 1 deletion tests/test_tcp_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ TEST_F(TCPServerTest, socket_creation)
comm::TCPServer server(port_);

// Shouldn't be able to create antoher server on same port
EXPECT_THROW(comm::TCPServer server2(port_), std::system_error);
EXPECT_THROW(comm::TCPServer server2(port_, 1, std::chrono::milliseconds(1)), std::system_error);

server.start();

Expand Down Expand Up @@ -326,6 +326,12 @@ TEST_F(TCPServerTest, client_connections)
EXPECT_FALSE(server.write(client2_fd, data, len, written));
EXPECT_FALSE(server.write(client3_fd, data, len, written));
}
TEST_F(TCPServerTest, check_address_already_in_use)
{
comm::TCPServer blocking_server(12321);

EXPECT_THROW(comm::TCPServer test_server(12321, 2, std::chrono::milliseconds(500)), std::system_error);
}

int main(int argc, char* argv[])
{
Expand Down

0 comments on commit e2bebd6

Please sign in to comment.