UDP-Network-Library

Blitz3D Forums/Blitz3D Userlibs/UDP-Network-Library

Xaron(Posted 2005) [#1]
Hi all!

As I write a little multiplayer game but only have access to a linux server (which I program in C++), I looked for a network library, that works under Windows (using Blitzbasic and C/C++) _and_ Linux (of course C/C++ only).

As a base I used the free ENet-UDP-Networklibrary, which was used for the FPS Cube. For further infos, look here:

http://enet.cubik.org/Features.html

My wrapper lib including a little BB-client/server-example can be downloaded here:

http://www.leidel.net/dl/bbenet.zip

ATTENTION: This is the very first version, faults by my side are possible!

Actually there is no real documentation, only the examples and the interface description. I will make a better documentation soon - hope so. ;)

Don't hesitate to ask questions or give me hints to make things better!

Thanks and regards - Xaron


PsychicParrot(Posted 2005) [#2]
Works great so far :) Nice work!


KuRiX(Posted 2005) [#3]
This is really interesting. Any idea of the stablility state of this library and the wrapper?


Xaron(Posted 2005) [#4]
@KuRiX:

The stability state of the eNet-library and the wrapper is: rocksolid. *g* It's quite really stable and fast. Ok, I did not stress test it, but it should work great.

Some things may be could be done better in the wrapper, especially some interface definitions...

Regards - Xaron


Mahan(Posted 2009) [#5]
Hi Guys,

Sorry to be a necromancer but I just wanted to share my very important security update of the enet for Blitz3D and BlitzPlus.

Credits still go to Xaron for creating this awesome DLL in the first place!


Reason for the update:

WARNING:
The old .dll offers a possibility for a malicious peer to send a package bigger than the buffer at an ordinary peer, causing crashes or even potentially execution of remote code!

http://download.ecma.webfactional.com/bbenet_reloaded.zip

The package looks almost identical to Xarons, and the source is of course included in this release also.

The DLL was rebuilt with VS2008.

Usage note:

The interface of this rebuild is backwards compatible with the original but I added a replacement function to mitigate the security vulnerability.

What you need to do is to replace all calls to this function ...
ENetCheckEvents%(peerID*, dataSize*, bank*):"_ENetCheckEvents@12"

... with calls to this function:
ENetCheckEventsSafe%(peerID*, dataSize*, bank*):"_ENetCheckEventsSafe@12"

From the updated docs:

-------------------------
ENetCheckEventsSafe%(peerID*, dataSize*, bank*):"_ENetCheckEventsSafe@12"
-------------------------

Returns any event data. To be used into the ENetDoEventCheck loop.

Example:
bank = CreateBank(128)
peerId = CreateBank(4)
dataSize = CreateBank(4)
PokeInt(dataSize, 0, 128) ; poke the maximum size we want to recieve
evt% = ENetCheckEventsSafe peerId, dataSize, bank



Especially note the additional poke to the dataSize buffer before the call to ENetCheckEventsSafe(). This is how you can control the maximum size you want to receive to the buffer regardless what size the packet that arrived had. Note that the rest of the packet (if it is larger than the given dataSize) is ignored.

Report back if you notice any problems with this update.

best regards
/MaHan


Xaron(Posted 2009) [#6]
Hey thanks for updating that! :)


Mahan(Posted 2009) [#7]
Hey man! :-)

Didn't know you where still around on these forums. 4 years is a long time. Thats why I released the update on my webspace.

Enet is imho the most viable option for reliable networking in the Blitz*-languages (taking in account that it's very stable + BSD licensed) so I thank you very much for your work to make it a userlib.

PS: Feel free to use my update (or modify it however you like) in your package if you want.


Sake906(Posted 2011) [#8]
Guys, sorry for the necro.

I am currently testing this lib and it works really well, but there are a few missing things; I can't find a way to "broadcast" a message to everyone, be it from a client or from the host, the functionality for this seems to be missing on the wrapper. By this I mean that the following:

ENetSendData (packet$, packetSize%, client%, reliable%)

is currently the only way to send any message, and the "client%" variable is supposed to be the peer where we send the message. The thing is, for clients it only lets you specify zero which sends any message to the host (so far from what I've tested) and on the host side, it only lets you specify a client ID. No such thing as a "broadcast" ID like on the directplay netcode.

Unless I overlooked something?

Last edited 2011


Sake906(Posted 2011) [#9]
Okay, so I worked on expanding the wrapper. Here you have a more or less streamlined chunk of code with handy functions and player storage variables. Code is commented.

enetfunctionality.bb :


; BB file by Sake906, based on Xaron and Mahan's wrapper for eNet

Const ENET_EVENT_TYPE_NONE       = 0
Const ENET_EVENT_TYPE_CONNECT    = 1
Const ENET_EVENT_TYPE_DISCONNECT = 2
Const ENET_EVENT_TYPE_RECEIVE    = 3

Global Bank = CreateBank(128)
Global PeerId = CreateBank(4)
Global DataSize = CreateBank(4)


Global eNet_CompressBank = CreateBank(4);Bank used for converting Floats to Strings


Global eNetStarted
Global eNetMode

Global eNetDisplayPackets=0

Global eNetMyID%
Global eNetMyName$



Type eNetUserStats
	Field ID
	Field Name$
End Type

Function eNet_Client_Store(ID)
	
	Local this.eNetUserStats
	
	For this.eNetUserStats=Each eNetUserStats
		If this\ID=ID
			PrintLog "User ID duplicate found ("+ID+","+this\Name+") when trying To store a newcoming user. Deleting old..."
			Delete this
		EndIf
	Next
		
	this.eNetUserStats=New eNetUserStats
	this\ID=ID
	PrintLog "Newcoming user ID stored ("+this\ID+")"
	
	
End Function


Function eNet_Client_Delete(ID)
	
	Local this.eNetUserStats
	
	For this.eNetUserStats=Each eNetUserStats
		If this\ID=ID
			PrintLog "User quit ("+ID+","+this\Name+"). Deleting data."
			Delete this
		EndIf
	Next
	
	
End Function


Function eNet_Client_GetName$(ID)
	
	Local this.eNetUserStats
	
	For this.eNetUserStats=Each eNetUserStats
		If this\ID=ID
			Return this\Name
		EndIf
	Next
	
	
End Function


Function eNet_Client_UpdateName(ID,name$)
	
	Local this.eNetUserStats
	
	For this.eNetUserStats=Each eNetUserStats
		If this\ID=ID
			this\Name=name
			PrintLog "User name for "+this\ID+" updated. Player name is "+this\Name
		EndIf
	Next
	
	
End Function


Function eNet_Start(host,address$,port,maxclients,myname$,IBW=0,OBW=0)
	If eNetStarted=0
		
		ENetInitialize()
		
		If host=1 ; host
			eNetStarted=ENetCreate(host,"any",port,maxclients,IBW,OBW)
		Else; client
			ENetCreate(host,"",port,maxclients,IBW,OBW)
			eNetStarted=ENetConnect(address, port)
		EndIf
		
		If eNetStarted=-1
			PrintLog "Failed to initialize network"
			eNet_End()
		Else
			If host=1
				PrintLog "Host ready "+eNetStarted
			Else
				PrintLog "Connected to host at "+address+" "+eNetStarted
			EndIf
			eNetMode=host
			eNetMyName=myname
		EndIf
		
	EndIf
End Function

Function eNet_Client_PurgeAll()
	
	Local this.eNetUserStats
	
	For this.eNetUserStats=Each eNetUserStats
		Delete this
	Next
	
	PrintLog "All Player data deleted"
	
End Function

Function eNet_End()
	
	Local this.eNetUserStats
	
	If eNetStarted<>0
		If eNetMode=0
			ENetDisconnect(0)
			eNet_Client_PurgeAll()
		Else
			For this.eNetUserStats=Each eNetUserStats
				ENetDisconnect(this\ID)
				Delete this
			Next
		EndIf
		eNet_Update()
		ENetDeInitialize()
		eNetStarted=0
		eNetMode=0
		eNetMyID=0
		PrintLog "eNet terminated"
	EndIf
	
End Function


Function eNet_Update()
	
	Local Evt%
	
	Local all$
	Local DT$
	Local mtype
	Local mfrom
	Local mto ; host only
	Local mrel; host only, tells the host if the use has sent a reliable message so whenever it is sent to another peer, it is also set to reliable
	Local endnet
	
	Local eu.eNetUserStats
	
	If eNetStarted=1
		
		While( ENetDoEventCheck(0) > 0 )
			
			PokeInt(DataSize, 0, 128) ; Poke the Banksize. This will protect against buffer overflow
			
			Evt = ENetCheckEventsSafe(PeerId, DataSize, Bank)
			
			If eNetMode=1; host
				
				
				If Evt=ENET_EVENT_TYPE_CONNECT ; someone joined
					
					; store this user, even If we don't know their name yet
					
					eNet_Client_Store(PeekInt(PeerId,0))
					
					; send them their ID so they know who they are
					
					eNet_SendMessage(1,PeekInt(PeerId,0),PeekInt(PeerId,0),-1,True)
					
				Else If Evt=ENET_EVENT_TYPE_DISCONNECT; someone left
					
					eNet_Client_Delete(PeekInt(PeerId,0))
					
					; Tell your clients that this user quit
					
					For eu.eNetUserStats=Each eNetUserStats
						
						eNet_SendMessage(4,"",eu\ID,PeekInt(PeerId,0),True)
						
					Next
					
				Else If Evt=ENET_EVENT_TYPE_RECEIVE
					
					all=eNet_ComposeFromBank$(PeekInt(DataSize, 0)-1)
					
					mtype=eNet_StrToInt(Mid(all,1,1))
					mto=eNet_StrToInt(Mid(all,2,1))
					
					If mtype=>5 ; if message type is part of the range for internal stuff, we wont read the reliable flag notification since it is always set to 1 for these
						mrel=Int_ReadBit(eNet_StrToInt(Mid(all,3,1)),8)
						DT=Mid(all,4)
					Else ; otherwise.....
						DT=Mid(all,3)
					EndIf
					
					mfrom=PeekInt(PeerId,0)
					
					If mtype<5 ; internal message types
						
						If mtype=2 ; player has sent you their name after being notified of their ID
							
						; update the name for the previously stored player
							
							eNet_Client_UpdateName(mfrom,DT)
							
						; after that, we notify everyone about this (even to the newcomer so they have their own data too)
							
							PrintLog "Telling other players (and the newcomer likewise) about the newcomer"
							
							For eu.eNetUserStats=Each eNetUserStats
								
							; first we update everyone (and the newcomer himself) about the new player
								eNet_SendMessage(3,DT,eu\ID,mfrom,True)
								
							Next
							
							PrintLog "Telling the newcomer about the other players"
							
							For eu.eNetUserStats=Each eNetUserStats
								
							; then we send the already-connected player data to the newcomer
								If eu\ID<>mfrom
									eNet_SendMessage(3,eu\Name,mfrom,eu\ID,True)
								EndIf
								
							Next
							
						EndIf
						
					Else If mtype=>5
						
						; Since this is the host, you should just redirect any incoming messages to their intended peer
						
						PrintLog eNet_Client_GetName$(mfrom)+" sent a packet to "+eNet_Client_GetName$(mto)
						
						eNet_SendMessage(mtype,DT,mto,mfrom,mrel)
						
					EndIf
					
				EndIf
				
				
			Else ; client
				
				If Evt=ENET_EVENT_TYPE_RECEIVE
					
					all=eNet_ComposeFromBank$(PeekInt(DataSize, 0)-1)
					
					mtype=eNet_StrToInt(Mid(all,1,1))
					mfrom=eNet_StrToInt(Mid(all,2,1))
					DT=Mid(all,3)
					
					If mtype=1 ; host has sent you your ID number, because you are just too dumb to know it yourself right after joining
						
						PrintLog "Welcome. Your player ID is: "+DT
						eNetMyID=Int(DT)
						; now you should perhaps tell the host what your name is.
						eNet_SendMessage(2,eNetMyName,0,0,True)
						
					Else If mtype=3 ; You have been sent "new player" data
						
						eNet_Client_Store(mfrom)
						eNet_Client_UpdateName(mfrom,DT)
						
					Else If mtype=4 ;someone quit
						
						eNet_Client_Delete(mfrom)
						
					Else
						
						; store the rest of message types here for use elsewhere in your application. Message types are limited to the range 5-255 and player IDs are limited to the range 0-254 (255 being the host)
						; to store the info, simply save whatever mfrom,DT and mtype contain on any variables, types or arrays.
						
						PrintLog all
						
					EndIf
					
				Else If Evt=ENET_EVENT_TYPE_DISCONNECT
					
					PrintLog "Game session ended. Host either kicked you or they quit"
					
					endnet=1
					
				EndIf
				
			EndIf
			
		Wend
		
		If endnet=1
			eNet_End()
		EndIf
		
	EndIf
	
End Function


Function eNet_SendMessage(mtype,mdata$,mto,mfrom,rel=False)
	
	Local packet$
	Local hostpacket$
	
	Local sendrel%
	
	If eNetStarted<>0
		
		If rel=False  ; yes stupid I know but maybe in the future, other bits might be placed in this byte.
			sendrel=%00000000
		Else
			sendrel=%00000001
		EndIf
		
		; The reliable flag is sent to the host so they know when a user has sent a message that was supposed to be reliable. Currently with this wrapper,
		; there is no way to tell if a message was sent with a reliable flag, so don't blame me. The reliable flag notification is only sent with message types above 5, which are
		; user message types. Anything below that is assumed to always be set to reliable (which are basic connectivity message types)
		
		
		hostpacket=eNet_IntToStr(mtype,1)+eNet_IntToStr(mfrom,1)+mdata
		If mtype<5
			packet=eNet_IntToStr(mtype,1)+eNet_IntToStr(mto,1)+mdata
		Else
			packet=eNet_IntToStr(mtype,1)+eNet_IntToStr(mto,1)+sendrel+mdata
		EndIf
		
		
		If eNetMode=1; host
			ENetSendData(hostpacket,Len(hostpacket)-1,mto,rel)
			If eNetDisplayPackets=1 Then PrintLog "Host packet sent: "+hostpacket
		Else
			ENetSendData(packet,Len(packet)-1,0,rel)
			If eNetDisplayPackets=1 Then PrintLog "Client packet sent: "+packet
		EndIf
		
	EndIf
	
End Function

Function eNet_Client_BroadcastMessage(mtype,mdata$,rel=False)
	
	Local this.eNetUserStats
	
	For this.eNetUserStats=Each eNetUserStats
		
		If this\ID<>eNetMyID
			
			eNet_SendMessage(mtype,mdata$,this\ID,0,rel)
			
		EndIf
		
	Next
	
End Function

Function eNet_ComposeFromBank$(size)
	
	Local c
	Local result$
	
	For c=0 To size
		result=result+Chr$(PeekByte(Bank, c))
	Next
	
	Return result$
	
End Function



Function eNet_IntToStr$(Num%, StrLen% = 4)
;-=-=-=Take an Integer and compress it to a string, of "strlen" bytes long.
	Local shiftin%
	Local st$ = Chr$(Num And 255)
	For shiftin = 1 To (StrLen - 1)
		st$ = st$ + Chr$(Num Sar (8 * shiftin))
	Next
	Return st
End Function 

Function eNet_FloatToStr$ (num#)
;-=-=-=Convert a floating point number to a 4 byte string
	Local st$ = "",i%
	PokeFloat eNet_CompressBank,0,num
	For i = 0 To 3 
		st$ = st$ + Chr$(PeekByte(eNet_CompressBank,i))
	Next 
	Return st$ 
End Function

Function eNet_StrToInt%(st$)
;-=-=-=Take a String of any length and turn it into an integer again.
	Local shiftin%
	Local num%
	For shiftin = 0 To (Len (st$) - 1)
		num = num Or (Asc (Mid$ (st$, shiftin + 1, 1)) Shl shiftin * 8)
	Next
	Return num
End Function

Function eNet_StrToFloat#(st$)
;-=-=-=Take a 4 byte string and turn it back into a floating point #.
	Local num#,i%
	For i = 0 To 3
		PokeByte eNet_CompressBank,i,Asc(Mid$(st$,i+1,1))
	Next
	num# = PeekFloat(eNet_CompressBank,0)
	Return num
End Function


Function PrintLog(p$)
	Print p
	DebugLog p
End Function


Function Int_ReadBit(num%,loc%)
	
	Local work%=Abs(num)
	Local st$
	
	If work>255
		work=255
	EndIf
	
	If loc>8
		loc=8
	EndIf
	
	st=Bin(work)
	
	Return Int(Mid(st,(Len(st)-8)+loc,1))
	
End Function



And the demo that puts the above to work:


Graphics 800,240,32,2

SetBuffer BackBuffer()

Global radial#

Include "enetfunctionality.bb"

Global mode
Global ip$
Global portnumber=2400
Global maxplayers=24
Global playername$
Global BWO
Global BWI


mode=SelectMode()

If mode=0
	ip=Input("Game IP: ")
	playername=Input("Your name: ")
EndIf

;If mode=1
;	BWO=0
;	BWI=0
;Else
;	BWO=2000
;	BWI=2000
;EndIf



eNet_Start(mode,ip,portnumber,maxplayers,playername,BWI,BWO)


If mode=1
	AppTitle "Host"
Else
	AppTitle playername
EndIf



While Not KeyHit(1)
	
	If mode=0
		If KeyHit(31)=1
			eNet_Client_BroadcastMessage(5,"Packet...",True)
		EndIf
	EndIf
	
	eNet_Update()
	
Wend



eNet_End()



Function SelectMode()
	
	Local sel$
	
	sel=Input("Host (H) or Client (C)?: ")
	
	If Lower(sel)="h"
		Return 1
	Else
		Return 0
	EndIf
	
End Function



Feel free to use or edit.

Last edited 2011


funkmaster5000(Posted 2015) [#10]
Hi all,
I really feel terrible for digging this out again. I'm testing Sake906s Version. It works like a charm - robust and fast. The only thing is: if I create a server locally, my server and the first player to join both get the same player ID = 0. Which is bad, because then I am moving 2 players at a time. I've read, that the server is supposed to get the ID 255. That isn't the case. So how can I test this or force a higher number on the player/host? Or is this really meant to be running on a server outside the client's network? Thanks for any help.


Rick Nasher(Posted 2015) [#11]
Hi, you may want to follow my thread as Flanker is creating a brand new fresh library, which is looking very promising so far.
See from post#40 and onwards for discussion about it: http://www.blitzbasic.com/Community/posts.php?topic=104840
And for Flanker's worklog: http://www.blitzbasic.com/logs/userlog.php?log=1916&user=15074