Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Detect if a connection is disconnected without reading data? #58

Open
superfury opened this issue Jul 30, 2022 · 11 comments
Open

Detect if a connection is disconnected without reading data? #58

superfury opened this issue Jul 30, 2022 · 11 comments

Comments

@superfury
Copy link
Contributor

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 superfury changed the title Detect is a connection is disconnected without reading data? Detect if a connection is disconnected without reading data? Jul 30, 2022
@icculus
Copy link
Collaborator

icculus commented Jul 30, 2022

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
Copy link
Collaborator

icculus commented Jul 30, 2022

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.

@superfury
Copy link
Contributor Author

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
Copy link
Contributor Author

superfury commented Jul 31, 2022

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
Copy link
Contributor Author

superfury commented Jul 31, 2022

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
Copy link
Contributor Author

superfury commented Oct 18, 2023

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?

@icculus
Copy link
Collaborator

icculus commented Oct 18, 2023

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.

@superfury
Copy link
Contributor Author

superfury commented Oct 18, 2023

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

@icculus
Copy link
Collaborator

icculus commented Oct 18, 2023

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.

icculus added a commit that referenced this issue Oct 22, 2023
(It defaults to 1 for WinSock and 0 most other places.)

We try to do this no matter what, and we don't care if it fails; in case of
failure, it was either applied to a non-IPv6 socket where it would be
meaningless, or we're just going to have to live with what we got anyhow.

Reference Issue #84.
Reference Issue #58.
@icculus
Copy link
Collaborator

icculus commented Oct 23, 2023

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

@superfury
Copy link
Contributor Author

superfury commented Sep 19, 2024

OK. I just checked the latest commit with my app.

I made my program setup a server socket, which is running properly.
Then I try to connect with a telnet program (the one built into Windows 10).
My program then sees that SDLNet_WaitUntilInputAvailable() becomes non-zero, thus starts alterting the other components of my app that an incoming connection is present (in reality it starts ringing it's emulated Hayes modem that listens to the incoming connection for this case).
Then, when I make telnet disconnect, somehow the polling of the server socket within SDLNet_WaitUntilInputAvailable keeps reading the value 0x100 in the pfd->revents field, which would mean POLLRDNORM was set (which is set for some odd reason, even though the other side has disconnected from what I can test (or another app is triggering port 23 somehow, but I don't see which one that'd be))?
Using windows' 'netstat -q' I clearly see that the port is still in listening mode? So nothing should be connected anymore afaik. Can Windows somehow display pending TCP connection requests?

Edit: Checked a bit deeper now. When I look at the connection through 'netstat -q' on Windows 10, I see the following (the listening server port replaced with 1234 and PC name with ABCD):

  Proto  Local Address          Foreign Address        State
  TCP    0.0.0.0:1234          ABCD:0        LISTENING
  TCP    127.0.0.1:57420        ABCD:1234    FIN_WAIT_2
  TCP    127.0.0.1:1234        ABCD:57420    CLOSE_WAIT
  TCP    [::]:1234             ABCD:0        LISTENING

The behaviour of the server listener is the same, it just polls SDLNet_WaitUntilInputAvailable with the first parameter being the server socket (that's listening to port 1234).
Those FIN_WAIT_2 and CLOSE_WAIT states eventually disappear on their own from the results (probably Windows discarding them for some reason?).
Edit: Accepting the connection and checking the SDLNet_WaitUntilInputAvailable for the connected socket (which is already long disconnected as seen above) reports pfd->events 0x0300 and pfd->revents 0x0002 (which indicates POLLHUP being set only in pfd->revents).
So then it actually doesn't detect the disconnect at first (seeing a normal connection with nothing sent or received from SDL_net), although the function reports 1 as a result, because it counts the errors(POLLHUP/POLLERR/POLLNVAL) and readable(POLLIN) as 'input' for some reason (isn't this supposed to be a negative result instead?).
Then, after some time, the disconnect is actually seen by said functions at some point, after which my app closes the non-existant stream. After that the server also doesn't report the incoming connection anymore.

So obviously the server socket is somehow read incorrectly from Windows at least? Does this happen on other platforms as well, or is it just a weird Windows issue?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants