Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: "Failed to bind listener socket" when quickly restarting a TCP server  (Read 21972 times)

0 Members and 1 Guest are viewing this topic.

Kalith

  • Jr. Member
  • **
  • Posts: 93
    • View Profile
Hi,

I am currently learning networking with the SFML, so please bear with me if I'm doing something stupid, or if this situation is normal.
Long story short: it seems that sf::TcpListener fails to listen again to the same port after it was closed while a client was connected.

You will find below a code sample that reproduces the problem. It is a simplified excerpt of a larger code I'm working on, but all the listening, connecting and disconnecting is handled in the same way SFML wise (which I hope is robust and clean enough, please tell me if it's not!).

On the one hand, the server is programmed to accept any incoming TCP connection using a sf::TcpListener, and stores a list of connected clients (sf::TcpSockets), each registered in a common sf::TcpSelector. Whenever one of the client sends a packet, I'm asking the server to restart (this is just a test case) : I close all connections and listening, then start listening again on the same port.

On the other hand, the client simply connects to the server by using a sf::TcpSocket, and sends a dummy packet to the server on demand. When the packet is sent, knowing that the server is going to restart, it routinely checks for incoming packets from the server in order to notice when it actually goes down. Once that is done, it closes the connection and asks for a new one.

The server:
#include <SFML/Network.hpp>
#include <iostream>
#include <vector>
#include <memory>

int main() {
    sf::TcpListener    listener;
    sf::SocketSelector selector;
   
    std::vector<std::unique_ptr<sf::TcpSocket>> client_list;
   
    while (true) {
        std::cout << "starting server..." << std::endl;
        std::size_t last_id = 0;
       
        // Try to open the port
        while (listener.listen(4444) != sf::Socket::Done) {
            std::cout << "cannot listen to port" << std::endl;
            std::cout << "try again (y/n) ? " << std::flush;
            char r; std::cin >> r;
            if (r == 'n') return 0;
        }
       
        std::cout << "connected!" << std::endl;

        selector.add(listener);
       
        // Start the main loop
        bool run = true;
        while (run) {
            selector.wait(sf::milliseconds(10));
           
            // Look for new clients
            if (selector.isReady(listener)) {
                std::unique_ptr<sf::TcpSocket> s(new sf::TcpSocket());
                if (listener.accept(*s) == sf::Socket::Done) {
                    selector.add(*s);
                    client_list.push_back(std::move(s));
                    std::cout << "new client" << std::endl;
                }
            }

            // Receive packets from each clients
            auto iter = client_list.begin();
            while (iter != client_list.end()) {
                auto& socket = **iter;
                if (selector.isReady(socket)) {
                    sf::Packet p;
                    switch (socket.receive(p)) {
                    case sf::Socket::Done :
                        run = false;
                        std::cout << "request restart" << std::endl;
                        break;
                    case sf::Socket::Disconnected :
                    case sf::Socket::Error :
                        socket.disconnect();
                        iter = client_list.erase(iter);
                        continue;
                    default : break;
                    }
                }
               
                ++iter;
            }
        }
       
        selector.clear();
        client_list.clear();
        listener.close();
       
        std::cout << "server is down" << std::endl;
    }

    return 0;
}

The client:
#include <SFML/Network.hpp>
#include <iostream>
#include <string>

int main() {
    sf::TcpSocket socket;
       
    while (true) {
        std::cout << "press [Enter] to connect: " << std::flush;
        std::string line;
        std::getline(std::cin, line);
       
        // Try to connect
        while (socket.connect(sf::IpAddress("127.0.0.1"), 4444, sf::seconds(2)) != sf::Socket::Done) {
            std::cout << "cannot connect" << std::endl;
            std::cout << "try again (y/n) ? " << std::flush;
            char r; std::cin >> r;
            if (r == 'n') return 0;
        }

        std::cout << "connected!" << std::endl;
        std::cout << "press [Enter] to restart server: " << std::flush;
        std::getline(std::cin, line);

        // Send a dummy packet to tell the server to restart
        std::uint8_t word = 0;
        sf::Packet p; p << word;
        if (socket.send(p) != sf::Socket::Done) {
            std::cout << "could not send packet" << std::endl;
            socket.disconnect();
            continue;
        }
       
        std::cout << "packet sent, waiting to see server going down" << std::endl;
       
        p.clear();
        socket.setBlocking(false);
        while (socket.receive(p) == sf::Socket::NotReady) {
            sf::sleep(sf::milliseconds(50));
        }
        socket.setBlocking(true);
       
        std::cout << "server is down" << std::endl;
       
        socket.disconnect();
    }

    return 0;
}

Compiling:
Code: [Select]
g++ -std=c++0x server.cpp -lsfml-network -lsfml-system -o server
g++ -std=c++0x client.cpp -lsfml-network -lsfml-system -o client

Here is how to demonstrate the issue:
  • In a first window, launch the server. If everything goes fine, it should print:
    Quote
    starting server...
    connected!
  • In a second window, launch the client, and press Enter to try to connect. If everything goes fine, it should print in the client window:
    Quote
    press [Enter] to connect
    connected!
    press [Enter] to restart server
    and in the server window:
    Quote
    new client
  • Still in the client window, press Enter once more to send the packet, then watch the two windows carefully. If everything goes fine, it should print fairly quickly in the client window:
    Quote
    packet sent, waiting to see server going down
    server is down
    press [Enter] to connect
    and in the server window:
    Quote
    request restart
    server is down
    starting server...
  • After a few seconds, the server will inform you that it failed to setup:
    Quote
    Failed to bind listener socket to port 4444
    cannot listen to port
    try again (y/n) ?
    If you try again repeatedly, it should be able to start after some time (took me more than 40 seconds).
This issue doesn't show up if I try to listen to a different port after the restart.
On the other hand, it also happens in this more brutal situation :
  • launch the server,
  • launch the client and connect,
  • kill the sever (Ctrl-C for example),
  • while the client is still alive, launch the server again.
Now that is something I can understand, because killing the server in such a brutal way prevents the SFML (or myself) from properly closing the connections. But I don't get why it still happens if I do things cleanly.

Am I doing it wrong? Or is this an inherent limitation of networking?

Thank you for your help.
Kal.

io

  • Jr. Member
  • **
  • Posts: 52
  • z/OS by day, SFML by night
    • View Profile
Haven't compiled or tried it yet/know if this is a valid idea, but have you tried sticking the line

sf::TcpListener    listener;
 

inside your while(1) loop?

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
This is the normal behaviour.

https://github.com/SFML/SFML/issues/150
Laurent Gomila - SFML developer

binary1248

  • SFML Team
  • Hero Member
  • *****
  • Posts: 1405
  • I am awesome.
    • View Profile
    • The server that really shouldn't be running
If you understand the implications already mentioned in the GitHub issue, you might find this useful:
#include <SFML/Network.hpp>

#ifdef SFML_SYSTEM_WINDOWS
        #include <winsock2.h>
#else
        #include <sys/socket.h>
#endif

class ReuseableListener : public sf::TcpListener {
public:
        void reuse() {
                char reuse = 1;
                setsockopt( getHandle(), SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof( reuse ) );
        }
};

int main() {
        ReuseableListener listener;

        listener.listen( 4444 );
        listener.reuse();

        return 0;
}
SFGUI # SFNUL # GLS # Wyrm <- Why do I waste my time on such a useless project? Because I am awesome (first meaning).

Kalith

  • Jr. Member
  • **
  • Posts: 93
    • View Profile
Thank you for your answers!

I think the first answer of this stackoverflow question (the one from Warren Young) describes the situation pretty well for users less familiar to networking concepts as I am ;) I think I can afford the risks associated to using this option.

However, according to the Windows documentation or this IBM article (and if I understand them correctly), the call to setsockopt() should be made on a valid socket before calling bind(), while sf::TcpListener::listen() creates the socket and immediately calls bind() on it. Can this be an issue?
Kal.

Lorizean

  • Newbie
  • *
  • Posts: 14
    • View Profile
Re: "Failed to bind listener socket" when quickly restarting a TCP server
« Reply #5 on: August 03, 2013, 04:06:42 pm »
Hi,

I've got the same problem with my application.

I already tried to implement SO_REUSEADDR like written above but to no avail:

Code: [Select]

class relistener : public sf::TcpListener
{
public:
    void reuse() ;
};

void relistener::reuse()
{
        char reusec = 1;
        setsockopt( getHandle(), SOL_SOCKET, SO_REUSEADDR, &reusec, sizeof( reusec ) );
}

int gameloop()
{
/*...*/

        BINDPORT = 53123;
relistener listener;
listener.setBlocking(false);
std::array<sf::TcpSocket,6> socks;

/*...*/

        unsigned playercount=0;
listener.reuse();
listener.listen(BINDPORT);
while(mgame.init)
{
if(listener.accept(socks[playercount]) == sf::Socket::Done)
{
std::cout << "accepted player" << std::endl;
playercount++;
socks[playercount].setBlocking(false);
}
handle_commands(mgame,socks); // lets one of the players set mgame.init to false
}
listener.close();

/*...*/

return 0;
}

int main()
{
while(true)
{
gameloop();
}

return 0;
}

When the gameloop is finished and tries to rebind a new listener, it tells me that it failed to bind and according to netstat, the port (53123 in this case) is still in TIME_WAIT.
I'm on Linux 3.10.3-1 (Arch Linux) if that matters.

I read about SO_REUSEPORT somewhere, but I can't find a header that implements it.

I hope somebody can help me ;)
« Last Edit: August 03, 2013, 04:17:23 pm by Lorizean »

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
Re: "Failed to bind listener socket" when quickly restarting a TCP server
« Reply #6 on: August 03, 2013, 04:24:34 pm »
Your code fails because the socket is created in the listen function.
Laurent Gomila - SFML developer

Lorizean

  • Newbie
  • *
  • Posts: 14
    • View Profile
Re: "Failed to bind listener socket" when quickly restarting a TCP server
« Reply #7 on: August 03, 2013, 04:31:24 pm »
Hm, I see, but even after calling listen() before reuse(), I still cannot bind to the port the second time round - do I need to somehow set SO_REUSEADDR after creation but before binding?
So I probably have to create my own sockets?

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
Re: "Failed to bind listener socket" when quickly restarting a TCP server
« Reply #8 on: August 03, 2013, 04:38:35 pm »
Quote
do I need to somehow set SO_REUSEADDR after creation but before binding?
Probably, yes.
Laurent Gomila - SFML developer

Lorizean

  • Newbie
  • *
  • Posts: 14
    • View Profile
Re: "Failed to bind listener socket" when quickly restarting a TCP server
« Reply #9 on: August 03, 2013, 04:40:33 pm »
Okay, thanks for the quick answers.