UDP-Network-Library
Blitz3D Forums/Blitz3D Userlibs/UDP-Network-Library
| ||
| 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 |
| ||
| Works great so far :) Nice work! |
| ||
| This is really interesting. Any idea of the stablility state of this library and the wrapper? |
| ||
| @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 |
| ||
| 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 |
| ||
| Hey thanks for updating that! :) |
| ||
| 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. |
| ||
| 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 |
| ||
| 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 |
| ||
| 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. |
| ||
| 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 |