Warning: this page refers to an old version of SFML. Click here to switch to the latest version.

Using a selector

Introduction

As you've seen in the previous tutorial, some socket functions (Accept, Receive) are blocking, meaning that they will stop the whole program if they are used. Also meaning that you will never be able to wait for two different sockets at the same time. One good solution is to use threads : put the blocking socket calls into a separate thread, and your program will still be able to run while the sockets are waiting. But using threads is not easy : it requires good synchronization, can be hard to debug, and can add some overhead if you run a lot of threads.

The other solution is to use selectors. Selectors allow multiplexing on a set of sockets, without having to run another thread. They are still blocking, but they return as soon as at least one of the sockets is ready. Selectors can also use a timeout value, to avoid waiting indefinitely.

Handling multiple clients

There are two types of selectors in SFML : sf::SelectorTCP (for TCP sockets) and sf::SelectorUDP (for UDP sockets). However only the type of socket is different, the functions and behavior are exactly the same for both classes.

So, let's try to use a TCP selector. Here we'll build a server that will be able to handle several clients at the same time, without using any thread.

#include <SFML/Network.hpp>

sf::SelectorTCP Selector;

Selectors act like arrays : you can Add and Remove sockets, or Clear them. Here we will add all our sockets, as we want to be notified when any client is sending something.

Before adding any client into the selector, remember that you must use a listening socket, to wait for incoming connections. As accepting a connection is blocking, we will have to put the listener into the selector, too.

sf::SocketTCP Listener;
if (!Listener.Listen(4567))
{
    // Error...
}

Selector.Add(Listener);

Then you can start an infinite loop that will receive both incoming connections, and messages from connected clients.
To get the sockets that are ready from the selector, you just have to call its Wait function, followed by GetSocketReady :

while (true)
{
    unsigned int NbSockets = Selector.Wait();

    for (unsigned int i = 0; i < NbSockets; ++i)
    {
        sf::SocketTCP Socket = Selector.GetSocketReady(i);

        // Do something with Socket...
    }
}

Wait can take an optional parameter, which is a timeout value in seconds (if you need to use it).

Please note that each time you call Wait, the selector will wait for at least one socket to be ready. So if you call it twice in a loop, don't expect the second call to return instantly, or to get the same sockets.

Let's take a look at what goes inside the loop. Obviously, we'll call Receive on our socket, as it is supposed to be ready to receive. But don't forget that our listening socket is also contained in the selector, and if it is ready, we'll have to accept a connection instead of receving a packet. And if a new client connects, we must add the new socket to the selector.

The loop from above...
{
    // Get the current socket
    sf::SocketTCP Socket = Selector.GetSocketReady(i);;

    if (Socket == Listener)
    {
        // If the listening socket is ready, it means that we can accept a new connection
        sf::IPAddress Address;
        sf::SocketTCP Client;
        Listener.Accept(Client, &Address);
        std::cout << "Client connected ! (" << Address << ")" << std::endl;

        // Add it to the selector
        Selector.Add(Client);
    }
    else
    {
        // Else, it is a client socket so we can read the data he sent
        sf::Packet Packet;
        if (Socket.Receive(Packet) == sf::Socket::Done)
        {
            // Extract the message and display it
            std::string Message;
            Packet >> Message;
            std::cout << "A client says : \"" << Message << "\"" << std::endl;
        }
        else
        {
            // Error : we'd better remove the socket from the selector
            Selector.Remove(Socket);
        }
    }
}

The code for the client is straight-forward : it connects to the server, gets input from the user and sends it to the server. All is included in the downloadable source code.

A "receive-with-timeout" function

As a selector can use a timeout, and as there is no problem putting only one socket into it, we can use it to build a receive function that takes a timeout as an extra parameter. It can be useful for implementing a ping function, to check if a client is still connected.

bool ReceiveWithTimeout(sf::SocketTCP Socket, sf::Packet& Packet, float Timeout = 0)
{
    sf::SelectorTCP Selector;
    Selector.Add(Socket);

    if (Selector.Wait(Timeout) > 0)
    {
        Socket.Receive(Packet);
        return true;
    }
    else
    {
        return false;
    }
}

Conclusion

Selectors are a useful tool for multi-client applications, and are often much more convenient than running multiple threads. Don't hesitate to use them.