Detecting a closed socket?

BlitzMax Forums/BlitzMax Programming/Detecting a closed socket?

SculptureOfSoul(Posted 2006) [#1]
Man, things haven't been going smooth over here lately. :/

Here's my latest snag.

My server loops through all active sockets and calls ReadAvail() which returns the # of bytes the socket can receive. It returns 0 if there is no activity.

If it is greater than 0, I call Recv() on the socket and get the data. When a client socket closes Recv returns 0, and you normally would use a simple if to detect this and then handle closing the socket server side. The problem here is that ReadAvail() returns 0 also when the socket sends its close message. This means Recv() is never called and I can't detect if the socket is closed.

Getting rid of my ReadAvail() call fixes that problem, but then Recv() is going to get called on every connection and whenever it hits a connection with no data it'll return 0 and then close the connection.

As far as I understand, I need to call ReadAvail() to see if I should call Recv(), but ReadAvail() is masking the notification of a socket closing.

Any ideas?


Who was John Galt?(Posted 2006) [#2]
There's a socket server example for bmx in the archives may help. Aside from that no idea.


SculptureOfSoul(Posted 2006) [#3]
Well, I figured out how to get around it. I'm no networking expert (by far), but I don't see why the method I came across isn't part of BMax standard.

I'll post working code in a bit.


tonyg(Posted 2006) [#4]
I'm no clarevoyant (by far) but I can't see whether the method you came across is part of Bmax standard.
Anyway, really need to offer some code but, at a guess, can't you use the autoclose feature of CreateSocketStream?


SculptureOfSoul(Posted 2006) [#5]
Well, the problem is using the built in functions with MAX, you really can't detect a client socket closing. Autoclose only works to close a socket that is garbage collected. The problem for me is, I've got an open socket for every client that connects to my server, but these won't autoclose if the client disconnects. Further, with the way things were my server couldn't receive the closing message and so never knew if a connection was actually alive or not (I could use a ping of sorts, and will end up doing so for potentially faulty connections (i.e. they haven't been able to receive in 15 seconds), but this shouldn't be necessary for connections that close normally and infact send a packet notifying you that they've closed!)

There are two ways, by default, that max provides you with to detect activity on a socket. Sock.ReadAvail() returns the # of bytes available to receive. Under the hood it's calling ioctl (or ioctlsocket() in Windows). A method strongly discouraged by many (from what I've read.)

The only other way in BMax to really tell if there is data available on a socket is to call Sock.Recv(). This normally returns -1 if nothing is available (and sets the error code to WSAEWOULDBLOCK), it returns 0 if the socket is closing, or otherwise returns the number of the bytes it actually read into the buffer provided.

For some reason the standard BMax socket module won't return -1 if nothing is available, but instead returns 0.
Method Recv( buf:Byte Ptr,count,flags=0 )
		Local n=recv_( _socket,buf,count,flags )
		If n<0 Return 0
		Return n
	End Method


Not sure why that is - but that prevents you from simply calling Recv() all the time and actually differentiating between 0 bytes to receive and a closed connection. Calling Recv() on all of your connections, even when there's nothing to receive, is also a bad practice.

The ideal approach (well, given that Bmax is using sockets 1.1) would either be Asynchronous sockets or the Select() method. I've ended up hacking together a Select() method - but due to the fact that a certain macro (FD_ISSET) isn't wrapped in any way available the code I ended up with is ugly.

Very ugly. It's so ugly I want to punch someone. I'm going to try and clean it up and then post it.


SculptureOfSoul(Posted 2006) [#6]
Okay, this is a huge and ugly behemoth. I should, and probably will break this into separate functions - yet at the same time I'm trying to reduce the overhead as much as possible. I'm not sure that this method is faster than calling .ReadAvail() on every socket, but I do know this method works and that it *does* detect closed sockets.

I'm going to put the code in it's own post since it's so large.


SculptureOfSoul(Posted 2006) [#7]
Method Listen()

        Const MAXCONNECTIONS = 1000
	global read_set:Int[MAXCONNECTIONS]
	Local count:Int = 0                 'the total number of current connections
	Local num_active:Int                'the number that have activity this frame
	Local readptr:Int Ptr               'will end up pointing to the read_set[]
	
	'--------------------Build the ReadSet--------------------------------------
	For Local conn:TConnection = EachIn connection_list    'connection_list is a global list of all connections
		
		read_set[count] = conn._socket
		readptr = read_set 
		count:+ 1						'get an accurate count of total conn's
			
	Next
	'---------------------------------------------------------------------------	
	'------------------Call select, get number of active------------------------
	'---------------------------------------------------------------------------
	num_active = select_( count , readptr,0,Null,0,Null,timeout )
	
	'------------if we've got any activity, we've got to act on it--------------
	If num_active <= 0
		Return False
	Else
		?debug
		Print "Num active sockets: " + num_active
		Print "Total count: " + count
		?
		
		Local activity_list:Int[] = New Int[num_active]
		Local activity_count:Int  = 0
		
		
		'------------------This is a hack to help emulate FD_ISSET-------------
		'------------------this builds the activity list ----------------------
		'-------------------a list of connections with activity----------------	
		'----------------------------------------------------------------------	
		For Local iter = 0 Until count	
			
			If read_set[iter] <> 0
				activity_list[activity_count] = read_set[iter]
				activity_count:+ 1
				num_active:- 1
				
				If num_active = 0
					?debug
					Print "Total # iterations: " + (iter + 1)
					?
					Continue	'break out of the loop if we've counted all active connections
				EndIf
			EndIf
		Next
		
		'-----------------------------------------------------------------------
		'-----------Test each connection against the activity list--------------
		'-----------------------------------------------------------------------	
		For Local conn2:TConnection = EachIn connection_list
			For Local iter2 = 0 Until activity_count
				If conn2._socket = activity_list[iter2]
					?debug
					Print "Calling receive!"
					?
					ConnectionReceive( conn2 )    'Replace with your own TSocket.Recv() call.
				EndIf
			Next
		Next
		'-----------------------------------------------------------------------
	EndIf

EndMethod


For all intents and purposes, the TConnection in the code is a TSocket (it actually extends TSocket).

I don't have the time right now to fully explain the code, although if you know how Select() based polling works it might, hopefully be at least semi-understandable. It is ugly, and fairly poorly commented. I apologize for that.


tonyg(Posted 2006) [#8]
I can't tell if you've create a CTP or UDP socket but what about Socketconnected()?


SculptureOfSoul(Posted 2006) [#9]
It's a TCP socket there. SocketConnected() only works when you explicitly call Connect() on a socket (the client socket when it connects to the server) but doesn't return true when you have a passive TCP socket (the socket the server creates when it detects a new incoming connection.)

Even if it would work it's still more unnecessary overhead.

I do appreciate all the suggestions though, even though the tone of my posts might not have indicated such. It's just been a long long day.