Detecting a closed socket?
BlitzMax Forums/BlitzMax Programming/Detecting a closed socket?
| ||
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? |
| ||
There's a socket server example for bmx in the archives may help. Aside from that no idea. |
| ||
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. |
| ||
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? |
| ||
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. |
| ||
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. |
| ||
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. |
| ||
I can't tell if you've create a CTP or UDP socket but what about Socketconnected()? |
| ||
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. |