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

Author Topic: SocketSelector wait() doesn't return true after a client TcpSocket disconnect  (Read 5089 times)

0 Members and 1 Guest are viewing this topic.

barisdoga

  • Newbie
  • *
  • Posts: 4
    • View Profile
Hello, I have written a simple code to show the situation and also attaching console output. On the server side I just call TcpSocket::disconnect() for a client socket but SocketSelector wait always returns false but the client SocketSelector can detect the disconnection.

I am not sure this is an expected behaviour.

Thanks in advance!

Using SFML 2.6.0 official release on Windows 10 Machine

sf::TcpListener tcpListener;
sf::SocketSelector socketSelector;
sf::TcpSocket clientSocket;
std::mutex lock;
bool disconnectClientSocket = false;

void Server()
{

        sf::Socket::Status tcpStatus = tcpListener.listen(LOGIN_SERVER_PORT);
        if (tcpStatus != sf::Socket::Done)
        {
                std::cout << "[Server] TcpListener couldn&#39;t be initialized!" << std::endl;
                return;
        }
        else
        {
                std::cout << "[Server] TcpListener initialized!" << std::endl;
        }

        sf::Socket::Status acceptStatus = tcpListener.accept(clientSocket);
        if (acceptStatus == sf::Socket::Done)
        {
                std::cout << "[Server] A new client is accepted!" << std::endl;
                socketSelector.add(clientSocket);

                std::cout << "[Server] Starting to listen with socket selector!" << std::endl;
                while (true)
                {
                        lock.lock();
                        if (socketSelector.wait(sf::milliseconds(1000)))
                        {
                                std::cout << "[Server] socketSelector.wait() returns true" << std::endl;

                                if (socketSelector.isReady(clientSocket))
                                {
                                        std::cout << "[Server] socketSelector.isReady(clientSocket) returns true" << std::endl;
                                }
                                else
                                {
                                        std::cout << "[Server] socketSelector.isReady(clientSocket) returns false" << std::endl;
                                }
                        }
                        else
                        {
                                std::cout << "[Server] socketSelector.wait() returns false" << std::endl;
                        }
                        lock.unlock();

                        Sleep(100);

                        if (disconnectClientSocket)
                        {
                                std::cout << "[Server] Disconnecting client socket" << std::endl;
                                clientSocket.disconnect();
                                disconnectClientSocket = false;
                        }
                }
        }
       
}

void Client()
{
        sf::SocketSelector selector;
        sf::TcpSocket tcpSocket;
        sf::Socket::Status status = tcpSocket.connect(GSO_SERVER_IP, LOGIN_SERVER_PORT, sf::milliseconds(1000));
        if (status != sf::Socket::Done)
        {
                std::cout << "[Client] Connection to server failed!" << std::endl;
                return;
        }
        else
        {
                std::cout << "[Client] Connected to server!" << std::endl;
        }

        selector.add(tcpSocket);
       
        bool waiting = true;
        while (waiting)
        {
                if (selector.wait())
                {
                        std::cout << "[Client] SocketSelector returns true!" << std::endl;
                        if (selector.isReady(tcpSocket))
                        {
                                std::cout << "[Client] SocketSelector tcpSocket is ready!" << std::endl;
                                waiting = false;
                        }
                        else
                        {
                                std::cout << "[Client] SocketSelector tcpSocket is not ready!" << std::endl;
                        }
                }
                else
                {
                        std::cout << "[Client] SocketSelector returns false!" << std::endl;
                }
                Sleep(100);    
        }
}


int main()
{
        std::thread s(Server);
        Sleep(1000);
        std::thread c(Client);

        while (true)
        {
                int i;
                std::cin >> i;
                if(i == 0)
                {
                        lock.lock();
                        std::cout << "[MainThread] Lock is locked. Sending disconnect command!" << std::endl;
                        disconnectClientSocket = true;
                        lock.unlock();
                }
        }

        return 0;
}


[Server] TcpListener initialized!
[Server] A new client is accepted!
[Server] Starting to listen with socket selector!
[Client] Connected to server!
[Server] socketSelector.wait() returns false
[Server] socketSelector.wait() returns false
[Server] socketSelector.wait() returns false
[Server] socketSelector.wait() returns false
0
[Server] socketSelector.wait() returns false
[MainThread] Lock is locked. Sending disconnect command!
[Server] Disconnecting client socket
[Server] socketSelector.wait() returns false
[Client] SocketSelector returns true!
[Client] SocketSelector tcpSocket is ready!
[Server] socketSelector.wait() returns false
[Server] socketSelector.wait() returns false
[Server] socketSelector.wait() returns false
[Server] socketSelector.wait() returns false
[Server] socketSelector.wait() returns false
[Server] socketSelector.wait() returns false
[Server] socketSelector.wait() returns false
« Last Edit: October 11, 2023, 04:23:20 pm by barisdoga »

barisdoga

  • Newbie
  • *
  • Posts: 4
    • View Profile
With a simple modification I have tested the same problem on client side disconnecting tcpSocket the result is same. Client SocketSelector can't detect the disconnect but servers SocketSelector can.

sf::TcpListener tcpListener;
sf::SocketSelector socketSelector;
sf::TcpSocket clientSocket;
std::mutex lock;
bool disconnectClient = false;

void Server()
{
        sf::Socket::Status tcpStatus = tcpListener.listen(LOGIN_SERVER_PORT);
        if (tcpStatus != sf::Socket::Done)
        {
                std::cout << "[Server] TcpListener couldn&#39;t be initialized!" << std::endl;
                return;
        }
        else
        {
                std::cout << "[Server] TcpListener initialized!" << std::endl;
        }

        sf::Socket::Status acceptStatus = tcpListener.accept(clientSocket);
        if (acceptStatus == sf::Socket::Done)
        {
                std::cout << "[Server] A new client is accepted!" << std::endl;
                socketSelector.add(clientSocket);

                std::cout << "[Server] Starting to listen with socket selector!" << std::endl;
                while (true)
                {
                        lock.lock();
                        if (socketSelector.wait(sf::milliseconds(1000)))
                        {
                                std::cout << "[Server] socketSelector.wait() returns true" << std::endl;

                                if (socketSelector.isReady(clientSocket))
                                {
                                        std::cout << "[Server] socketSelector.isReady(clientSocket) returns true" << std::endl;
                                }
                                else
                                {
                                        std::cout << "[Server] socketSelector.isReady(clientSocket) returns false" << std::endl;
                                }
                        }
                        else
                        {
                                std::cout << "[Server] socketSelector.wait() returns false" << std::endl;
                        }
                        lock.unlock();
                        Sleep(100);
                }
        }

}

void Client()
{
        sf::SocketSelector selector;
        sf::TcpSocket tcpSocket;
        sf::Socket::Status status = tcpSocket.connect(GSO_SERVER_IP, LOGIN_SERVER_PORT, sf::milliseconds(1000));
        if (status != sf::Socket::Done)
        {
                std::cout << "[Client] Connection to server failed!" << std::endl;
                return;
        }
        else
        {
                std::cout << "[Client] Connected to server!" << std::endl;
        }

        selector.add(tcpSocket);

        bool waiting = true;
        while (waiting)
        {
                lock.lock();
                if (selector.wait(sf::milliseconds(1000)))
                {
                        std::cout << "[Client] SocketSelector returns true!" << std::endl;
                        if (selector.isReady(tcpSocket))
                        {
                                std::cout << "[Client] SocketSelector tcpSocket is ready!" << std::endl;
                                waiting = false;
                        }
                        else
                        {
                                std::cout << "[Client] SocketSelector tcpSocket is not ready!" << std::endl;
                        }
                }
                else
                {
                        std::cout << "[Client] SocketSelector returns false!" << std::endl;
                }
                lock.unlock();
                Sleep(100);
                if (disconnectClient)
                {
                        std::cout << "[Client] Disconnecting tcp socket" << std::endl;
                        tcpSocket.disconnect();
                        disconnectClient = false;
                }
        }
}

int main()
{
        std::thread s(Server);
        Sleep(1000);
        std::thread c(Client);

        while (true)
        {
                int i;
                std::cin >> i;
                if(i == 0)
                {
                        lock.lock();
                        std::cout << "[MainThread] Lock is locked. Sending disconnect command!" << std::endl;
                        disconnectClient = true;
                        lock.unlock();
                }
        }

        return 0;
}

[Server] TcpListener initialized!
[Client] Connected to server!
[Server] A new client is accepted!
[Server] Starting to listen with socket selector!
[Client] SocketSelector returns false!
[Server] socketSelector.wait() returns false
[Client] SocketSelector returns false!
[Server] socketSelector.wait() returns false
[Client] SocketSelector returns false!
[Server] socketSelector.wait() returns false
[Client] SocketSelector returns false!
[Server] socketSelector.wait() returns false
0
[Client] SocketSelector returns false!
[Server] socketSelector.wait() returns false
[MainThread] Lock is locked. Sending disconnect command!
[Client] SocketSelector returns false!
[Client] Disconnecting tcp socket
[Server] socketSelector.wait() returns true
[Server] socketSelector.isReady(clientSocket) returns true
[Client] SocketSelector returns false!
[Client] SocketSelector returns false!
[Server] socketSelector.wait() returns true
[Server] socketSelector.isReady(clientSocket) returns true
[Client] SocketSelector returns false!
[Server] socketSelector.wait() returns true
[Server] socketSelector.isReady(clientSocket) returns true

barisdoga

  • Newbie
  • *
  • Posts: 4
    • View Profile
One more thing to note here is that, after TcpSocket::disconnect() is called, the wait(1000) starts to return without waiting the timeout value.

barisdoga

  • Newbie
  • *
  • Posts: 4
    • View Profile
I have investigated the issue in more depth;

It seems like calling disconnect() causes TcpSocket to go INVALID_SOCKET therefore wait() function return false due to winapi select() returning error(-1) because it still sees a socket is valid and returning error.

Calling TcpSocket::disconnect() first and then calling SocketSelector::remove() also doesn't remove it because the socket goes INVALID_SOCKET state resulting wait() return false.

The conculusion is to remove the socket from selector before disconnecting it. But then you can not receive the disconnection data from SocketSelector...
« Last Edit: October 11, 2023, 11:42:54 pm by barisdoga »

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 11030
    • View Profile
    • development blog
    • Email
The SocketSelector just does a socket select, which basically waits for a socket to become readable.
A client disconnecting from a socket doesn't really make the socket readable, so the selector correctly doesn't notify of any sockets being ready.

Then then however still leaves you with the problem of having sockets in the selector that are potentially disconnected already. TCP is a very old protocol designed around problems from the 70s, as such there's no proper defined "disconnect" state or event and as such SFML also doesn't really provide anything specifically in that direction.

You could try to read from the sockets in the selector and it would return a disconnected status, but if you're in a blocking setup, you might risk blocking on the read if the connection is still alive (hence why you're already using a selector).

The alternative is to define a custom connection timeout. If a client isn't sending any updates within a given time frame, then the connection is closed from the server side.
This means, you'll have to track the last read times for each socket and implement a sort of keep-alive protocol, so ensure that the connection is kept open, even if you don't have any content to send.

Maybe if you write code for one specific platform, there would be additional ways to detect a disconnect in more details, but one cannot stress enough that SFML's network module is very outdated in terms of network programming, and for anything more serious, one should consider ASIO or similar.
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/