Closed Socket is still running!

BlitzMax Forums/BlitzMax Programming/Closed Socket is still running!

Scaremonger(Posted 2007) [#1]
Below is some test code for a small multi-user program I am developing; but I have a problem... If the server closes with clients attached; I do not seem to be able to detect it until I exit the application.

Run this twice on your local system (Load Blitzmax twice), and click on Server on one, and Client on the other.

You can start and stop (ESC) the client (When the server is running), and the server detects this, but stop the server and the client does not notice...

Am I closing the Socket correctly in the Server code?

multiuser.bmx
SuperStrict

Const GS_MODE% = 0
Const GS_CLIENT% = 1
Const GS_SERVER% = 2

Const SERVER_ADDRESS$ = "127.0.0.1"
Const SERVER_PORT% = 31245

Graphics 320 , 200
Global gw% = GraphicsWidth()
Global th% = TextHeight( "A" )
Global state% = GS_MODE
Global quit% = False

'#
Global Server:TSocket
Global Client:TSocket
Global Clients:Tlist = CreateList()

'############################################################
Repeat
	Delay(10)
	Cls
	Select state
	'--------------------
	Case GS_MODE
		SetColor( 255,255,0 )
		centerText( "CLIENT" , 0 , 50 , GW/2 )
		centerText( "SERVER" , GW/2 , 50 , GW/2)
		centerText( "Press ESC to Exit" , 0 , 150 , GW)
		DrawLine( gw/2,0,gw/2,GraphicsHeight() )
		If KeyHit( KEY_ESCAPE ) Then quit = True
		If MouseHit(1) Then
			If MouseX() < gw / 2 Then 
				client = createClient( SERVER_ADDRESS , SERVER_PORT )
				If client Then
					Print "#CLIENT CONNECTED"
					state = GS_CLIENT
				Else
					Print "** ERROR CONNECTING TO SERVER"
				End If
			Else
				server = createServer( SERVER_PORT )
				If server Then 
					Print "#SERVER STARTED"
					state = GS_SERVER
				Else
					Print "** ERROR STARTING SERVER"
				End If
			End If
		End If
	'--------------------
	Case GS_CLIENT
		centerText( "CLIENT" , 0 , TH , GW )
		centerText( "Press ESC to Exit" , 0 , TH*2 , GW )
		If KeyHit( KEY_ESCAPE ) Then 
			state = GS_MODE
			CloseSocket( client )
			Print "#CLIENT STOPPED"
		Else
			'# Check server still connected
'##### THIS DOES NOT APPEAR TO WORK #####
			If Not SocketConnected(client) Then
				Print "<Server connection lost"
				state = GS_MODE
				CloseSocket( client )
			End If
		End If
	'--------------------
	Case GS_SERVER
	Local conn:TSocket
		centerText( "SERVER" , 0 , TH , GW )
		centerText( "Press ESC to Exit" , 0 , TH*2 , GW )
		'# Are we closing the server?
		If KeyHit( KEY_ESCAPE ) Then 
			state = GS_MODE
			CloseSocket( server )
			Print "#SERVER STOPPED"
		Else
			'# Check for NEW connections
			conn = AcceptClient( server )
			If conn Then
				Print ">Client Connected from " + DottedIP(SocketRemoteIP(conn))
				clients.addlast( conn )
			End If
			'# Check connections
			For client = EachIn clients
				If Not SocketConnected(client) Then
					Print "<Client disconnected"
					clients.remove( client )
					CloseSocket( client )
				End If
			Next
		End If
	End Select
	Flip
Until quit

'############################################################
Function centerText( txt$, x%, y%, w% )
DrawText( txt$ , x+( w-TextWidth(txt$)) / 2 , y )
End Function

'############################################################
Function CreateServer:TSocket(port:Int)
	Local server:TSocket = New TSocket
	server = CreateTCPSocket()
	If Not BindSocket(server, port) Or Not SocketListen(server) Then
		CloseSocket(server)
		Return Null
	EndIf
	Return server
EndFunction
	
'############################################################
Function CreateClient:TSocket(address:String, port:Int)
	Local client:TSocket = New TSocket
	client = CreateTCPSocket()
	If Not ConnectSocket(client, HostIp(address), port)
		CloseSocket(client)
		Return Null
	EndIf
	Return client
EndFunction

'############################################################
Function AcceptClient:TSocket(server:TSocket)
Local accepted_socket:TSocket = SocketAccept(server)
Local client:TSocket 
	If Not accepted_socket Return Null
	client = New Tsocket
	client = accepted_socket
	Return client
EndFunction



rdodson41(Posted 2007) [#2]
SocketConnected works strangly and doesn't always seem to work for me. I believe that the only way to truly check if a socket on either end has been closed for sure is to read or write something to it and have it fail. If using the plain socket Send and Recv methods then they will return 0 if there is an error or the socket has been remotely closed. Or if you are using a SocketStream and using the normal WriteInt and ReadInt type functions then they will throw an exception if the socket has been remotely closed.

You should check out my simple network module I made that wraps up some messy network code into an interface similar to the one for BlitzPlus and Blitz3D. The best part is that you can simply use the normal IO functions like WriteInt and ReadInt directly on a client object.

I hope it helps, if you need more explanation just tell.

EDIT: On further observation of your code I see that you did use my module, just changed the code. The reason for it was to wrap up that nasty socket code for ease of use, but do whatever suits you.


Scaremonger(Posted 2007) [#3]
Yeah well spotted, I do use your code module in the full program but when I started getting some odd problems, I created this test program and pulled out a load of code just to figure out where it was going wrong.

SocketConnected() is missing from your wrapper; did you omit it because you had problems with it?

Is this a Blitzmax Bug then?. It seems a bit odd to be creating "Keep-alive" code, when there is a function that should do the job for you.


rdodson41(Posted 2007) [#4]
No see I left it out because it doesn't work as expected. It will return true that the socket is still connected even if the socket really has been closed but there is still data left to be read or written in its buffer. The only way to make sure the socket is closed is to read or write and have it fail:
' a write that will close client on failure

Try
	WriteInt(client, 100)
Catch e:TStreamWriteException
	CloseClient(client)
EndTry

' a read that will close client on failure

Try
	i = ReadInt(client)
Catch e:TStreamReadException
	CloseClient(client)
EndTry

Also I'm glad that someone has used my module because I think it makes writing network code easier and cleaner.


Scaremonger(Posted 2007) [#5]
Thanks.

I've put in something similar already and it works a treat.
Cheers.


Scaremonger(Posted 2007) [#6]
Actually we've been missing something obvious... When the server closes, the clients have to be closed first...

  ...
  '# Are we closing the server?
  If KeyHit( KEY_ESCAPE ) Then 
    state = GS_MODE
    CloseSocket( server )
      For client = EachIn clients
        CloseSocket( client )
      Next
    Print "#SERVER STOPPED"
  Else
  ...



skn3(Posted 2007) [#7]
But what if the server crashes!


Scaremonger(Posted 2007) [#8]
That is picked up as a failed connection, and appears to work as well.