SFML community forums

Help => Network => Topic started by: Kalith on May 17, 2013, 11:56:29 pm

Title: "Failed to bind listener socket" when quickly restarting a TCP server
Post by: Kalith on May 17, 2013, 11:56:29 pm
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:
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 :
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.
Title: Re: "Failed to bind listener socket" when quickly restarting a TCP server
Post by: io on May 18, 2013, 05:38:33 am
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?
Title: Re: "Failed to bind listener socket" when quickly restarting a TCP server
Post by: Laurent on May 18, 2013, 08:28:43 am
This is the normal behaviour.

https://github.com/SFML/SFML/issues/150
Title: Re: "Failed to bind listener socket" when quickly restarting a TCP server
Post by: binary1248 on May 18, 2013, 12:39:00 pm
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;
}
Title: Re: "Failed to bind listener socket" when quickly restarting a TCP server
Post by: Kalith on May 18, 2013, 03:37:22 pm
Thank you for your answers!

I think the first answer of this stackoverflow question (http://stackoverflow.com/questions/3229860/what-is-the-meaning-of-so-reuseaddr-setsockopt-option-linux) (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 (http://msdn.microsoft.com/en-us/library/windows/desktop/ms740621%28v=vs.85%29.aspx) or this IBM article (http://www.ibm.com/developerworks/library/l-sockpit/) (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?
Title: Re: "Failed to bind listener socket" when quickly restarting a TCP server
Post by: Lorizean 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 ;)
Title: Re: "Failed to bind listener socket" when quickly restarting a TCP server
Post by: Laurent on August 03, 2013, 04:24:34 pm
Your code fails because the socket is created in the listen function.
Title: Re: "Failed to bind listener socket" when quickly restarting a TCP server
Post by: Lorizean 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?
Title: Re: "Failed to bind listener socket" when quickly restarting a TCP server
Post by: Laurent on August 03, 2013, 04:38:35 pm
Quote
do I need to somehow set SO_REUSEADDR after creation but before binding?
Probably, yes.
Title: Re: "Failed to bind listener socket" when quickly restarting a TCP server
Post by: Lorizean on August 03, 2013, 04:40:33 pm
Okay, thanks for the quick answers.