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 |