libsdl-org / SDL_net

A simple, cross-platform wrapper over TCP/IP sockets.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Detect if a connection is disconnected without reading data?

superfury opened this issue · 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?

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.

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.

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).

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?

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!
}

Hmmm... I've just looked at the new SDL3_net code.

Could it be that with my TCPServer_incoming() function (of course everything ported to SDL3) that can be properly handled now on SDL3_net?

Like (according to my understanding):

void *serv_socket[1];
serv_socket[0] =  server_socket; //What socket to check: check for incoming connections!
if (SDLNet_WaitUntilInputAvailable(&serv_socket,1,0) //Incoming connection?
{
	return 1;//Incoming connection!
}
return 0; //Nothing to connect!

That would allow (in my case, other virtual software modem developers in the same way) allow for checking of the incoming connection without accepting it (giving a ringtone on the receiver's end) and the connection not receiving any data before the receiver's end is ready?

Am I understanding this correctly? Ofc I also don't see any SDL2_net to SDL3_net migration guide I think?

Like (according to my understanding):

Yes, this is how it works; it'll tell you there's at least one connection, without actually accepting it, and it can be checked without blocking.

Ofc I also don't see any SDL2_net to SDL3_net migration guide I think?

It's a complete rewrite of SDL_net, with a totally different API. You can see the motivations for it in #77, and some basic programs are in the "examples" directory.

But there isn't really a migration guide, because it's pretty different.

I've looked at the documentation of the functions and based on that modified my SDL2_net code to support SDL3_net as well. The basics are similar enough, just most of it being simplified (less memory buffer allocations and function calls). The basic framework (adjusting for some type changes) of my code can still work (https://bitbucket.org/superfury/commonemuframework/src/default/support/tcphelper.c ). It's roughly based on a modified version of Dosbox's connecting and server code (although extended for multiple incoming connections).

It seems to work with the default case (IPv6 connecting to dual stack IPv4/v6 server). But when I connect using IPv4 it can't connect somehow? I've debugged into SDL3_net's code, but neither the server socket (using SDLNet_WaitUntilInputAvailable(&serverlistensocketset, 1, 0) ) nor directly (using (SDLNet_AcceptClient(server_socket, &new_tcpsock) != 0) ) see the incoming connection somehow? Is it failing with dual-stack IPv4/v6 connections when trying to connect using IPv4?

Other than that weird IPv4/v6 dual stack connecting though IPv4 issue, it's working properly with that basic setup (running as single client/server 'modem' emulation as well as single client with the server being 256 clients (raw TCP connections supported)).

We don't explicitly turn off the IPV6_V6ONLY flag at the moment...on Linux, this defaults to off (dual-stack support), but WinSock defaults it to on (no dual-stack support). This is a bug, we intend to fix it.

Dual-stack sockets should be enabled on all platforms, as of the latest in revision control.