[bmx] UDP Networking Class by JoshK [ 1+ years ago ]

Started by BlitzBot, June 29, 2017, 00:28:43

Previous topic - Next topic

BlitzBot

Title : UDP Networking Class
Author : JoshK
Posted : 1+ years ago

Description : This is a base networking lib on top of which more advanced features can be added.

Code :
Code: blitzmax
SuperStrict

Import brl.socket
Import brl.map

Type TNetNode
	
	Const LOCALIP:Int=2130706433
	
	Global map:TMap=New TMap
	
	Field socket:TSocket
	Field ip:Int
	Field port:Int
	
	Method SendNetMessage:Int(recipient:TNetNode,data:Byte Ptr=Null,size:Int=0)
		If ip<>LOCALIP Return False
		Return sendto_(socket._socket,data,size,0,recipient.ip,recipient.port)
	EndMethod
 	
	Method GetNetMessage:TNetMessage()
		If ip<>LOCALIP Return Null
		Local message:TNetMessage,size:Int,ip:Int,port:Int,buf:Byte[]
		size=socket.ReadAvail()
		If size
			message=New TNetMessage
			message.data=New Byte[size]
			recvfrom_(socket._socket,message.data,size,0,ip,port)
			message.sender=Create(ip,port)
			message.size=message.data.length
			Return message
		EndIf
	EndMethod
 	
	Method WaitNetMessage:TNetMessage(timeout:Int=0)
		Local time:Int
		Local message:TNetMessage
		time=MilliSecs()
		Repeat
			message=GetNetMessage()
			If message Return message
			If timeout
				If MilliSecs()-time>timeout Return Null
			EndIf
			Delay 1
		Forever
	EndMethod
	
	Function Create:TNetNode(ip:Int,port:Int=41000)
		Local netnode:TNetNode
		netnode=Find(ip,port)
		If netnode Return netnode
		netnode=New TNetNode
		netnode.ip=ip
		netnode.port=port
		If ip=LOCALIP
			netnode.socket=CreateUDPSocket()
			If Not BindSocket(netnode.socket,port) Return Null
		EndIf
		map.insert netnode,netnode
		Return netnode
	EndFunction
	
	Function Find:TNetNode(ip:Int,port:Int=41000)
		Local netnode:TNetNode
		netnode=New TNetNode
		netnode.ip=ip
		netnode.port=port
		netnode=TNetNode(map.valueforkey(netnode))
		Return netnode
	EndFunction
	
	Method Compare:Int(o:Object)
		Local netnode:TNetNode
		netnode=TNetNode(o)
		If netnode.ip>ip Return 1
		If netnode.ip<ip Return -1
		If netnode.port>port Return 1
		If netnode.port<port Return -1
		Return 0
	EndMethod
	
EndType

Type TNetMessage
	
	Field sender:TNetNode
	Field data:Byte[]
	Field size:Int
	
	Method ToString:String()
		Return String.fromCString(data)
	EndMethod
	
EndType

Function CreateNetNode:TNetNode(ip:Int=TNetNode.LOCALIP,port:Int=41000)
	Return TNetNode.Create(ip,port)
EndFunction

Function SendNetMessage:Int(sender:TNetNode,recipient:TNetNode,data:Byte Ptr,size:Int)
	Return sender.SendNetMessage(recipient,data,size)
EndFunction

Function GetNetMessage:TNetMessage(recipient:TNetNode)
	Return recipient.GetNetMessage()
EndFunction

Function WaitNetMessage:TNetMessage(recipient:TNetNode,timeout:Int=0)
	Return recipient.WaitNetMessage(timeout)
EndFunction


Comments :


JoshK(Posted 1+ years ago)

 Here is an example of usage:
Code: BASIC
SuperStrict

Framework brl.system

Import "network.bmx"

AppTitle=""

Local client:TNetNode[2]

client[0]=CreateNetNode(HostIp("127.0.0.1"),80)
client[1]=CreateNetNode(HostIp("127.0.0.1"),81)

SendNetMessage(client[0],client[1],"Hello!".toCstring(),7)

Local message:TNetMessage=WaitNetMessage(client[1],1000)
If message Notify message.ToString()



BlitzSupport(Posted 1+ years ago)

 Looks handy, thanks.


Chroma(Posted 1+ years ago)

 Very clever and streamlined.  So how many players could this handle without threading?


BlitzSupport(Posted 1+ years ago)

 Anyone tried using this? The example program creates both client and server, so can 'see' both TNetNodes.However, if you put the server in one program and the client in another, each program has no knowledge of the other TNetNode information.I've just done a little hack that manually creates a TNetNode to represent the opposite party, setting only the other party's IP and port, and seems to work, but does this seem like a sensible approach?SERVER:
Code: BASIC

Import "udpnet.bmx"

Local server:TNetNode = CreateNetNode (HostIp ("127.0.0.1"), 80)

' Hack representing client:

Local client:TNetNode = New TNetNode
client.ip = HostIp ("127.0.0.1")
client.port = 81

If server

	Local netmsg:TNetMessage
	Local msg:String
	
	Repeat
	
		netmsg = WaitNetMessage (server, 30)
		
		If netmsg

			msg = netmsg.ToString ()
			
			Print msg
		
		EndIf
		
	Until msg = "quit"

EndIf
CLIENT:
Code: BASIC

Import "udpnet.bmx"

Local client:TNetNode = CreateNetNode (HostIp ("127.0.0.1"), 81)

' Hack representing server:

Local server:TNetNode = New TNetNode
server.ip = HostIp ("127.0.0.1")
server.port = 80

If client

	Graphics 1024, 768
	
	Repeat
	
		If KeyDown (KEY_LEFT)
			SendNetMessage (client, server, "LEFT".ToCString (), 4)
		Else
			If KeyDown (KEY_RIGHT)
				SendNetMessage (client, server, "RIGHT".ToCString (), 5)
			EndIf
		EndIf
		
		Cls
		
		Flip
		
	Until KeyHit (KEY_ESCAPE)
	
EndIf
I've also taken out the references to LOCALIP, which I assume were just for testing purposes on a single PC, as they'd prevent sending over a network in a real situation:UDPNET.BMX:
Code: BASIC

SuperStrict

Import brl.socket
Import brl.map

Type TNetNode
	
	'Const LOCALIP:Int=2130706433
	
	Global map:TMap=New TMap
	
	Field socket:TSocket
	Field ip:Int
	Field port:Int
	
	Method SendNetMessage:Int(recipient:TNetNode,data:Byte Ptr=Null,size:Int=0)
	'	If ip<>LOCALIP Return False
		Return sendto_(socket._socket,data,size,0,recipient.ip,recipient.port)
	EndMethod
 	
	Method GetNetMessage:TNetMessage()
	'	If ip<>LOCALIP Return Null
		Local message:TNetMessage,size:Int,ip:Int,port:Int,buf:Byte[]
		size=socket.ReadAvail()
		If size
			message=New TNetMessage
			message.data=New Byte[size]
			recvfrom_(socket._socket,message.data,size,0,ip,port)
			message.sender=Create(ip,port)
			message.size=message.data.length
			Return message
		EndIf
	EndMethod
 	
	Method WaitNetMessage:TNetMessage(timeout:Int=0)
		Local time:Int
		Local message:TNetMessage
		time=MilliSecs()
		Repeat
			message=GetNetMessage()
			If message Return message
			If timeout
				If MilliSecs()-time>timeout Return Null
			EndIf
			Delay 1
		Forever
	EndMethod
	
	Function Create:TNetNode(ip:Int,port:Int=41000)
		Local netnode:TNetNode
		netnode=Find(ip,port)
		If netnode Return netnode
		netnode=New TNetNode
		netnode.ip=ip
		netnode.port=port
		'If ip=LOCALIP
			netnode.socket=CreateUDPSocket()
			If Not BindSocket(netnode.socket,port) Return Null
		'EndIf
		map.insert netnode,netnode
		Return netnode
	EndFunction
	
	Function Find:TNetNode(ip:Int,port:Int=41000)
		Local netnode:TNetNode
		netnode=New TNetNode
		netnode.ip=ip
		netnode.port=port
		netnode=TNetNode(map.valueforkey(netnode))
		Return netnode
	EndFunction
	
	Method Compare:Int(o:Object)
		Local netnode:TNetNode
		netnode=TNetNode(o)
		If netnode.ip>ip Return 1
		If netnode.ip<ip Return -1
		If netnode.port>port Return 1
		If netnode.port<port Return -1
		Return 0
	EndMethod
	
EndType

Type TNetMessage
	
	Field sender:TNetNode
	Field data:Byte[]
	Field size:Int
	
	Method ToString:String()
		Return String.fromCString(data)
	EndMethod
	
EndType

Function CreateNetNode:TNetNode(ip:Int,port:Int=41000)
	Return TNetNode.Create(ip,port)
EndFunction

Function SendNetMessage:Int(sender:TNetNode,recipient:TNetNode,data:Byte Ptr,size:Int)
	Return sender.SendNetMessage(recipient,data,size)
EndFunction

Function GetNetMessage:TNetMessage(recipient:TNetNode)
	Return recipient.GetNetMessage()
EndFunction

Function WaitNetMessage:TNetMessage(recipient:TNetNode,timeout:Int=0)
	Return recipient.WaitNetMessage(timeout)
EndFunction



BlitzSupport(Posted 1+ years ago)

 I set this up in two separate MaxIDE instances, side-by-side, so I can see the server Output tab while 'playing' in the client (left/right keys), but I notice some garbage coming into the output now and then, eg:
Code: BASIC
LEFT
LEFTT
LEFTTo
LEFTT
I know packets can be lost, etc (though presumably this would be almost non-existent on localhost), but any ideas as to where the extra characters might be coming from? I don't even send a lower-case 'o' from the client!


Henri(Posted 1+ years ago)

 Hello,size variable seems to remain constant, so if you change this method from the UDPNET.bmx:
Code: BASIC
Type TNetMessage
	
	Field sender:TNetNode
	Field data:Byte[]
	Field size:Int
	
	Method ToString:String()
		Return String.fromCString(data)
	EndMethod
	
EndType
...to this:
Code: BASIC
Type TNetMessage
	
	Field sender:TNetNode
	Field data:Byte[]
	Field size:Int
	
	Method ToString:String()
		Local s:String
		For Local b:Byte = EachIn data
			s:+ Chr(b)
		Next
		Return s
	EndMethod
	
EndType
...it seems to work.-Henri


BlitzSupport(Posted 1+ years ago)

 Hi Henri,Many thanks for taking a look, that does indeed seem to work -- much appreciated!


Hardcoal(Posted 1+ years ago)

 Josh , thanks for simplifying this


Hardcoal(Posted 1+ years ago)

 since i dont know so much about networking ive started investigating josh code.I would like to increase the commands in this example.for example how do you check if a port is already occupied.and how do you close a client.Anyway ill get it one way or the otherits all matter of practice right. [/i]