Sockets API a bit confusing ...

Monkey Forums/Monkey Programming/Sockets API a bit confusing ...

Shinkiro1(Posted 2013) [#1]
Or maybe it's just me. But it's really unclear if a parameter is used to be written to or actually a read only parameter for the method.

Specifically I am doing some UDP stuff and yes I have looked at the udp_echoserver example but that confuses -.-

Has somebody maybe a really simple UDP client server sample?


benmc(Posted 2013) [#2]
I did get a Client/Server TCP system working between multiple Android devices. However, I'm having trouble because I can't seem to tell when a Client disconnects, and I can't restart or stop the server, and it seems like everything just crashes when something goes wrong and I can't seem to figure out how to do any error handling. I think I'm just not very familiar with the Async stuff. At any rate, here is the code that works for my little tcp client/server. (Plan to do the same with UDP because I read it's a little more forgiving)

Lots of bugs in this, and it's just a simple test, and you have to know the IP address and Port of the device that will be the server because I can't figure out how to get the IP of the device so I know how to connect to another device automatically.

I'm only posting this because I hope someone can lead us in the right direction, and by no means is this production ready or bug free...


#rem

Connect a client to a server.
Send the mouse or touch coords back and forth and display a circle in that spot on both screens.

Hardcode the server's IP into the code below.

Run on the Client Device
Run on the Server Device
Touch the word SERVER at the top on the Server device.
Touch the word CLIENT at the top on the Client device.
Start clicking/touching the screens and see the dots move around.

Works (kind-of) for me :/

#end

Import mojo

Import brl.socket


Const SERVER_IP:String = "192.168.1.129" ' Change to your Server device's IP
Const SERVER_PORT:Int = 12345 ' Change to your own

Const SOCKET_NONE = 0
Const SOCKET_SERVER = 1
Const SOCKET_CLIENT = 2

Global msgStr:String = ""
Global serverStr:String = "0,0"
Global clientStr:String = "0,0"
Global isServer:Int = 0
Global socketType:Int = SOCKET_NONE

Global hasMsg:String = "No messages"

Global killClient:Int = 0

Global clickX:Int = 0
	Global clickY:Int = 0	
	Global serverX:Int = 0
	Global serverY:Int = 0
	Global clientX:Int = 0
	Global clientY:Int = 0

Class TcpEchoServer Implements IOnAcceptComplete

	Method New( port:Int )
		_socket=New Socket( "server" )
		If Not _socket.Bind( "",port ) Then
			hasMsg = "Server IP Bind Failed"
			Return
		Else
			hasMsg = "Bind Successful"			
		End If
		_socket.AcceptAsync( Self )
		
	End
	
	Private
	
	Field _socket:Socket
	Field clients:TcpEchoServerClient[10]
	Field onClient:Int = 0
	
	
	Method OnAcceptComplete:Void( socket:Socket,source:Socket )
		If Not socket Then
			hasMsg = "Server Client Connection Accept Error" ' "Accept Error"
			Return 
		End If		
		hasMsg = "TcpEchoServer: Accepted client connection"
		clients[onClient] = New TcpEchoServerClient( socket )
		onClient = onClient + 1
	End
	
	
End

Class TcpEchoServerClient Implements IOnSendComplete,IOnReceiveComplete

	Method New( socket:Socket )
		_socket=socket
		_socket.ReceiveAsync _data,0,_data.Length,Self
	End
	
	Private
	
	Field _socket:Socket
	Field _data:=New DataBuffer( 1024 )
	
	Method OnReceiveComplete:Void( data:DataBuffer,offset:Int,count:Int,source:Socket )
	
		If Not count Or killClient = 1 Then
			hasMsg = "TcpEchoServer: Closing client connection"
			_socket.Close()
			killClient = 0
			Return
		Endif
		'Print "received " + offset + ", " + count + ", " + data.PeekString( offset,count )
		clientStr = data.PeekString( offset,count )
		Local sstr:String[2]
		sstr = clientStr.Split(",")
		If sstr.Length=2 Then
			clientX = Int(Float(sstr[0]))
			clientY = Int(Float(sstr[1]))			
		End If
		msgStr = serverStr+":"+clientStr		
		data.PokeString(0,serverStr)
		_socket.SendAsync data,0,serverStr.Length(),Self ' offset
	End

	Method OnSendComplete:Void( data:DataBuffer,offset:Int,count:Int,source:Socket )
		_socket.ReceiveAsync _data,0,_data.Length,Self
	End
	
End

Class MyApp Extends App Implements IOnConnectComplete,IOnSendComplete,IOnReceiveComplete

	Field _server:TcpEchoServer
	
	Field _socket:Socket
	Field _data:=New DataBuffer( 1024 )
	
	Field onSocketTry:Int = 0
		
	Method OnCreate()
	
		socketType = SOCKET_NONE
		
		msgStr = serverStr + "," + clientStr
				
		SetUpdateRate 60
		
		
	End
	
	Method OnUpdate()
		UpdateAsyncEvents
		
				
			
		If MouseHit() Then
		
		
			Local mx:Float = MouseX()
			Local my:Float = MouseY()
			
			If my < 30 Then
			
				If mx >= 0 And mx <= 92 Then
				
					If socketType = SOCKET_NONE Then
						socketType = SOCKET_SERVER
						_server=New TcpEchoServer( SERVER_PORT )										
					End If
					
				Elseif mx > 92 And mx<= 192 Then
				
					If socketType = SOCKET_NONE Then
						socketType = SOCKET_CLIENT
						_socket=New Socket( "stream" )
						_socket.ConnectAsync SERVER_IP,SERVER_PORT,Self
					End If
				
				End If
				
								
				
			
			End If				
		
		Elseif MouseDown() Then
		
			clickX = MouseX()
			clickY = MouseY()
			
			If socketType=SOCKET_CLIENT Then
			
				clientX = clickX
				clientY = clickY
			
				clientStr = clickX + "," + clickY
				msgStr = serverStr+":"+clientStr
			
			Elseif socketType=SOCKET_SERVER Then
			
				serverX = clickX
				serverY = clickY
			
				serverStr = clickX + "," + clickY	
				msgStr = serverStr+":"+clientStr			

			End If			
		
		End If
			
		
	End
	
	Method OnRender()
		Cls
		If socketType=SOCKET_SERVER Then
			DrawTextNormal "SERVER ON PORT " + SERVER_PORT,10,10			
		Elseif socketType=SOCKET_CLIENT Then
			DrawTextNormal "CLIENT",10,10			
		Else
			DrawTextNormal "Server            Client",10,10
		End If
		
		DrawTextNormal msgStr,10,30
		
		DrawTextNormal hasMsg, 10, 50
		
		DrawTextNormal MouseX() + "," + MouseY(), 10, 70
		
		If serverX>0 Or serverY>0 Then
		
			SetColor 255,0,0
			DrawCircle serverX,serverY,20
			
		End If
		
		If clientX>0 Or clientY>0 Then
		
			SetColor 0,255,0
			DrawCircle clientX,clientY,20
			
		End If
		
		
	End
	
	
	
	Method SendMore:Void()
		
		' we send our coords to the server
		Local n:=_data.PokeString( 0, clickX + "," + clickY )
		_socket.SendAsync _data,0,n,Self
		
	End
	
	Method OnConnectComplete:Void( connected:Bool,source:Socket )
		If Not connected Then
			hasMsg = "Error Connecting"
			_socket.Close()
			Return
		Else
			SendMore
		End If
	End
	
	Method OnSendComplete:Void( data:DataBuffer,offset:Int,count:Int,source:Socket )
		_socket.ReceiveAsync _data,0,_data.Length,Self
	End

	Method OnReceiveComplete:Void( data:DataBuffer,offset:Int,count:Int,source:Socket )
		' The server always just sends back its coords, attach ours
		serverStr = data.PeekString( offset,count )
		Local sstr:String[2]
		sstr = serverStr.Split(",")
		If sstr.Length=2 Then
			serverX = Int(Float(sstr[0]))
			serverY = Int(Float(sstr[1]))			
		End If
		msgStr = serverStr + ":" + clientStr
		SendMore
	End
	
	
	
	
End

Function Main()

	New MyApp
	
End




Shinkiro1(Posted 2013) [#3]
Thanks for the code ben.
I still can't wrap my head around it. Especially the OnSomething/Async Methods.

Also while UDP seems to be the way to go for fast paced games, it seems you can get through with tcp if your game is not in that category.
At least you don't have to worry about the order of packets that way.


benmc(Posted 2013) [#4]
Agreed. UDP looks really similar to TCP setup as far as the EchoServer goes, I'm trying to convert this to UDP for my game now too.

What confuses me is the creation of the Client inside the server. It looks like it creates the Client variables, and somehow those persist forever, even if the client creates the TcpEchoServerClient as a local variable.

It makes no sense to me either, but this is as far as I've gotten so far. My other posts about the matter have had no responses, so I fear it's something very few have worked with yet.


marksibly(Posted 2013) [#5]
> Or maybe it's just me. But it's really unclear if a parameter is used to be written to or actually a read only parameter for the method.

If you mean the 'address' parameter of SendTo and ReceiveFrom, then:

* The address you pass to SendTo must be initialized before the call - this is how SendTo knows where to send the message.

* ReceiveFrom fills in the address with the address the message came from. The address you pass to ReceiveFrom can either be a new 'empty' address object created with 'New SocketAddress', or an existing address object you don't need any more.

The example UdpEchoServer code uses a single SocketAddress object for all reads/writes. When a message arrives at the server and the server OnReceiveFromComplete is called, it simply uses the same address object to 'echo' back the message with SendToAsync.

> What confuses me is the creation of the Client inside the server. It looks like it creates the Client variables, and somehow those persist forever, even if the client creates the TcpEchoServerClient as a local variable.

This is just the 'magic' of garbage collection at work.

The New() method of TcpEchoServerClient calls ReceiveAsync before returning, so the async system is effectively 'hanging on' to the object for you. It needs to hang on to it in order to be able to send it the receive result later.

Ditto, once a result has been received, the OnReceive code replies with SendAysnc, which will also keep the client 'alive'- and so on etc. Eventually, when the external client closes the socket, ReceiveAsync will receive a count of '0' indicating 'end of stream', in which case it DOESN'T call receive (or send!), it just returns and will eventually be garbage collected.

In a real world program, you may well want to build a proper list of connected clients into the server though.