SDL_net icon indicating copy to clipboard operation
SDL_net copied to clipboard

Detect if a connection is disconnected without reading data?

Open superfury opened this issue 1 year ago • 10 comments

I have a case where I need to detect if a connection is disconnected (e.g. SDL_Net_TCP_Recv with size 1 returns <=0), but without actually receiving data (since the client itself isn't ready to parse it yet). And dropping the data is a big no-go, since that isn't expected of the setup (a virtual dial-up modem line being emulated over TCP).

Is this possible in any way with SDL(2)_net?

superfury avatar Jul 30 '22 14:07 superfury

Does https://wiki.libsdl.org/SDL_net/SDLNet_CheckSockets report the socket has new information in the case of a disconnect? I would check that first and see what happens.

icculus avatar Jul 30 '22 18:07 icculus

Oh, it isn't enough to know something happened, you need the socket to retain new data.

https://wiki.libsdl.org/SDL_net/SDLNet_TCP_Recv

You can probably request zero bytes of data and see if you get -1, but we probably need a better API to ensure this does what you want.

Failing that, you can keep a small buffer in your app that contains bytes read from the socket but not yet given to the app.

icculus avatar Jul 30 '22 18:07 icculus

What I've done right now is the following:

  • It uses the normal CheckSockets function combined with the SDLNet_TCP_Recv with a maximum size of 0 bytes to check for a disconnect. This doesn't seem to report any disconnect when the connection is acknowledged by the SDLNet_TCP_Accept function.
  • It also uses a socketset(size of 1 entry) with the server socket added to it. It then uses SDLNet_CheckSockets on said socket set to check if any inbound connection is still pending. This is still done that way atm, but it doesn't seem to report the requests being lifted (in this case because the telnet application that's 'dialling' to the TCP port has been closed (essentially dropped the dial tone).

superfury avatar Jul 31 '22 09:07 superfury

OK. I've added some simple code to the SDL_net library: Before SDLNet_TCP_Accept in SDLNetTCP.c:

TCPsocket SDLNet_TCP_Accept_clearready(TCPsocket server)
{
    /* Only server sockets can accept */
    if (!server->sflag) {
        SDLNet_SetError("Only server sockets can accept_clearready()");
        return(NULL);
    }
    server->ready = 0;
    return server; //Give it back!
}

int SDLNet_TCP_isready(TCPsocket sock)
{
    return sock->ready; //Is it ready?
}

And in the SDL_net.h (before the same function):

#define SDLNET_TCP_ACCEPT_CLEARREADY
/* Clears the ready flag for detection of an used server socket.
*/
extern DECLSPEC TCPsocket SDLCALL SDLNet_TCP_Accept_clearready(TCPsocket server);

#define SDLNET_TCP_ISREADY
/* Detects the ready flags on the given socket.
*/
extern DECLSPEC int SDLCALL SDLNet_TCP_isready(TCPsocket sock);

Then in my own project's code I called it together with the CheckSockets call for detecting if a connection is being requested:

byte TCPserverincoming() //Is anything incoming?
{
#ifdef GOTNET
	if (NET_READY == 0) return 0; //Not ready!
	TCPServer_INTERNAL_startserver(); //Start the server unconditionally, if required!
	if (Server_READY != 1) return 0; //Server not running? Not ready!

#ifdef SDLNET_TCP_ACCEPT_CLEARREADY
	if (SDLNet_TCP_Accept_clearready(server_socket) == NULL)
	{
		return 0; //Nothing to detect!
	}
#endif
	if (!SDLNet_CheckSockets(serverlistensocketset, 0))
	{
		return 0; //Nothing to connect!
	}

#ifdef SDLNET_TCP_ACCEPT_CLEARREADY
	if (SDLNet_TCP_isready(server_socket)) //Became ready?
	{
		return 1; //Ready for retrieval!
	}
	return 0; //Not ready after all!
#endif

	return 1; //Incoming connection!
#endif
	return 0; //Not supported!
}

But somehow, SDLNet_TCP_isready still reports the ready flag being set, even if the connection isn't being requested anymore?

superfury avatar Jul 31 '22 10:07 superfury

Also I've already tried receiving 0 bytes and checking for -1. It will keep continuing to give a result of 0, never -1. The SDLNet_TCP_Recv will always return 0 in below case (the listensocketset entry is the accepted TCP connection and the mysock entry is the accepted TCP socket itself that's in the socket set for listening to incoming data). It's using the same logic as for receiving data with zero delay, but only reads 0 bytes and checks if it's disconnected (in this case non-zero being checked). That is the 'return 0; //Socket closed' case.

byte TCP_Connected(sword id)
{
#ifdef GOTNET
	const char *error;
	byte data;
	if (id < 0) return -1; //Invalid ID!
	if (id >= NUMITEMS(allocatedconnections)) return 0; //Invalid ID!
	if (!allocatedconnections[id]) return 0; //Not allocated!
	if (!Client_READY[id]) return 0; //Not connected?
	SDLNet_SetError("UNKNOWN!");
	if (SDLNet_CheckSockets(listensocketset[id], 0))
	{
		if (SDLNet_TCP_Recv(mysock[id], &data, 0) != 0) {
			return 0; //Socket closed
		}
		else
		{
			#ifdef SDL2
			error = SDLNet_GetError(); //Any error?
			if (error)
			{
				if (strcmp(error, "UNKNOWN!") != 0) //Not unknown?
				{
					return 0; //Errored out, so disconnected!
				}
			}
			#endif
			return 1; //Got data!
		}
	}
	else return 1; //No data to receive!
#endif
	return 0; //No socket by default!
}

superfury avatar Jul 31 '22 10:07 superfury