mzehr.net - Netzwerkmodul basierend auf enet

Übersicht BlitzMax, BlitzMax NG Codearchiv & Module

Neue Antwort erstellen

Jolinah

Betreff: mzehr.net - Netzwerkmodul basierend auf enet

BeitragFr, Sep 16, 2011 17:17
Antworten mit Zitat
Benutzer-Profile anzeigen
Version 1.1:
In Version 1.1 wird nicht mehr das pub.enet-Modul gelinkt, da dies eine ziemlich alte Version von ENet ist. Stattdessen wird direkt der Sourcecode von ENet 1.3.13 in das Modul einkompiliert.

Allerdings gab es beim Wechsel von ENet 1.2 zu ENet 1.3 eine Änderung im Netzwerk-Protokoll. Das heisst, falls jemand ein existierendes Game hat, welches mzehr.net 1.0 verwendet und nun gerne auf 1.1 aktualisieren möchte, der muss nebst dem Server natürlich auch die Clients aktualisieren und neu an die Spieler verteilen (sei es Manuell oder via Update-Funktion).

Download:
https://www.blitzforum.de/upload/file.php?id=13346
https://www.michael-zehr.ch/do...et_1.1.zip (Mirror)

Quellcode:
Ist weiter unten als neuen Post zu finden (https://www.blitzforum.de/foru...001#419001)
Ausserdem auf GitHub: https://github.com/Jolinah/mzehr.net

Changelog:
    Arrow pub.enet ersetzt mit ENet 1.3.13
    Arrow Lizenz von Public Domain zu MIT X11-Lizenz angepasst wie ENet selber (heisst im Prinzip nur, dass die License.txt immer mit dem Quellcode zusammen verteilt werden sollte, nebst einem kleinen Schutz für mich, dass ich nicht dafür verantwortlich gemacht werden kann, falls die Software irgendwo und irgendwie Schaden anrichten sollte, da ich nicht zu 100% garantieren kann, dass die Software absolut Fehler-frei ist.)
    Arrow Dokumentation von Deutsch zu Englisch geändert. Wer mit Englisch Probleme hat, kann für die Dokumentation die Version 1.0 herunterladen. Im Prinzip sind alles noch die gleichen Types und Funktionen.


Version 1.0:
Ich habe ein kleines Netzwerkmodul basierend auf dem pub.enet-Modul erstellt, welches schon Standard bei BlitzMax dabei ist. Eigentlich sind es nur ein paar Wrapper-Klassen die es einem erleichtern mit enet zu programmieren. Das enet-Projekt allgemein ist zu finden unter http://enet.bespin.org

Eigenschaften von ENET:

- Verwendet UDP
- Unterstützt zuverlässige sowie unzuverlässige Nachrichten (reliable)
- Unterstützt mehrere Datenkanäle
- Eingebautes Session-Management
- Automatische Datenflusskontrolle, d.h. man kann beliebig grosse "Pakete" machen, diese werden automatisch in mehrere UDP-Pakete aufgeteilt. Weiterhin wird Datenstau etc. verhindert.

Eigenschaften von mzehr.net:

- Alle Eigenschaften von oben
- OOP - Einfach zu verwendende Klassen, kein rumhantieren mehr mit Byte-Pointer wie bei pub.enet
- Optionale Komprimierung von Paketen mittels pub.zlib:

Die Komprimierung ist gut, wenn man z.B. 100x100 Tiles, oder gar wie bei den Minecraft-Klonen 100x100x100 Tiles senden will, denn meistens sind ja sehr viele Blöcke identisch und hier erzielt die Komprimierung eine gute Kompressionsrate. Damit die Komprimierung beim Empfangen eines Pakets erkannt werden kann fügt das Modul zu jedem Paket noch 1 oder 2 Ints hinzu, was aber nicht weiter tragisch sein sollte.)

Klassen:
    Arrow TNetServer und TNetClient sind die beiden Hauptklassen
    Arrow TNetServerEvent und TNetClientEvent werden vom Server/Client bei einem Ereignis zurückgegeben
    Arrow TNetPeer stellt bei einem Server einen verbundenen Client dar (konnte ich leider nicht ebenfalls TNetClient nennen)
    Arrow TNetPacket ermöglicht das einfache schreiben/lesen in/aus einem Datenpaket

Download:
https://www.blitzforum.de/upload/file.php?id=13343
https://www.michael-zehr.ch/do...hr.net.zip (Mirror)

Quellcode:

net.bmx
BlitzMax: [AUSKLAPPEN]
SuperStrict

Rem
bbdoc: Netzwerkmodul basierend auf enet (http://enet.bespin.org)
End Rem

Module mzehr.net

ModuleInfo "Version 1.0"
ModuleInfo "Author: Michael Zehr (Jolinah)"
ModuleInfo "License: Public Domain"
ModuleInfo "Credit: Lee Salzman (http://enet.bespin.org)"

ModuleInfo "History: 1.0 Release"

Import brl.linkedlist
Import brl.bank
Import brl.bankstream

Import pub.enet
Import pub.zlib

Include "TNetServer.bmx"
Include "TNetPeer.bmx"
Include "TNetClient.bmx"
Include "TNetEvent.bmx"
Include "TNetPacket.bmx"


TNetClient.bmx
BlitzMax: [AUSKLAPPEN]

Rem
bbdoc: Klasse zum Herstellen einer Verbindung mit einem Server
End Rem

Type TNetClient
Field _host:Byte Ptr
Field _peer:Byte Ptr

Rem
bbdoc: Erstellt einen neuen, unverbundenen, Client
End Rem

Function Create:TNetClient()
Local client:TNetClient = New TNetClient
Return client
End Function

Rem
bbdoc: Stellt eine Verbindung mit einem Server her
returns: True wenn die Verbindung zu Stande kommt, False wenn nicht
about:
host:String IP/Hostname des Servers
port:Int Port des Servers
connectTimeout:Int (Optional) Maximale Zeit in ms für die Verbindungsherstellung.
End Rem

Method Connect:Int(host:String, port:Int, connectTimeout:Int = 5000)
'In case we are connected -> disconnect
Disconnect()

'Create address struct
Local addr:Byte Ptr = enet_address_create(0, port)
If addr = Null Then Return False
enet_address_set_host(addr, host)

'Create host
_host = enet_host_create(Null, 1, 0, 0)
If _host = Null Then Return False

'Connect to the remote host
_peer = enet_host_connect(_host, addr, 2)
enet_address_destroy(addr)

'No peer available for connection?
If _peer = Null Then
enet_host_destroy(_host)
_host = Null
Return False
EndIf

Local ev:ENetEvent = New ENetEvent

'Wait for connection response
If enet_host_service(_host, ev, connectTimeout) > 0 And ev.event = ENET_EVENT_TYPE_CONNECT
'Connection successful
Return True
Else
'Timeout reached
enet_peer_reset(_peer)
enet_host_destroy(_host)
_host = Null
_peer = Null
Return False
End If
End Method

Rem
bbdoc: Trennt die Verbindung mit dem Server
about:
disconnectTime:Int (Optional) Maximale Zeit in ms, zum Senden allfälliger letzter Nachrichten vor der Trennung
End Rem

Method Disconnect(disconnectTime:Int = 2000)
'Already disconnected?
If _host = Null And _peer = Null Then Return

If _peer = Null Then
enet_host_destroy(_host)
_host = Null
Return
End If

If _host = Null Then
enet_peer_reset(_peer)
_peer = Null
Return
End If

'Immediate disconnect (no server notification)
If disconnectTime <= 0 Then
enet_peer_reset(_peer)
enet_host_destroy(_host)
_host = Null
_peer = Null
Return
End If

'Gentle disconnect (wait for server acknowledgement)
Local ev:ENetEvent = New ENetEvent
Local begin:Int = MilliSecs()

enet_peer_disconnect(_peer)

While enet_host_service(_host, ev, disconnectTime) > 0
Select ev.event
Case ENET_EVENT_TYPE_RECEIVE
'Drop any received packets
enet_packet_destroy(ev.packet)

Case ENET_EVENT_TYPE_DISCONNECT
'Disconnection successful
Exit
End Select

'Disconnect time reached?
If MilliSecs() - begin >= disconnectTime Then Exit
Wend

'Reset the peer and destroy the host
enet_peer_reset(_peer)
enet_host_destroy(_host)
_host = Null
_peer = Null
End Method

Rem
bbdoc: Aktualisiert den Client (empfängt und sendet Nachrichten)
returns: Ein TNetClientEvent-Objekt, das beschreibt was passiert ist, oder Null wenn kein Ereignis stattfand
about:
waitTime:Int (Optional) Zeit in ms die der Client auf Nachrichten wartet. (Standard = 0, das heisst wenn keine Nachrichten vorhanden sind, kehrt die Methode sofort zurück und wartet nicht weiter)
End Rem

Method Update:TNetClientEvent(waitTime:Int = 0)
If _host = Null Then Return Null

Local ev:ENetEvent = New ENetEvent
If enet_host_service(_host, ev, waitTime) > 0
Select ev.event
Case ENET_EVENT_TYPE_RECEIVE
DebugLog("Packet received")
Return TNetClientEvent.ClientEvent(ev.event, TNetPacket.FromEnet(ev.packet), ev.channel)

Case ENET_EVENT_TYPE_DISCONNECT
DebugLog("Disconnected")
Return TNetClientEvent.ClientEvent(ev.event, Null, 0)
End Select
Else
Return Null
End If
End Method

Rem
bbdoc: Sendet ein Packet an den Server
about:
packet:TNetPacket Das Packet/Die Daten die gesendet werden
reliable:Int (Optional) Bestimmt ob das Packet zwingend ankommen muss (Standard: Nein, gut für Positions-Übermittlung etc.)
channel:Int (Optional) Kanal über den das Packet gesendet wird (Standard: 0, Wichtige reliable-Nachrichten sollten über einen anderen Kanal gesendet werden als unwichtige Nachrichten, z.B. Kanal 1)
compressed:Int (Optional) Bestimmt ob die Daten vor der Übermittlung komprimiert werden (Standard: Nein, lohnt sich nur bei sehr grossen Daten-Paketen, wie z.b. wenn man 100x100 Tiles in 1 Paket senden möchte).
End Rem

Method Send(packet:TNetPacket, reliable:Int = False, channel:Int = 0, compressed:Int = False)
If packet = Null Then Return
If _host = Null Or _peer = Null Then Return

Local flags:Int = 0
If reliable Then flags = ENET_PACKET_FLAG_RELIABLE

Local p:Byte Ptr = packet.EnetPacket(flags, compressed)
If p = Null Then Return

enet_peer_send(_peer, channel, p)
End Method

Rem
bbdoc: Sendet alle zwischengespeicherten Nachrichten so schnell wie möglich bzw. sofort
about:
Es ist besser Update zu verwenden, da Update ebenfalls alle Nachrichten sendet und auch Nachrichten empfängt
End Rem

Method Flush()
If _host = Null Then Return
enet_host_flush(_host)
End Method
End Type


TNetEvent.bmx
BlitzMax: [AUSKLAPPEN]

Rem
bbdoc: Stellt ein Ereignis dar, dass vom Client ausgelöst wurde
End Rem

Type TNetClientEvent
Rem
bbdoc: Die Verbindung wurde getrennt (Client: Verbindung mit dem Server | Server: Verbindung mit einem bestimmten Client).
End Rem

Const DISCONNECT:Int = ENET_EVENT_TYPE_DISCONNECT

Rem
bbdoc: Daten erhalten (Client: Vom Server | Server: Von einem bestimmten Client)
End Rem

Const DATA:Int = ENET_EVENT_TYPE_RECEIVE

Rem
bbdoc: Ereignis das ausgelöst wurde (Wert = TNetClientEvent.CONNECT oder DATA)
End Rem

Field Event:Int

Rem
bbdoc: Packet das empfangen wurde (nur Verfügbar beim DATA-Ereignis)
End Rem

Field Packet:TNetPacket

Rem
bbdoc: Kanal auf dem die Nachricht empfangen wurde
End Rem

Field Channel:Int

Function ClientEvent:TNetClientEvent(event:Int, packet:TNetPacket, channel:Int)
Local ev:TNetClientEvent = New TNetClientEvent
ev.event = event
ev.packet = packet
ev.channel = channel
Return ev
End Function
End Type

Rem
bbdoc: Stellt ein Ereignis dar, dass vom Server ausgelöst wurde
End Rem

Type TNetServerEvent Extends TNetClientEvent
Rem
bbdoc: Ein neuer Client hat sich mit dem Server verbunden
End Rem

Const CONNECT:Int = ENET_EVENT_TYPE_CONNECT

Rem
bbdoc: Client der sich verbunden/getrennt hat, oder von dem Daten empfangen wurden
End Rem

Field Peer:TNetPeer

Function ServerEvent:TNetServerEvent(event:Int, packet:TNetPacket, peer:TNetPeer, channel:Int)
Local ev:TNetServerEvent = New TNetServerEvent
ev.event = event
ev.packet = packet
ev.peer = peer
ev.channel = channel
Return ev
End Function
End Type


TNetPacket.bmx
BlitzMax: [AUSKLAPPEN]

Rem
bbdoc: Stellt ein Datenpaket dar, in das Werte geschrieben, oder von dem Werte gelesen werden können
End Rem

Type TNetPacket
Field _packet:Byte Ptr
Field _bank:TBank
Field _stream:TBankStream

Rem
bbdoc: Erstellt ein neues Datenpaket, in welches Daten geschrieben werden können (zum Versand)
End Rem

Function Create:TNetPacket()
Local packet:TNetPacket = New TNetPacket
packet._bank = TBank.Create(0)
packet._stream = TBankStream.Create(packet._bank)
packet.WriteByte(0)
Return packet
End Function

Rem
bbdoc: Liest ein Datenpaket aus einer ENet-Struktur
about:
Die Funktion wird intern verwendet und muss nicht manuell aufgerufen werden
End Rem

Function FromEnet:TNetPacket(p:Byte Ptr)
Local packet:TNetPacket = New TNetPacket
packet._packet = p
packet._bank = TBank.CreateStatic(enet_packet_data(p), enet_packet_size(p))
packet._stream = TBankStream.Create(packet._bank)

'Decompression
Local cmp:Byte = packet.ReadByte()
If cmp Then
Local buf:Byte Ptr = MemAlloc(packet._bank.Size() - 1)
Local l:Int = 0
uncompress(buf, l, Varptr packet._bank.buf()[1], packet._bank.Size() - 1)

packet._stream.Close()
packet._bank = TBank.Create(l)
packet._stream = TBankStream.Create(packet._bank)

MemCopy(packet._bank.buf(), buf, l)
MemFree(buf)
End If

Return packet
End Function

Method Delete()
Destroy()
End Method

Rem
bbdoc: Gibt den vom Paket verwendeten Speicher wieder frei
about:
Wird automatisch aufgerufen wenn ein Paket nicht mehr verwendet wird (GarbageCollector),
kann aber auch manuell aufgerufen werden
End Rem

Method Destroy()
If _stream <> Null Then
_stream.Close()
_stream = Null
End If

If _bank <> Null Then
_bank = Null
End If

If _packet <> Null Then
enet_packet_destroy(_packet)
_packet = Null
DebugLog("TNetPacket destroyed")
End If
End Method

Rem
bbdoc: Liest mehrere Bytes vom Paket in einen benutzerdefinierten Speicherbereich
returns: Anzahl effektiv gelesene Bytes
about:
buf:Byte Ptr Speicherbereich in dem die gelesenen Daten abgelegt werden
count:Int Anzahl Bytes die gelesen werden
End Rem

Method Read:Int(buf:Byte Ptr, count:Int)
Return _stream.Read(buf, count)
End Method

Rem
bbdoc: Liest ein Byte vom Paket
End Rem

Method ReadByte:Byte()
Return _stream.ReadByte()
End Method

Rem
bbdoc: Liest mehrere Bytes vom Paket in einen benutzerdefinierten Speicherbereich
returns: Anzahl effektiv gelesene Bytes
about:
buf:Byte Ptr Speicherbereich in dem die gelesenen Daten abgelegt werden
count:Int Anzahl Bytes die gelesen werden
End Rem

Method ReadBytes:Int(buf:Byte Ptr, count:Int)
Return _stream.ReadBytes(buf, count)
End Method

Rem
bbdoc: Liest ein Short vom Paket
End Rem

Method ReadShort:Short()
Return _stream.ReadShort()
End Method

Rem
bbdoc: Liest ein Int vom Paket
End Rem

Method ReadInt:Int()
Return _stream.ReadInt()
End Method

Rem
bbdoc: Liest ein Long vom Paket
End Rem

Method ReadLong:Long()
Return _stream.ReadLong()
End Method

Rem
bbdoc: Liest ein Float vom Paket
End Rem

Method ReadFloat:Float()
Return _stream.ReadFloat()
End Method

Rem
bbdoc: Liest ein Double vom Paket
End Rem

Method ReadDouble:Double()
Return _stream.ReadDouble()
End Method

Rem
bbdoc: Liest einen String vom Paket
about:
Liest ein Int mit der Länge des String und anschliessend den String selber aus
End Rem

Method ReadString:String()
Local l:Int = _stream.ReadInt()
If l = -1 Then Return Null
Return _stream.ReadString(l)
End Method

Rem
bbdoc: Liest eine Zeile vom Paket
End Rem

Method ReadLine:String()
Return _stream.ReadLine()
End Method

Rem
bbdoc: Liest ein Objekt vom Paket (nicht getestet)
End Rem

Method ReadObject:Object()
Return _stream.ReadObject()
End Method

Rem
bbdoc: Schreibt mehrere Bytes in das Paket
about:
buf:Byte Ptr Speicherbereich aus dem die Daten ins Paket gelesen werden
count:Int Anzahl Bytes die geschrieben/gelesen werden
End Rem

Method Write(buf:Byte Ptr, count:Int)
_stream.Write(buf, count)
End Method

Rem
bbdoc: Schreibt ein Byte in das Paket
End Rem

Method WriteByte(value:Byte)
_stream.WriteByte(value)
End Method

Rem
bbdoc: Schreibt mehrere Bytes in das Paket
about:
buf:Byte Ptr Speicherbereich aus dem die Daten ins Paket gelesen werden
count:Int Anzahl Bytes die geschrieben/gelesen werden
End Rem

Method WriteBytes(buf:Byte Ptr, count:Int)
_stream.WriteBytes(buf, count)
End Method

Rem
bbdoc: Schreibt ein Short in das Paket
End Rem

Method WriteShort(value:Short)
_stream.WriteShort(value)
End Method

Rem
bbdoc: Schreibt ein Int in das Paket
End Rem

Method WriteInt(value:Int)
_stream.WriteInt(value)
End Method

Rem
bbdoc: Schreibt ein Long in das Paket
End Rem

Method WriteLong(value:Long)
_stream.WriteLong(value)
End Method

Rem
bbdoc: Schreibt ein Float in das Paket
End Rem

Method WriteFloat(value:Float)
_stream.WriteFloat(value)
End Method

Rem
bbdoc: Schreibt ein Double in das Paket
End Rem

Method WriteDouble(value:Double)
_stream.WriteDouble(value)
End Method

Rem
bbdoc: Schreibt ein String in das Paket
about:
Schreibt ein Int mit der Länge des Strings und anschliessend den String selber
End Rem

Method WriteString(value:String)
If value = 0
_stream.WriteInt(-1)
Return
Else
_stream.WriteInt(value.Length)
_stream.WriteString(value)
End If
End Method

Rem
bbdoc: Schreibt eine Zeile in das Paket
End Rem

Method WriteLine(value:String)
_stream.WriteLine(value)
End Method

Rem
bbdoc: Schreibt ein Objekt in das Paket (nicht getestet)
End Rem

Method WriteObject(value:Object)
_stream.WriteObject(value)
End Method

Rem
bbdoc: Generiert eine ENet-Paket-Struktur
about:
Diese Methode wird intern verwendet und muss nicht manuell aufgerufen werden
End Rem

Method EnetPacket:Byte Ptr(enet_flags:Int = 0, compressed:Int = False)
If _packet <> Null Then Return _packet
If _bank = Null Then Return Null

If compressed Then
'Compression
Local cmp:Byte Ptr = MemAlloc(_bank.Size() - 1)
Local cmpLen:Int = 0
compress(cmp, cmpLen, Varptr _bank.buf()[1], _bank.Size() - 1)

Local buf:Byte Ptr = MemAlloc(cmpLen + 1)
MemCopy(Varptr buf[1], cmp, cmpLen)
buf[0] = 1

Local p:Byte Ptr = enet_packet_create(buf, cmpLen + 1, enet_flags)

MemFree(cmp)
MemFree(buf)

Return p
Else
'No compression
Return enet_packet_create(_bank.buf(), _bank.Size(), enet_flags)
End If
End Method

End Type


TNetPeer.bmx
BlitzMax: [AUSKLAPPEN]

Rem
bbdoc: Klasse die auf dem Server einen Client/Spieler repräsentiert
End Rem

Type TNetPeer
Field _peer:Byte Ptr
Field _data:Object

Rem
bbdoc: Erstellt einen neuen Client aufgrund eines ENet-Peers
about:
Diese Methode wird intern vom Server verwendet und muss nicht manuell aufgerufen werden
End Rem

Function Create:TNetPeer(peer:Byte Ptr)
Local p:TNetPeer = New TNetPeer
p._peer = peer
Return p
End Function

Rem
bbdoc: Gibt den ENet-Peer zurück, der diesem Client/Spieler zugrunde liegt
about:
Diese Methode wird intern vom Server verwendet und muss nicht manuell aufgerufen werden
End Rem

Method GetPeer:Byte Ptr()
Return _peer
End Method

Rem
bbdoc: Gibt die dem Client/Spieler zugewiesenen Daten zurück (siehe SetData)
End Rem

Method GetData:Object()
Return _data
End Method

Rem
bbdoc: Legt für diesen Client/Spieler ein Daten-Objekt fest
about:
Dies Kann verwendet werden um dem TNetPeer eine eigene Spieler-Objekt oder sonstige Daten zuzuweisen.
Mittels TNetServer.GetClients() kann dann eine Liste von TNetPeers abgerufen werden und bei diesen TNetPeers
wiederum die gespeicherten Daten abgerufen werden, mit TNetPeer.GetData()
End Rem

Method SetData(value:Object)
_data = value
End Method
End Type


TNetServer.bmx
BlitzMax: [AUSKLAPPEN]
Rem
bbdoc: Klasse zum Betreiben eines Servers
End Rem

Type TNetServer
Field _host:Byte Ptr
Field _peers:TList

Method New()
_peers = New TList
End Method

Rem
bbdoc: Erstellt einen neuen Server welcher auf der angegebenen IP-Adresse und Port auf eingehende Verbindungen wartet
returns: Entweder ein TNetServer-Objekt oder Null, falls der Server nicht gestartet werden konnte
about:
ip:String IP-Adresse auf der der Server läuft (Null, 127.0.0.1 oder localhost verwenden um jede lokale IP abzuhören)
port:Int Port auf der der Server läuft (empfohlen: über 1000 und möglichst hoch, z.B. 50000)
maxConnections:Int Anzahl maximaler Verbindungen/Spieler für den Server
End Rem

Function Create:TNetServer(ip:String, port:Int, maxConnections:Int)
Local addr:Byte Ptr = enet_address_create(ENET_HOST_ANY, port)
If addr = Null Then Return Null

If ip <> Null And ip <> "" And ip <> "localhost" And ip <> "127.0.0.1"
enet_address_set_host(addr, ip)
End If

Local host:Byte Ptr = enet_host_create(addr, maxConnections, 0, 0)
enet_address_destroy(addr)
If host = Null Then Return Null

Local sv:TNetServer = New TNetServer
sv._host = host
Return sv
End Function

Rem
bbdoc: Beendet die Ausführung des Servers
about:
shutdownTime:Int (Optional) Maximale Zeit in ms in der es erlaubt ist vor dem Beenden noch die letzten Nachrichten an Clients zu senden
End Rem

Method Shutdown(shutdownTime:Int = 5000)
If _host = Null Then Return

Local p:TNetPeer = Null

For p = EachIn _peers
enet_peer_disconnect(p.GetPeer())
Next

Local ev:ENetEvent = New ENetEvent

Local begin:Int = MilliSecs()

If shutdownTime > 0 Then
While enet_host_service(_host, ev, shutdownTime) > 0
Select ev.event
Case ENET_EVENT_TYPE_CONNECT
enet_peer_reset(ev.peer)

Case ENET_EVENT_TYPE_RECEIVE
enet_packet_destroy(ev.packet)

Case ENET_EVENT_TYPE_DISCONNECT
p = FindPeer(ev.peer)
If p <> Null Then _peers.Remove(p)

End Select

If _peers.IsEmpty() Then Exit
If MilliSecs() - begin >= shutdownTime Then Exit
Wend
EndIf

If Not _peers.IsEmpty() Then
For p = EachIn _peers
enet_peer_reset(p)
Next
_peers.Clear()
EndIf

enet_host_destroy(_host)
_host = Null
End Method

Rem
bbdoc: Aktualisiert den Server (empfängt und sendet Nachrichten)
returns: Ein TNetServerEvent-Objekt, das beschreibt was passiert ist, oder Null wenn kein Ereignis stattfand
about:
waitTime:Int (Optional) Zeit in ms die der Server auf Nachrichten wartet. (Standard = 0, das heisst wenn keine Nachrichten vorhanden sind, kehrt die Methode sofort zurück und wartet nicht weiter)
End Rem

Method Update:TNetServerEvent(waitTime:Int = 0)
If _host = Null Then Return Null

Local p:TNetPeer = Null
Local ev:ENetEvent = New ENetEvent

If enet_host_service(_host, ev, waitTime) > 0
Select ev.event
'New client
Case ENET_EVENT_TYPE_CONNECT
p = TNetPeer.Create(ev.peer)
_peers.AddLast(p)

Return TNetServerEvent.ServerEvent(ev.event, Null, p, ev.channel)


'Data from existing client
Case ENET_EVENT_TYPE_RECEIVE
p = FindPeer(ev.peer)
If p = Null Then Return Null

Return TNetServerEvent.ServerEvent(ev.event, TNetPacket.FromEnet(ev.packet), p, ev.channel)


'Client disconnected
Case ENET_EVENT_TYPE_DISCONNECT
p = FindPeer(ev.peer)
If p = Null Then Return Null
_peers.Remove(p)

Return TNetServerEvent.ServerEvent(ev.event, Null, p, ev.channel)
End Select
Else
Return Null
End If
End Method


'Finds a client/peer by enet peer struct
Method FindPeer:TNetPeer(peer:Byte Ptr)
For Local p:TNetPeer = EachIn _peers
If p.GetPeer() = peer Then Return p
Next
Return Null
End Method


Rem
bbdoc: Sendet ein Packet an einen bestimmten Client/Spieler
about:
peer:TNetPeer Der Client/Spieler an den die Nachricht gesendet wird
packet:TNetPacket Das Packet/Die Daten die gesendet werden
reliable:Int (Optional) Bestimmt ob das Packet zwingend beim Spieler ankommen muss (Standard: Nein, gut für Positions-Übermittlung etc.)
channel:Int (Optional) Kanal über den das Packet gesendet wird (Standard: 0, Wichtige reliable-Nachrichten sollten über einen anderen Kanal gesendet werden als unwichtige Nachrichten, z.B. Kanal 1)
compressed:Int (Optional) Bestimmt ob die Daten vor der Übermittlung komprimiert werden (Standard: Nein, lohnt sich nur bei sehr grossen Daten-Paketen, wie z.b. wenn man 100x100 Tiles in 1 Paket senden möchte).
End Rem

Method Send(peer:TNetPeer, packet:TNetPacket, reliable:Int = False, channel:Int = 0, compressed:Int = False)
If _host = Null Or peer = Null Or packet = Null Then Return
If peer.GetPeer() = Null Then Return

Local flags:Int = 0
If reliable Then flags = ENET_PACKET_FLAG_RELIABLE

Local p:Byte Ptr = packet.EnetPacket(flags, compressed)
If p = Null Then Return

enet_peer_send(peer.GetPeer(), channel, p)
End Method


Rem
bbdoc: Sendet ein Packet an alle verbundenen Clients/Spieler
about:
packet:TNetPacket Das Packet/Die Daten die gesendet werden
reliable:Int (Optional) Bestimmt ob das Packet zwingend beim Spieler ankommen muss (Standard: Nein, gut für Positions-Übermittlung etc.)
channel:Int (Optional) Kanal über den das Packet gesendet wird (Standard: 0, Wichtige reliable-Nachrichten sollten über einen anderen Kanal gesendet werden als unwichtige Nachrichten, z.B. Kanal 1)
compressed:Int (Optional) Bestimmt ob die Daten vor der Übermittlung komprimiert werden (Standard: Nein, lohnt sich nur bei sehr grossen Daten-Paketen, wie z.b. wenn man 100x100 Tiles in 1 Paket senden möchte).
End Rem

Method Broadcast(packet:TNetPacket, reliable:Int = False, channel:Int = 0, compressed:Int = False)
If _host = Null Or packet = Null Then Return

Local flags:Int = 0
If reliable Then flags = ENET_PACKET_FLAG_RELIABLE

Local p:Byte Ptr = packet.EnetPacket(flags, compressed)
If p = Null Then Return

enet_host_broadcast(_host, channel, p)
End Method

Rem
bbdoc: Gibt die Liste mit allen verbundenen Clients/Spielern zurück
about:
Die Liste sollte nicht manuell verändert werden, das heisst kein Löschen oder Hinzufügen eines Spielers. Dazu sollten die DisconnectClient/DropClient-Methoden benutzt werden.
End Rem

Method GetClients:TList()
Return _peers
End Method

Rem
bbdoc: Sendet alle zwischengespeicherten Nachrichten so schnell wie möglich bzw. sofort
about:
Es ist besser Update zu verwenden, da Update ebenfalls alle Nachrichten sendet und auch Nachrichten empfängt
End Rem

Method Flush()
If _host = Null Then Return
enet_host_flush(_host)
End Method

Rem
bbdoc: Sendet eine Disconnect-Nachricht an den Client/Spieler und trennt anschliessend die Verbindung wenn die Nachricht bestätigt wurde
End Rem

Method DisconnectClient(peer:TNetPeer)
If peer = Null Or peer.GetPeer() = Null Then Return
enet_peer_disconnect(peer.GetPeer())
End Method

Rem
bbdoc: Trennt sofort die Verbindung mit einem Client. Der Client wird nicht benachrichtigt und merkt evtl. erst später (Timeout) dass die Verbindung gekappt wurde.
End Rem

Method DropClient(peer:TNetPeer)
If peer = Null Or peer.GetPeer() = Null Then Return
enet_peer_reset(peer.GetPeer())
_peers.Remove(peer)
End Method
End Type


ExampleClient.bmx
BlitzMax: [AUSKLAPPEN]
SuperStrict

'Modul importieren
Import mzehr.net

AppTitle = "ExampleClient"
SeedRnd(MilliSecs())



'Kleine Klasse für den eigenen und die anderen Spieler
Type TPlayer
'Liste mit allen Spielern
Global List:TList = New TList

'Name des Spielers
Field Name:String

'Position des Spielers
Field X:Float
Field Y:Float

Method New()
'Neue Spieler in die Liste einfügen
List.AddLast(Self)
End Method

'Zeichnet den Spieler
Method Draw()
DrawText(Name, X - TextWidth(Name) / 2, Y - 40 - TextHeight(Name))
DrawOval(X - 10, Y - 40, 20, 20)
DrawLine(X, Y - 30, X, Y + 10)
DrawLine(X, Y - 10, X - 20, Y - 30)
DrawLine(X, Y - 10, X + 20, Y - 30)
DrawLine(X, Y + 10, X - 20, Y + 30)
DrawLine(X, Y + 10, X + 20, Y + 30)
End Method
End Type


'Einen neuen Client erstellen
Global client:TNetClient = TNetClient.Create()

'Den lokalen Spieler erstellen
Local myPlayer:TPlayer = New TPlayer
myPlayer.Name = "Guest" + Rand(1000, 9999)

'Verbindung herstellen
If client.Connect("localhost", 50000)
'Login-Paket erstellen
Local paket:TNetPacket = TNetPacket.Create()
paket.WriteByte(1) 'Nachricht-Nr. 1
paket.WriteString(myPlayer.Name) 'Name des eigenen Spielers

'Paket an den Server senden (wichtige Nachricht auf Kanal 1)
client.Send(paket, True, 1)

'Maximal 2 Sekunden auf die Antwort des Servers warten
Local event:TNetClientEvent = client.Update(2000)
If event <> Null And event.event = TNetClientEvent.DATA
'Daten empfangen

Local msgId:Byte = event.Packet.ReadByte()
If msgId = 1
'Antwort auslesen
Local loggedIn:Int = event.Packet.ReadByte()
If loggedIn
'Bestehende Spieler einlesen
Local numPlayers:Int = event.Packet.ReadInt()
For Local i:Int = 0 To numPlayers - 1
'Spielerdaten lesen
Local name:String = event.Packet.ReadString()
Local x:Float = event.Packet.ReadFloat()
Local y:Float = event.Packet.ReadFloat()

'Spieler erstellen
Local player:TPlayer = New TPlayer
player.name = name
player.x = x
player.y = y
Next
Else
'Login fehlgeschlagen
Local error:String = event.Packet.ReadString()
client.Disconnect()
Notify(error)
End
End If
Else
'Ungültige Antwort
client.Disconnect()
Notify("Der Server hat eine unerwartete Antwort gesendet. Protokoll-Fehler?")
End
End If
Else
'Keine Antwort
client.Disconnect()
Notify("Der Server hat innerhalb 2 Sekunden keine Antwort gesendet. Verbindung fehlgeschlagen.")
End
End If
Else
'Connect schlug fehl
Notify "Es konnte keine Verbindung hergestellt werden."
End
End If


'An dieser Stelle ist die Verbindung hergestellt und der Spieler eingeloggt

'Grafik initialisieren
Graphics 800, 600, 0, 0

'Variable zum Zwischenspeichern von Tasten-Stati
Local keyState:Byte[] = New Byte[256]

'Variable zum Beenden des Programms aus der Hauptschleife heraus
Local quitApp:String = Null

Repeat
Cls

'Status-Änderungen von Tasten an den Server senden
UpdateKey(KEY_UP, keyState[KEY_UP])
UpdateKey(KEY_DOWN, keyState[KEY_DOWN])
UpdateKey(KEY_LEFT, keyState[KEY_LEFT])
UpdateKey(KEY_RIGHT, keyState[KEY_RIGHT])

'Client aktualisieren (empfangen/senden)
Local event:TNetClientEvent = client.Update()
While event <> Null
Select event.event
'Der Server hat die Verbindung getrennt
Case TNetClientEvent.DISCONNECT
quitApp = "Der Server hat die Verbindung getrennt."

'Daten vom Server empfangen
Case TNetClientEvent.DATA
'Daten verarbeiten
DatenVerarbeitung(event)

End Select

'Nächstes Ereignis abrufen
event = client.Update()
Wend


'Alle Spieler zeichnen
For Local player:TPlayer = EachIn TPlayer.List
player.Draw()
Next

Flip
Until AppTerminate() Or KeyHit(KEY_ESCAPE) Or quitApp <> Null

EndGraphics

'Client schliessen
client.Disconnect()

'Grund für das Beenden des Programms anzeigen (falls gegeben)
If quitApp <> Null Then Notify(quitApp)

End


'Findet einen Spieler per Name
Function FindPlayer:TPlayer(name:String)
'Gross-/Kleinschreibung spielt keine Rolle
name = name.ToLower()

For Local player:TPlayer = EachIn TPlayer.List
'Namen überprüfen, falls sie identisch sind den Spieler zurückgeben
If player.name.ToLower() = name Then Return player
Next

'Kein Spieler gefunden
Return Null
End Function


'Sender Status-Änderungen einer bestimmten Taste (falls notwendig)
Function UpdateKey(code:Int, lastState:Byte Var)
'Update erforderlich?
Local update:Int = False

If KeyDown(code) And Not lastState Then
lastState = True
update = True 'Update veranlassen
Else If lastState And Not KeyDown(code) Then
lastState = False
update = True 'Update veranlassen
End If

'Update falls erforderlich
If update Then
'Paket erstellen
Local paket:TNetPacket = TNetPacket.Create()
paket.WriteByte(4) 'Nachricht-Nr. 4
paket.WriteByte(code) 'Taste
paket.WriteByte(lastState) 'Status

'Paket senden
client.Send(paket, True, 1)
End If
End Function


'Verarbeitet die vom Server empfangenen Daten
'event:TNetClientEvent Event-Objekt welches den Absender der Nachricht und das Paket selbst enthält
Function DatenVerarbeitung(event:TNetClientEvent)

'Ein Byte vom Paket lesen
'In diesem Beispiel fängt jede Nachricht mit diesem Byte an
'Anhand dieses Bytes wird unterschieden welche Daten vom Paket gelesen werden
Local msgId:Byte = event.Packet.ReadByte()

'Je nach dem Wert des Bytes unterschiedliche Daten lesen/senden etc.
Select msgId
'2 = Neuer Mitspieler
Case 2
'Daten lesen
Local name:String = event.Packet.ReadString()
Local x:Float = event.Packet.ReadFloat()
Local y:Float = event.Packet.ReadFloat()

'Spieler erstellen
Local player:TPlayer = New TPlayer
player.Name = name
player.X = x
player.Y = y

'3 = Spieler verlässt das Spiel
Case 3
'Daten lesen
Local name:String = event.Packet.ReadString()

'Spieler entfernen
Local player:TPlayer = FindPlayer(name)
If player <> Null Then TPlayer.List.Remove(player)

'5 = Positions-Updates
Case 5
'Anzahl Spieler im Update lesen
Local numPlayers:Int = event.Packet.ReadInt()

For Local i:Int = 0 To numPlayers - 1
'Daten von einem Spieler lesen
Local name:String = event.Packet.ReadString()
Local x:Float = event.Packet.ReadFloat()
Local y:Float = event.Packet.ReadFloat()

'Spieler suchen und Position aktualisieren
Local player:TPlayer = FindPlayer(name)
If player <> Null Then
player.X = x
player.Y = y
End If
Next
End Select

End Function


ExampleServer.bmx
BlitzMax: [AUSKLAPPEN]
SuperStrict

'Modul importieren
Import mzehr.net

AppTitle = "ExampleServer"
SeedRnd(MilliSecs())


'Kleine Klasse um Spieler-Infos zu speichern
Type TPlayer
'Name des Spielers
Field Name:String

'Tasten-Stati des Spielers
Field Keys:Byte[]

'Position des Spielers
Field X:Float
Field Y:Float

Method New()
Keys = New Byte[256]
End Method

'Legt den Status einer Taste des Spielers fest (gedrückt/nicht gedrückt)
Method SetKey(code:Int, pressed:Int)
If code >= 0 And code < Keys.Length Then
Keys[code] = pressed
End If
End Method

'Fragt den Status einer Taste ab (gedrückt/nicht gedrückt)
Method GetKey:Int(code:Int)
If code >= 0 And code < Keys.Length Then Return Keys[code]
Return False
End Method
End Type



'Einen Server auf Port 50000 mit maximal 10 Clients/Spielern erstellen
Global server:TNetServer = TNetServer.Create(Null, 50000, 10)

'Fehlermeldung anzeigen wenn der Server nicht initialisiert werden konnte
If server = Null Then
Notify("Der Server konnte nicht initialisiert werden.")
End
End If




'Grafik initialisieren (bei einem Server eigentlich nicht notwendig)
Graphics 800, 600, 0, 0

'Variablen für Positions-Updates an alle Spieler
Local lastPosUpdate:Int = 0
Local numPosUpdates:Int = 15

'Variablen für die Zeitmessung eines Frames (Frame-Unabhängige Programmierung)
Local deltaTime:Float = 0
Local lastFrame:Int = MilliSecs()

'Hauptschleife des Server-Programms
Repeat
deltaTime = (MilliSecs() - lastFrame) / 1000.0
lastFrame = MilliSecs()
Cls

'Server aktualisieren (Nachrichten senden/empfangen)
Local event:TNetServerEvent = server.Update()

'Solange Events vorhanden sind, diese abarbeiten und Update erneut ausführen
'bis kein Event mehr zurückgegeben wird
While event <> Null

Select event.event
'Ein Client/Spieler hat sich mit dem Server verbunden
Case TNetServerEvent.CONNECT
'Spieler-Objekt erstellen
Local player:TPlayer = New TPlayer
player.X = Rand(20, 780)
player.Y = Rand(20, 580)

'Objekt dem Client zuweisen
event.Peer.SetData(player)

'Bereits verbundene Spieler müssen noch über den neuen Spieler informiert werden
'Dies wird allerdings erst gemacht wenn der Name des Spielers bekannt ist
'(siehe DatenVerarbeitung_NichtAngemeldet)


'Ein Client/Spieler hat den Server verlassen
Case TNetServerEvent.DISCONNECT
'Allen verbundenen Clients/Spielern mitteilen dass dieser Spieler
'nicht mehr verbunden ist
SendeTrennungsNachricht(event)

'Zuvor erstelltes Spieler-Objekt entfernen
event.Peer.SetData(Null)


'Ein Client/Spieler hat Daten gesendet
Case TNetServerEvent.DATA
'Mit dieser Zeile wird das Spieler-Objekt geholt, das zuvor zugewiesen wurde (bei CONNECT)
Local player:TPlayer = TPlayer(event.Peer.GetData())

'Daten zwecks Übersichtlichkeit in einer separaten Funktion verarbeiten
If player.Name = Null Then
'Wenn der Spieler noch keinen Namen hat, dannt wird nur die Nachricht mit der Nr. 1 akzeptiert
DatenVerarbeitung_NichtAngemeldet(event)
Else
'Wenn der Spieler anschliessend einen Namen gesendet hat (mit der Nachricht Nr. 1),
'werden vom Server auch Nachrichten mit anderen Nummern verarbeitet
DatenVerarbeitung_Angemeldet(event)
End If

End Select

'Update erneut ausführen
event = server.Update()
Wend


'Welt/Spieler aktualisieren
For Local peer:TNetPeer = EachIn server.GetClients()
Local player:TPlayer = TPlayer(peer.GetData())

'Spieler aufgrund seiner Tastendrücke 20 Pixel/Sekunde bewegen lassen
player.X:+player.GetKey(KEY_LEFT) * -30 * deltaTime + player.GetKey(KEY_RIGHT) * 30 * deltaTime;
player.Y:+player.GetKey(KEY_UP) * -30 * deltaTime + player.GetKey(KEY_DOWN) * 30 * deltaTime;

'Position auf der X-Achse limitieren
If player.X < 0
player.X = 0
ElseIf player.X > 800
player.X = 800
EndIf

'Position auf der Y-Achse limitieren
If player.Y < 0
player.Y = 0
ElseIf player.Y > 600
player.Y = 600
End If
Next


'15 mal pro Sekunde die Position jedes Spielers an jeden Client senden
If MilliSecs() - lastPosUpdate > 1000 / numPosUpdates

'Paket erstellen mit Infos zu jedem Spieler
Local paket:TNetPacket = TNetPacket.Create()
paket.WriteByte(5) 'Nachricht-Nr. 5
paket.WriteInt(server.GetClients().Count()) 'Anzahl Spieler

'Spieler-Infos in das Paket schreiben
For Local peer:TNetPeer = EachIn server.GetClients()
Local player:TPlayer = TPlayer(peer.GetData())
paket.WriteString(player.Name)
paket.WriteFloat(player.X)
paket.WriteFloat(player.Y)
Next

'Paket an alle Spieler senden
'Diesmal als unwichtiges Paket auf Kanal 0, da die Positionen sehr häufig gesendet werden
'macht es in der Regel nichts wenn mal 1 Paket verloren geht
server.Broadcast(paket)

'Letztes Update = gerade eben
lastPosUpdate = MilliSecs()
EndIf


Flip
Until AppTerminate() Or KeyHit(KEY_ESCAPE)

'Grafik-Modus beenden
EndGraphics

'Server herunterfahren
server.Shutdown()

'Programm-Ende
End


'Verarbeitet von Clients empfangene Daten (wenn diese noch nicht Angemeldet sind)
'event:TNetServerEvent Event-Objekt mit dem Absender des Pakets und dem Paket selber
Function DatenVerarbeitung_NichtAngemeldet(event:TNetServerEvent)

'Mit dieser Zeile wird das zuvor zugewiesene Spieler-Objekt vom Client geholt
'Über dieses Objekt kann z.B. auf den Namen des Spielers zugegriffen werden
Local player:TPlayer = TPlayer(event.Peer.GetData())

'Ein Byte vom Paket lesen
'In diesem Beispiel fängt jede Nachricht mit diesem Byte an
'Anhand dieses Bytes wird unterschieden welche Daten vom Paket gelesen werden
Local msgId:Byte = event.Packet.ReadByte()

'Je nach dem Wert des Bytes unterschiedliche Daten lesen/senden etc.
Select msgId

'1 = Name (Der Spieler sendet seinen gewünschten Namen)
Case 1
'Name vom Paket lesen
Local name:String = event.Packet.ReadString()

'Name überprüfen
If name = Null Or name.Length < 3 Then
'Spieler-Name ist zu KURZ -> Nachricht zurück senden
Local paket:TNetPacket = TNetPacket.Create()
paket.WriteByte(1) 'Nachricht-Nr. 1
paket.WriteByte(0) 'Anmeldung fehlgeschlagen
paket.WriteString("Dieser Name ist zu kurz.") 'Fehlermeldung

'Das Antwort-Paket zurücksenden, als wichtige Nachricht und auf dem Kanal 1 (nicht komprimiert)
server.Send(event.Peer, paket, True, 1)

ElseIf FindPlayer(name) <> Null
'Es existiert bereits ein Spieler mit diesem Namen
Local paket:TNetPacket = TNetPacket.Create()
paket.WriteByte(1) 'Nachricht-Nr. 1
paket.WriteByte(0) 'Anmeldung fehlgeschlagen
paket.WriteString("Dieser Name wird bereits verwendet.") 'Fehlermeldung

'Das Antwort-Paket zurücksenden, als wichtige Nachricht und auf dem Kanal 1 (nicht komprimiert)
server.Send(event.Peer, paket, True, 1)

Else
'Spieler-Name ist OK -> Name setzen
'(Dadurch gilt der Spieler als angemeldet und darf nun auch andere Nachrichten senden)
player.name = name

'Antwort senden
Local paket:TNetPacket = TNetPacket.Create()
paket.WriteByte(1) 'Nachricht-Nr. 1
paket.WriteByte(1) 'Anmeldung erfolgreich (keine Fehlermeldung notwendig)

'Infos über alle anderen Spieler in das Paket schreiben
paket.WriteInt(server.GetClients().Count() - 1)
For Local peer:TNetPeer = EachIn server.GetClients()
'Eigenen Spieler ignorieren
If peer = event.peer Then Continue

Local p:TPlayer = TPlayer(peer.GetData())
paket.WriteString(p.name)
paket.WriteFloat(p.X)
paket.WriteFloat(p.Y)
Next

'Das Antwort-Paket zurücksenden, als wichtige Nachricht und auf dem Kanal 1 (nicht komprimiert)
server.Send(event.Peer, paket, True, 1)

'Bereits verbundene Spieler erst jetzt über den neuen Spieler informieren (da nun erst der Name bekannt ist)
SendeVerbindungsNachricht(event)
EndIf

End Select

End Function


'Sucht nach einem TPlayer mit dem angegebenen Namen
'name:String Name des Spielers
'Gibt Null oder den Spieler zurück, wenn er gefunden wurde
Function FindPlayer:TPlayer(name:String)

'Gross-/Kleinschreibung des Namens spielt keine Rolle
name = name.ToLower()

For Local peer:TNetPeer = EachIn server.GetClients()
'TPlayer-Objekt vom Client holen
Local player:TPlayer = TPlayer(peer.GetData())

'Prüfen ob der Name übereinstimmt und falls ja, den Spieler zurückgeben
If player <> Null And player.name.ToLower() = name Then Return player
Next

'Kein Spieler gefunden
Return Null
End Function


'Verarbeitet von Clients empfangene Daten (wenn diese Angemeldet sind)
'event:TNetServerEvent Event-Objekt mit dem Absender des Pakets und dem Paket selber
Function DatenVerarbeitung_Angemeldet(event:TNetServerEvent)

'Mit dieser Zeile wird das zuvor zugewiesene Spieler-Objekt vom Client geholt
'Über dieses Objekt kann z.B. auf den Namen des Spielers zugegriffen werden
Local player:TPlayer = TPlayer(event.Peer.GetData())

'Ein Byte vom Paket lesen
'In diesem Beispiel fängt jede Nachricht mit diesem Byte an
'Anhand dieses Bytes wird unterschieden welche Daten vom Paket gelesen werden
Local msgId:Byte = event.Packet.ReadByte()

'Je nach dem Wert des Bytes unterschiedliche Daten lesen/senden etc.
Select msgId

'4 = Tasten-Status des Clients
Case 4
'Tasten-Code lesen
Local code:Byte = event.Packet.ReadByte()

'Status lesen (gedrückt/nicht gedrückt)
Local pressed:Byte = event.Packet.ReadByte()

'Taste beim Spieler-Objekt aktualisieren
player.SetKey(code, pressed)

End Select

End Function


'Sendet eine Verbindungsnachricht an alle anderen Spieler
'event:TNetServerEvent Event-Objekt mit dem Client der sich verbunden hat
Function SendeVerbindungsNachricht(event:TNetServerEvent)

'Diese Zeile holt das Spieler-Objekt
Local player:TPlayer = TPlayer(event.Peer.GetData())

'Paket erstellen, dass an alle anderen Spieler gesendet wird
Local paket:TNetPacket = TNetPacket.Create()
paket.WriteByte(2) 'Nachricht-Nr. 2
paket.WriteString(player.Name) 'Name des Spielers
paket.WriteFloat(player.X) 'X-Position des Spielers
paket.WriteFloat(player.Y) 'Y-Position des Spielers

For Local client:TNetPeer = EachIn server.GetClients()
'Nicht an den Client senden der sich verbunden hat
If client = event.Peer Then Continue

'Aber an alle anderen Senden
server.Send(client, paket, True, 1)
Next

End Function

'Sendet eine Trennungsnachricht an alle anderen Spieler
'event:TNetServerEvent Event-Objekt mit dem Client der die Verbindung getrennt hat
Function SendeTrennungsNachricht(event:TNetServerEvent)

'Diese Zeile holt das Spieler-Objekt
Local player:TPlayer = TPlayer(event.Peer.GetData())

'Paket erstellen, dass an alle anderen Spieler gesendet wird
Local paket:TNetPacket = TNetPacket.Create()
paket.WriteByte(3) 'Nachricht-Nr. 3
paket.WriteString(player.Name) 'Name des Spielers

For Local client:TNetPeer = EachIn server.GetClients()
'Nicht an den Client senden der die Verbindung getrennt hat
If client = event.Peer Then Continue

'Aber an alle anderen Senden
server.Send(client, paket, True, 1)
Next

End Function
  • Zuletzt bearbeitet von Jolinah am So, März 18, 2018 21:22, insgesamt 9-mal bearbeitet

Thorsten

BeitragDo, Nov 17, 2011 17:09
Antworten mit Zitat
Benutzer-Profile anzeigen
Ich werde mir die Lib mal ansehen und in Zukunft eine Rückmeldung abgeben, eventuell dann auch in einem kommerziellen Projekt verwenden.

mfG,

Thorsten

Firstdeathmaker

BeitragDo, Nov 17, 2011 18:39
Antworten mit Zitat
Benutzer-Profile anzeigen
Danke für das Modul, werd ich bei Gelegenheit Nutzen und so lange im Hinterkopf behalten!
www.illusion-games.de
Space War 3 | Space Race | Galaxy on Fire | Razoon
Gewinner des BCC #57 User posted image

juse4pro

BeitragMo, Dez 05, 2011 2:55
Antworten mit Zitat
Benutzer-Profile anzeigen
Um mal Resonanz zu liefern:
Ich finde die Idee des Moduls richtig genial! Ich hab mich als erstes (zu Beginn meiner Programmiererzeiten) mit den Sockets direkt beschäftigt und gemerkt, dass das einfach zu fehleranfällig war. Dann habe ich Vertex' BNetEx verwendet.
Und irgendwann stoße ich durch Zufall auf das hier... Und ich muss sagen: *Daumen hoch*
Das lästige "sichere" UDP-Pakete Gehabe ist jetzt Geschichte und trotzdem du anscheinend noch ein bisschen was mit den Paketen machst (z.b. Komprimierung [Was ich wieder sehr praktisch finde, in Kombination mit dem automatischen Paket-Teilen]) der Ping noch 1A bleibt, und deine ganzen Methoden und Funktionen lächerlich mehr Aufwand kosten, als wenn ich das ganze selbst, nach einer Woche "roter Augen", einprogrammiert hätte.
Hab jetzt schon den Anfang meines SpaceShooters mit dem Modul ausgestattet und bis jetzt lief alles klasse! Wink

Eins hab' ich noch nicht ganz mitbekommen: Wird eine Verbindung mit einem Client als getrennt angesehen, wenn sein Prozess gekillt wurde (also kein sauberer Disconnect durchgeführt wurde)?
Wenn sich wirklich keine Probleme zeigen, werde ich dieses Modul, dem von Vertex' vorziehen. ;D

Jolinah

BeitragDi, Dez 06, 2011 1:02
Antworten mit Zitat
Benutzer-Profile anzeigen
Danke Wink

Wobei die Idee des Moduls eigentlich die Idee von enet selber ist. Das Modul ist wirklich nur ein OOP-Wrapper für enet. Aber schön dass du es verwenden kannst Smile

Zu deiner Frage: Enet regelt das sozusagen automatisch mit regelmässigen Pings. Wenn eine Verbindung abreisst sollte ein Spieler auf dem Server nach ein paar Sekunden (10?, ich weiss es nicht genau) getrennt werden. Das heisst, es sollte sogar ein Disconnected-Event auf dem Server ausgelöst werden wenn ich mich nicht irre.

Auch an die anderen ein Danke fürs anschauen/testen!

Jolinah

BeitragSo, März 18, 2018 17:31
Antworten mit Zitat
Benutzer-Profile anzeigen
Sorry für den Doppelpost (aber sonst hätte niemand etwas davon mitbekommen) Wink

Es gibt jetzt eine Version 1.1 welche das neuste ENet 1.3.13 verwendet (siehe 1. Post). Hier noch den Quellcode dazu, falls die Download-Links oben einmal nicht funktionieren sollten:

net.bmx (Hauptdatei des Moduls)
BlitzMax: [AUSKLAPPEN]
SuperStrict

Rem
bbdoc: Network module based on ENet (http://enet.bespin.org)
End Rem

Module mzehr.net

ModuleInfo "Version 2.0"
ModuleInfo "Author: Michael Zehr (Jolinah)"
ModuleInfo "License: MIT X11 License (License.txt)"
ModuleInfo "Credit: Lee Salzman (http://enet.bespin.org)"

ModuleInfo "History: 1.0 First release"
ModuleInfo "History: 1.1 Instead of importing pub.enet, the newest source code of ENet has been compiled into the module (version 1.3.13 as of now). However, the protocol of ENet version 1.3 and above is not compatible with ENet versions 1.2 and below. You will need to redistribute the client to all existing players after updating the server or they won't be able to play on the new server."

Import brl.linkedlist
Import brl.bank
Import brl.bankstream

'Import pub.stdc
Import pub.zlib

Import "enet/enet/*.h"
Import "enet/callbacks.c"
Import "enet/compress.c"
Import "enet/host.c"
Import "enet/list.c"
Import "enet/packet.c"
Import "enet/peer.c"
Import "enet/protocol.c"

?Win32
Import "enet/win32.c"
Import "-lws2_32"
Import "-lwinmm"
?MacOs
Import "enet/unix.c"
?Linux
Import "enet/unix.c"
?

Include "TNetServer.bmx"
Include "TNetPeer.bmx"
Include "TNetClient.bmx"
Include "TNetEvent.bmx"
Include "TNetPacket.bmx"

Type ENetEvent
Field event:Int
Field peer:Byte Ptr
Field channelID:Byte
Field data:Int
Field packet:Byte Ptr
End Type

Extern "C"
Const ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT:Int = 1
Const ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT:Int = 255

Const ENET_HOST_ANY:Int = 0

Const ENET_PACKET_FLAG_RELIABLE:Int = 0
Const ENET_PACKET_FLAG_UNSEQUENCED:Int = 1

'Address functions
Function enet_address_get_host:Int(address:Byte Ptr, HostName$z, nameLength:Int)
Function enet_address_get_host_ip:Int(address:Byte Ptr, HostName$z, nameLength:Int)
Function enet_address_set_host:Int(address:Byte Ptr, HostName$z)

'Global functions
Function enet_deinitialize()
Function enet_initialize:Int()
Function enet_initialize_with_callbacks:Int(version:Int, inits:Byte Ptr)
Function enet_linked_version:Int()

'Host functions
Function enet_host_bandwith_limit(host:Byte Ptr, incomingBandwith:Int, outgoingBandwith:Int)
Function enet_host_broadcast(host:Byte Ptr, channelID:Byte, packet:Byte Ptr)
Function enet_host_channel_limit(host:Byte Ptr, channelLimit:Int)
Function enet_host_check_events:Int(host:Byte Ptr, event:Byte Ptr)
Function enet_host_compress(host:Byte Ptr, compressor:Byte Ptr)
Function enet_host_compress_with_range_coder:Int(host:Byte Ptr)
Function enet_host_connect:Byte Ptr(host:Byte Ptr, address:Byte Ptr, channelCount:Int, data:Int = 0)
Function enet_host_create:Byte Ptr(address:Byte Ptr, peerCount:Int, channelLimit:Int, incomingBandwith:Int, outgoingBandwith:Int)
Function enet_host_destroy(host:Byte Ptr)
Function enet_host_flush(host:Byte Ptr)
Function enet_host_service:Int(host:Byte Ptr, event:Byte Ptr, timeout:Int)

'Packet functions
Function enet_packet_create:Byte Ptr(data:Byte Ptr, dataLength:Int, flags:Int)
Function enet_packet_destroy(packet:Byte Ptr)
Function enet_packet_resize:Int(packet:Byte Ptr, dataLength:Int)

'Peer functions
Function enet_peer_disconnect(peer:Byte Ptr, data:Int = 0)
Function enet_peer_disconnect_later(peer:Byte Ptr, data:Int = 0)
Function enet_peer_disconnect_now(peer:Byte Ptr, data:Int = 0)
Function enet_peer_ping(peer:Byte Ptr)
Function enet_peer_ping_interval(peer:Byte Ptr, pingInterval:Int)
Function enet_peer_receive:Byte Ptr(peer:Byte Ptr, channelID:Byte Ptr)
Function enet_peer_reset(peer:Byte Ptr)
Function enet_peer_send:Int(peer:Byte Ptr, channelID:Byte, packet:Byte Ptr)
Function enet_peer_throttle_configure(peer:Byte Ptr, interval:Int, acceleration:Int, deceleration:Int)
Function enet_peer_timeout(peer:Byte Ptr, timeoutLimit:Int, timeoutMinimum:Int, timeoutMaximum:Int)

'Undocumented / internal functions
'Function enet_host_bandwith_throttle(host:Byte Ptr)
'Function enet_crc32:Int(buffers:Byte Ptr, bufferCount:Int)
'Function enet_peer_dispatch_incoming_reliable_commands(peer:Byte Ptr, channel:Byte Ptr)
'Function enet_peer_dispatch_incoming_unreliable_commands(peer:Byte Ptr, channel:Byte Ptr)
'Function enet_peer_on_connect(peer:Byte Ptr)
'Function enet_peer_on_disconnect(peer:Byte Ptr)
'Function enet_peer_queue_acknowledgement:Byte Ptr(peer:Byte Ptr, command:Byte Ptr, sentTime:Short)
'Function enet_peer_queue_incoming_command:Byte Ptr(peer:Byte Ptr, command:Byte Ptr, data:Byte Ptr, dataLength:Int, flags:Int, fragmentCount:Int)
'Function enet_peer_queue_outgoing_command:Byte Ptr(peer:Byte Ptr, command:Byte Ptr, packet:Byte Ptr, offset:Int, length:Short)
'Function enet_peer_reset_queues(peer:Byte Ptr)
'Function enet_peer_setup_outgoing_command(peer:Byte Ptr, outgoingCommand:Byte Ptr)
'Function enet_peer_throttle:Int(peer:Byte Ptr, rtt:Int)
'Function enet_socket_accept:Int(socket:Int, address:Byte Ptr)
'Function enet_socket_bind:Int(socket:Int, address:Byte Ptr)
'Function enet_socket_create:Int(socketType:Int)
'Function enet_socket_destroy(socket:Int)
'Function enet_socket_get_address:Int(socket:Int, address:Byte Ptr)
'Function enet_socket_get_option:Int(socket:Int, socketOption:Int, option:Int Ptr)
'Function enet_socket_listen:Int(socket:Int, port:Int)
'Function enet_socket_receive:Int(socket:Int, address:Byte Ptr, buffer:Byte Ptr, length:Int)
'Function enet_socket_send:Int(socket:Int, address:Byte Ptr, buffer:Byte Ptr, length:Int)
'Function enet_socket_set_option:Int(socket:Int, socketOption:Int, option:Int)
'Function enet_socket_shutdown:Int(socket:Int, socketShutdown:Int)
'Function enet_socket_wait:Int(socket:Int, param1:Int Ptr, param2:Int)
'Function enet_socketset_select:Int(socket:Int, socketset:Byte Ptr, socketset2:Byte Ptr, param3:Int)
End Extern

Function enet_peer_address(peer:Byte Ptr, host_ip:Int Var, host_port:Int Var)
Local ip:Int = (Int Ptr peer)[3]
Local port:Int = (Short Ptr peer)[8]
?LittleEndian
ip = (ip Shr 24) | (ip Shr 8 & $ff00) | (ip Shl 8 & $ff0000) | (ip Shl 24)
?
host_ip = ip
host_port = port
End Function

Function enet_packet_data:Byte Ptr(packet:Byte Ptr)
Return Byte Ptr((Int Ptr packet)[2])
End Function

Function enet_packet_size:Int(packet:Byte Ptr)
Return (Int Ptr packet)[3]
End Function

Function enet_address_create:Byte Ptr(host_ip:Int, host_port:Int)
Local t:Byte Ptr = MemAlloc(6)
?BigEndian
(Int Ptr t)[0] = host_ip
?LittleEndian
(Int Ptr t)[0] = (host_ip Shr 24) | (host_ip Shr 8 & $ff00) | (host_ip Shl 8 & $ff0000) | (host_ip Shl 24)
?
(Short Ptr t)[2] = host_port
Return t
End Function

Function enet_address_destroy(address:Byte Ptr)
MemFree address
End Function

enet_initialize()
atexit_(enet_deinitialize)


TNetClient.bmx
BlitzMax: [AUSKLAPPEN]
Rem
bbdoc: Class used to establish a connection with a server.
End Rem

Type TNetClient
Field _host:Byte Ptr
Field _peer:Byte Ptr

Rem
bbdoc: Creates a new and unconnected client.
End Rem

Function Create:TNetClient()
Local client:TNetClient = New TNetClient
Return client
End Function

Rem
bbdoc: Establishes a connection with a server.
returns: True if the connection has been established, False if not.
about:
<pre>
host:String IP address or hostname on which to listen for incoming connections.
port:Int Port on which to listen for incoming connections.
connectTimeout:Int (Optional) Maximum time in ms to wait for the connection.
channelCount:Int (Optional) Number of communication channels. Will be limited to 1-255. (Default: 2)
</pre>
End Rem

Method Connect:Int(host:String, port:Int, connectTimeout:Int = 5000, channelCount:Int = 2)
'In case we are connected -> disconnect
Disconnect()

'Create address struct
Local addr:Byte Ptr = enet_address_create(0, port)
If addr = Null Then Return False
enet_address_set_host(addr, host)

If channelCount < ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT
channelCount = ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT
ElseIf channelCount > ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT
channelCount = ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT
End If

'Create host
_host = enet_host_create(Null, 1, channelCount, 0, 0)
If _host = Null Then Return False

'Connect to the remote host
_peer = enet_host_connect(_host, addr, channelCount)
enet_address_destroy(addr)

'No peer available for connection?
If _peer = Null Then
enet_host_destroy(_host)
_host = Null
Return False
EndIf

Local ev:ENetEvent = New ENetEvent

'Wait for connection response
If enet_host_service(_host, ev, connectTimeout) > 0 And ev.event = TNetClientEvent.CONNECT
'Connection successful
Return True
Else
'Timeout reached
enet_peer_reset(_peer)
enet_host_destroy(_host)
_host = Null
_peer = Null
Return False
End If
End Method

Rem
bbdoc: Closes the connection to the server.
about:
<pre>
disconnectTime:Int (Optional) Maximum time in ms to wait for remaining messages to be sent before the connection will be closed.
</pre>
End Rem

Method Disconnect(disconnectTime:Int = 2000)
'Already disconnected?
If _host = Null And _peer = Null Then Return

If _peer = Null Then
enet_host_destroy(_host)
_host = Null
Return
End If

If _host = Null Then
enet_peer_reset(_peer)
_peer = Null
Return
End If

'Immediate disconnect (no server notification)
If disconnectTime <= 0 Then
enet_peer_reset(_peer)
enet_host_destroy(_host)
_host = Null
_peer = Null
Return
End If

'Gentle disconnect (wait for server acknowledgement)
Local ev:ENetEvent = New ENetEvent
Local begin:Int = MilliSecs()

enet_peer_disconnect(_peer)

While enet_host_service(_host, ev, disconnectTime) > 0
Select ev.event
Case TNetClientEvent.DATA
'Drop any received packets
enet_packet_destroy(ev.packet)

Case TNetClientEvent.DISCONNECT
'Disconnection successful
Exit
End Select

'Disconnect time reached?
If MilliSecs() - begin >= disconnectTime Then Exit
Wend

'Reset the peer and destroy the host
enet_peer_reset(_peer)
enet_host_destroy(_host)
_host = Null
_peer = Null
End Method

Rem
bbdoc: Updates the client (receives and sends messages).
returns: An object of type TNetClientEvent that represents the last event or Null if there was no event.
about:
<pre>
waitTime:Int (Optional) Time in ms to wait for incoming messages. (Default = 0, this means the function will return immediatley if there are no messages to receive).
</pre>
End Rem

Method Update:TNetClientEvent(waitTime:Int = 0)
If _host = Null Then Return Null

Local ev:ENetEvent = New ENetEvent
If enet_host_service(_host, ev, waitTime) > 0
Select ev.event
Case TNetClientEvent.DATA
Return TNetClientEvent.ClientEvent(ev.event, TNetPacket.FromEnet(ev.packet), ev.channelID)

Case TNetClientEvent.DISCONNECT
Return TNetClientEvent.ClientEvent(ev.event, Null, 0)
End Select
Else
Return Null
End If
End Method

Rem
bbdoc: Sends a packet / a message to the server.
about:
<pre>
packet:TNetPacket The packet that will be sent
reliable:Int (Optional) Whether the packet should be sent reliable or not (Default: False, this is suitable for unimportant anf fast data like player positions)
channel:Int (Optional) The channel used for transmitting the packet (Default: 0, reliable/important messages should be sent on a different channel than unimportant messages, for example channel 1)
compressed:Int (Optional) Whether the data will be compressed or not (Default: False, should only be used with large data, i.e. when sending 100x100 tiles etc.)
</pre>
End Rem

Method Send(packet:TNetPacket, reliable:Int = False, channel:Int = 0, compressed:Int = False)
If packet = Null Then Return
If _host = Null Or _peer = Null Then Return

Local flags:Int = 0
If reliable Then flags = ENET_PACKET_FLAG_RELIABLE

Local p:Byte Ptr = packet.EnetPacket(flags, compressed)
If p = Null Then Return

enet_peer_send(_peer, channel, p)
End Method

Rem
bbdoc: Sends all buffered messages as soon as possible or immediately.
about:
It__COMMENT16__
End Rem

Method Flush()
If _host = Null Then Return
enet_host_flush(_host)
End Method
End Type


TNetEvent.bmx
BlitzMax: [AUSKLAPPEN]
Rem
bbdoc: An event raised by the client.
End Rem

Type TNetClientEvent
Rem
bbdoc: Server: A new client has established a connection with the server. | Client: The connection with the server has been established.
End Rem

Const CONNECT:Int = 1

Rem
bbdoc: The connection was closed (client: connection with the server | server: connection with a specific client).
End Rem

Const DISCONNECT:Int = 2

Rem
bbdoc: Data received (client: from the server | server: from a specific client).
End Rem

Const DATA:Int = 3

Rem
bbdoc: Event that was raised (equals to TNetClientEvent.CONNECT or DATA).
End Rem

Field Event:Int

Rem
bbdoc: The packet that was received (only available in a DATA event).
End Rem

Field Packet:TNetPacket

Rem
bbdoc: The channel on which the packet was received.
End Rem

Field Channel:Int

Function ClientEvent:TNetClientEvent(event:Int, packet:TNetPacket, channel:Int)
Local ev:TNetClientEvent = New TNetClientEvent
ev.event = event
ev.packet = packet
ev.channel = channel
Return ev
End Function
End Type

Rem
bbdoc: An event raised by the server.
End Rem

Type TNetServerEvent Extends TNetClientEvent
Rem
bbdoc: The client that received data/connected/disconnected.
End Rem

Field Peer:TNetPeer

Function ServerEvent:TNetServerEvent(event:Int, packet:TNetPacket, peer:TNetPeer, channel:Int)
Local ev:TNetServerEvent = New TNetServerEvent
ev.event = event
ev.packet = packet
ev.peer = peer
ev.channel = channel
Return ev
End Function
End Type


TNetPacket.bmx
BlitzMax: [AUSKLAPPEN]
Rem
bbdoc: A data packet/message. Data can be written into or read from the packet.
End Rem

Type TNetPacket
Field _packet:Byte Ptr
Field _bank:TBank
Field _stream:TBankStream

Rem
bbdoc: Creates a new packet (for sending).
End Rem

Function Create:TNetPacket()
Local packet:TNetPacket = New TNetPacket
packet._bank = TBank.Create(0)
packet._stream = TBankStream.Create(packet._bank)
packet.WriteByte(0)
Return packet
End Function

Rem
bbdoc: Reads a packet from an ENet packet struct.
about:
This function is used internally and should not be called manually.
End Rem

Function FromEnet:TNetPacket(p:Byte Ptr)
Local packet:TNetPacket = New TNetPacket
packet._packet = p
packet._bank = TBank.CreateStatic(enet_packet_data(p), enet_packet_size(p))
packet._stream = TBankStream.Create(packet._bank)

'Decompression
Local cmp:Byte = packet.ReadByte()
If cmp Then
Local buf:Byte Ptr = MemAlloc(packet._bank.Size() - 1)
Local l:Int = 0
uncompress(buf, l, Varptr packet._bank.buf()[1], packet._bank.Size() - 1)

packet._stream.Close()
packet._bank = TBank.Create(l)
packet._stream = TBankStream.Create(packet._bank)

MemCopy(packet._bank.buf(), buf, l)
MemFree(buf)
End If

Return packet
End Function

Method Delete()
Destroy()
End Method

Rem
bbdoc: Releases the memory used by the packet.
about:
Will be called automatically on deconstruction of the object (garbage collector), but can also be called manually.
End Rem

Method Destroy()
If _stream <> Null Then
_stream.Close()
_stream = Null
End If

If _bank <> Null Then
_bank = Null
End If

If _packet <> Null Then
enet_packet_destroy(_packet)
_packet = Null
End If
End Method

Rem
bbdoc: Reads multiple Bytes from the packet and writes them to the specified memory location.
returns: Number of successfully read bytes.
about:
<pre>
buf:Byte Ptr Memory location where the bytes will be written to.
count:Int Number of bytes that should be read.
</pre>
End Rem

Method Read:Int(buf:Byte Ptr, count:Int)
Return _stream.Read(buf, count)
End Method

Rem
bbdoc: Reads a byte from the packet.
End Rem

Method ReadByte:Byte()
Return _stream.ReadByte()
End Method

Rem
bbdoc: Reads multiple Bytes from the packet and writes them to the specified memory location.
returns: Number of successfully read bytes.
about:
<pre>
buf:Byte Ptr Memory location where the bytes will be written to.
count:Int Number of bytes that should be read.
</pre>
End Rem

Method ReadBytes:Int(buf:Byte Ptr, count:Int)
Return _stream.ReadBytes(buf, count)
End Method

Rem
bbdoc: Reads a short from the packet.
End Rem

Method ReadShort:Short()
Return _stream.ReadShort()
End Method

Rem
bbdoc: Reads an int from the packet.
End Rem

Method ReadInt:Int()
Return _stream.ReadInt()
End Method

Rem
bbdoc: Reads a long from the packet.
End Rem

Method ReadLong:Long()
Return _stream.ReadLong()
End Method

Rem
bbdoc: Reads a float from the packet.
End Rem

Method ReadFloat:Float()
Return _stream.ReadFloat()
End Method

Rem
bbdoc: Reads a double from the packet.
End Rem

Method ReadDouble:Double()
Return _stream.ReadDouble()
End Method

Rem
bbdoc: Reads a string from the packet.
about:
Reads an int that specifies the length of the string and then the string itself.
End Rem

Method ReadString:String()
Local l:Int = _stream.ReadInt()
If l = -1 Then Return Null
Return _stream.ReadString(l)
End Method

Rem
bbdoc: Reads a line of text from the packet.
End Rem

Method ReadLine:String()
Return _stream.ReadLine()
End Method

Rem
bbdoc: Reads an object from the packet (untested).
End Rem

Method ReadObject:Object()
Return _stream.ReadObject()
End Method

Rem
bbdoc: Writes multiple bytes into the packet.
about:
<pre>
buf:Byte Ptr Memory location from where the data is read and then written to the packet.
count:Int Number of bytes that should be read/written.
</pre>
End Rem

Method Write(buf:Byte Ptr, count:Int)
_stream.Write(buf, count)
End Method

Rem
bbdoc: Writes a byte into the packet.
End Rem

Method WriteByte(value:Byte)
_stream.WriteByte(value)
End Method

Rem
bbdoc: Writes multiple bytes into the packet.
about:
<pre>
buf:Byte Ptr Memory location from where the data is read and then written to the packet.
count:Int Number of bytes that should be read/written.
</pre>
End Rem

Method WriteBytes(buf:Byte Ptr, count:Int)
_stream.WriteBytes(buf, count)
End Method

Rem
bbdoc: Writes a short into the packet.
End Rem

Method WriteShort(value:Short)
_stream.WriteShort(value)
End Method

Rem
bbdoc: Writes an int into the packet.
End Rem

Method WriteInt(value:Int)
_stream.WriteInt(value)
End Method

Rem
bbdoc: Writes a long into the packet.
End Rem

Method WriteLong(value:Long)
_stream.WriteLong(value)
End Method

Rem
bbdoc: Writes a float into the packet.
End Rem

Method WriteFloat(value:Float)
_stream.WriteFloat(value)
End Method

Rem
bbdoc: Writes a double into the packet.
End Rem

Method WriteDouble(value:Double)
_stream.WriteDouble(value)
End Method

Rem
bbdoc: Writes a string into the packet.
about:
Writes an int containing the length of the string followed by the string itself.
End Rem

Method WriteString(value:String)
If value = 0
_stream.WriteInt(-1)
Return
Else
_stream.WriteInt(value.Length)
_stream.WriteString(value)
End If
End Method

Rem
bbdoc: Writes a line of text into the packet.
End Rem

Method WriteLine(value:String)
_stream.WriteLine(value)
End Method

Rem
bbdoc: Writes an object to the packet (untested).
End Rem

Method WriteObject(value:Object)
_stream.WriteObject(value)
End Method

Rem
bbdoc: Converts the packet to an ENet packet struct.
about:
This method is used internally and should not be called manually.
End Rem

Method EnetPacket:Byte Ptr(enet_flags:Int = 0, compressed:Int = False)
If _packet <> Null Then Return _packet
If _bank = Null Then Return Null

If compressed Then
'Compression
Local cmp:Byte Ptr = MemAlloc(_bank.Size() - 1)
Local cmpLen:Int = 0
compress(cmp, cmpLen, Varptr _bank.buf()[1], _bank.Size() - 1)

Local buf:Byte Ptr = MemAlloc(cmpLen + 1)
MemCopy(Varptr buf[1], cmp, cmpLen)
buf[0] = 1

Local p:Byte Ptr = enet_packet_create(buf, cmpLen + 1, enet_flags)

MemFree(cmp)
MemFree(buf)

Return p
Else
'No compression
Return enet_packet_create(_bank.buf(), _bank.Size(), enet_flags)
End If
End Method

End Type


TNetPeer.bmx
BlitzMax: [AUSKLAPPEN]
Rem
bbdoc: Represents a peer/client/player on the server.
End Rem

Type TNetPeer
Field _peer:Byte Ptr
Field _data:Object

Rem
bbdoc: Creates a new peer from an ENet peer struct.
about:
This method is used internally by the server and should not be called manually.
End Rem

Function Create:TNetPeer(peer:Byte Ptr)
Local p:TNetPeer = New TNetPeer
p._peer = peer
Return p
End Function

Rem
bbdoc: Returns the ENet peer struct.
about:
This methos is used internally by the server and should not be called manually.
End Rem

Method GetPeer:Byte Ptr()
Return _peer
End Method

Rem
bbdoc: Gets the data assigned to this peer (see SetData).
End Rem

Method GetData:Object()
Return _data
End Method

Rem
bbdoc: Assigns a custom data object to this peer.
about:
This can be used to assign a player object or other data to the peer.
On the server, you can retrieve a list of connected peers with TNetServer.GetClients() and those
peers (TNetPeer) contain the assigned data object which can be fetched by calling TNetPeer.GetData().
End Rem

Method SetData(value:Object)
_data = value
End Method
End Type


TNetServer.bmx
BlitzMax: [AUSKLAPPEN]
Rem
bbdoc: Class used to run a server.
End Rem

Type TNetServer
Field _host:Byte Ptr
Field _peers:TList

Method New()
_peers = New TList
End Method

Rem
bbdoc: Creates a new server that listens for incoming connections on the specified ip address and port.
returns: An object of Type TNetServer or Null in case the server could not be started.
about:
<pre>
ip:String IP address on which to listen for incoming connections (use Null, "127.0.0.1" or "localhost" to listen on all local addresses).
port:Int Port number (should be greater than 1000 and as high as possible, i.e. 50000 or another number that is not used by other protocols or services).
maxConnections:Int Maximum number of allowed connections / players.
maxChannels:Int (Optional) Maximum number of communication channels. Will be limited to 1-255. (Default: 2)
</pre>
End Rem

Function Create:TNetServer(ip:String, port:Int, maxConnections:Int, maxChannels:Int = 2)
Local addr:Byte Ptr = enet_address_create(ENET_HOST_ANY, port)
If addr = Null Then Return Null

If ip <> Null And ip <> "" And ip <> "localhost" And ip <> "127.0.0.1"
enet_address_set_host(addr, ip)
End If

If maxChannels < ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT
maxChannels = ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT
ElseIf maxChannels > ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT
maxChannels = ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT
End If

Local host:Byte Ptr = enet_host_create(addr, maxConnections, maxChannels, 0, 0)
enet_address_destroy(addr)
If host = Null Then Return Null

Local sv:TNetServer = New TNetServer
sv._host = host
Return sv
End Function

Rem
bbdoc: Shuts down the server.
about:
<pre>
shutdownTime:Int (Optional) Maximum time in ms to wait for the remaining messages to be sent before shutting down. (Default: 5000)
</pre>
End Rem

Method Shutdown(shutdownTime:Int = 5000)
If _host = Null Then Return

Local p:TNetPeer = Null

For p = EachIn _peers
enet_peer_disconnect(p.GetPeer())
Next

Local ev:ENetEvent = New ENetEvent

Local begin:Int = MilliSecs()

If shutdownTime > 0 Then
While enet_host_service(_host, ev, shutdownTime) > 0
Select ev.event
Case TNetServerEvent.CONNECT
enet_peer_reset(ev.peer)

Case TNetServerEvent.DATA
enet_packet_destroy(ev.packet)

Case TNetServerEvent.DISCONNECT
p = FindPeer(ev.peer)
If p <> Null Then _peers.Remove(p)

End Select

If _peers.IsEmpty() Then Exit
If MilliSecs() - begin >= shutdownTime Then Exit
Wend
EndIf

If Not _peers.IsEmpty() Then
For p = EachIn _peers
enet_peer_reset(p)
Next
_peers.Clear()
EndIf

enet_host_destroy(_host)
_host = Null
End Method

Rem
bbdoc: Updates the server (receives and sends messages).
returns: An object of Type TNetServerEvent tha represents the last event or null if there was no event.
about:
<pre>
waitTime:Int (Optional) Time in ms to wait for incoming messages. (Default = 0, this means the function will return immediatley if there are no messages to receive).
</pre>
End Rem

Method Update:TNetServerEvent(waitTime:Int = 0)
If _host = Null Then Return Null

Local p:TNetPeer = Null
Local ev:ENetEvent = New ENetEvent

If enet_host_service(_host, ev, waitTime) > 0
Select ev.event
'New client
Case TNetServerEvent.CONNECT
p = TNetPeer.Create(ev.peer)
_peers.AddLast(p)

Return TNetServerEvent.ServerEvent(ev.event, Null, p, ev.channelID)


'Data from existing client
Case TNetServerEvent.DATA
p = FindPeer(ev.peer)
If p = Null Then Return Null

Return TNetServerEvent.ServerEvent(ev.event, TNetPacket.FromEnet(ev.packet), p, ev.channelID)


'Client disconnected
Case TNetServerEvent.DISCONNECT
p = FindPeer(ev.peer)
If p = Null Then Return Null
_peers.Remove(p)

Return TNetServerEvent.ServerEvent(ev.event, Null, p, ev.channelID)
End Select
Else
Return Null
End If
End Method


'Finds a client/peer by ENet peer struct
Method FindPeer:TNetPeer(peer:Byte Ptr)
For Local p:TNetPeer = EachIn _peers
If p.GetPeer() = peer Then Return p
Next
Return Null
End Method


Rem
bbdoc: Sends a packet to the specified peer / player.
about:
<pre>
peer:TNetPeer The peer / player that will receive the packet.
packet:TNetPacket The packet that will be sent.
reliable:Int (Optional) Whether the packet should be sent reliable or not (Default: False, this is suitable for unimportant anf fast data like player positions)
channel:Int (Optional) The channel used for transmitting the packet (Default: 0, reliable/important messages should be sent on a different channel than unimportant messages, for example channel 1).
compressed:Int (Optional) Whether the data will be compressed or not (Default: False, should only be used with large data, i.e. when sending 100x100 tiles etc.)
</pre>
End Rem

Method Send(peer:TNetPeer, packet:TNetPacket, reliable:Int = False, channel:Int = 0, compressed:Int = False)
If _host = Null Or peer = Null Or packet = Null Then Return
If peer.GetPeer() = Null Then Return

Local flags:Int = 0
If reliable Then flags = ENET_PACKET_FLAG_RELIABLE

Local p:Byte Ptr = packet.EnetPacket(flags, compressed)
If p = Null Then Return

enet_peer_send(peer.GetPeer(), channel, p)
End Method


Rem
bbdoc: Sends a packet to all connected peers / players.
about:
<pre>
packet:TNetPacket The packet that will be sent.
reliable:Int (Optional) Whether the packet should be sent reliable or not (Default: False, this is suitable for unimportant anf fast data like player positions)
channel:Int (Optional) The channel used for transmitting the packet (Default: 0, reliable/important messages should be sent on a different channel than unimportant messages, for example channel 1).
compressed:Int (Optional) Whether the data will be compressed or not (Default: False, should only be used with large data, i.e. when sending 100x100 tiles etc.)
</pre>
End Rem

Method Broadcast(packet:TNetPacket, reliable:Int = False, channel:Int = 0, compressed:Int = False)
If _host = Null Or packet = Null Then Return

Local flags:Int = 0
If reliable Then flags = ENET_PACKET_FLAG_RELIABLE

Local p:Byte Ptr = packet.EnetPacket(flags, compressed)
If p = Null Then Return

enet_host_broadcast(_host, channel, p)
End Method

Rem
bbdoc: Gets a list containing all the connected peers / players.
about:
You should not modify the list in any way! Use the DisconnectClient or DropClient functions instead.
End Rem

Method GetClients:TList()
Return _peers
End Method

Rem
bbdoc: Sends all buffered messages as soon as possible or immediately.
about:
It__COMMENT5__
End Rem

Method Flush()
If _host = Null Then Return
enet_host_flush(_host)
End Method

Rem
bbdoc: Sends a disconnect message to the peer / player and closes the connection after the message has been acknowledged.
End Rem

Method DisconnectClient(peer:TNetPeer)
If peer = Null Or peer.GetPeer() = Null Then Return
enet_peer_disconnect(peer.GetPeer())
End Method

Rem
bbdoc: Closes the connection to a peer immediately. The peer won__COMMENT6__
End Rem

Method DropClient(peer:TNetPeer)
If peer = Null Or peer.GetPeer() = Null Then Return
enet_peer_reset(peer.GetPeer())
_peers.Remove(peer)
End Method
End Type


Beispiele:
ExampleServer.bmx
BlitzMax: [AUSKLAPPEN]
SuperStrict

'Modul importieren
Import mzehr.net

AppTitle = "Server"
SeedRnd(MilliSecs())


'Kleine Klasse um Spieler-Infos zu speichern
Type TPlayer
'Name des Spielers
Field Name:String

'Tasten-Stati des Spielers
Field Keys:Byte[]

'Position des Spielers
Field X:Float
Field Y:Float

Method New()
Keys = New Byte[256]
End Method

'Legt den Status einer Taste des Spielers fest (gedrückt/nicht gedrückt)
Method SetKey(code:Int, pressed:Int)
If code >= 0 And code < Keys.Length Then
Keys[code] = pressed
End If
End Method

'Fragt den Status einer Taste ab (gedrückt/nicht gedrückt)
Method GetKey:Int(code:Int)
If code >= 0 And code < Keys.Length Then Return Keys[code]
Return False
End Method
End Type



'Einen Server auf Port 50000 mit maximal 10 Clients/Spielern erstellen
Global server:TNetServer = TNetServer.Create(Null, 50000, 10)

'Fehlermeldung anzeigen wenn der Server nicht initialisiert werden konnte
If server = Null Then
Notify("Der Server konnte nicht initialisiert werden.")
End
End If

'Grafik initialisieren (bei einem Server eigentlich nicht notwendig)
Graphics 200, 200, 0

'Variablen für Positions-Updates an alle Spieler
Local lastPosUpdate:Int = 0
Local numPosUpdates:Int = 10

'Variablen für die Zeitmessung eines Frames (Frame-Unabhängige Programmierung)
Local deltaTime:Float = 0
Local lastFrame:Int = MilliSecs()

'Hauptschleife des Server-Programms
Repeat
deltaTime = (MilliSecs() - lastFrame) / 1000.0
lastFrame = MilliSecs()
Cls

'Server aktualisieren (Nachrichten senden/empfangen)
Local event:TNetServerEvent = server.Update()

'Solange Events vorhanden sind, diese abarbeiten und Update erneut ausführen
'bis kein Event mehr zurückgegeben wird
While event <> Null

Select event.event
'Ein Client/Spieler hat sich mit dem Server verbunden
Case TNetServerEvent.CONNECT
'Spieler-Objekt erstellen
Local player:TPlayer = New TPlayer
player.X = Rand(20, 780)
player.Y = Rand(20, 580)

'Objekt dem Client zuweisen
event.Peer.SetData(player)

'Bereits verbundene Spieler müssen noch über den neuen Spieler informiert werden
'Dies wird allerdings erst gemacht wenn der Name des Spielers bekannt ist
'(siehe DatenVerarbeitung_NichtAngemeldet)


'Ein Client/Spieler hat den Server verlassen
Case TNetServerEvent.DISCONNECT
'Allen verbundenen Clients/Spielern mitteilen dass dieser Spieler
'nicht mehr verbunden ist
SendeTrennungsNachricht(event)

'Zuvor erstelltes Spieler-Objekt entfernen
event.Peer.SetData(Null)


'Ein Client/Spieler hat Daten gesendet
Case TNetServerEvent.DATA
'Mit dieser Zeile wird das Spieler-Objekt geholt, das zuvor zugewiesen wurde (bei CONNECT)
Local player:TPlayer = TPlayer(event.Peer.GetData())

'Daten zwecks Übersichtlichkeit in einer separaten Funktion verarbeiten
If player.Name = Null Then
'Wenn der Spieler noch keinen Namen hat, dannt wird nur die Nachricht mit der Nr. 1 akzeptiert
DatenVerarbeitung_NichtAngemeldet(event)
Else
'Wenn der Spieler anschliessend einen Namen gesendet hat (mit der Nachricht Nr. 1),
'werden vom Server auch Nachrichten mit anderen Nummern verarbeitet
DatenVerarbeitung_Angemeldet(event)
End If

End Select

'Update erneut ausführen
event = server.Update()
Wend


'Welt/Spieler aktualisieren
For Local peer:TNetPeer = EachIn server.GetClients()
Local player:TPlayer = TPlayer(peer.GetData())

'Spieler aufgrund seiner Tastendrücke 200 Pixel/Sekunde bewegen lassen
player.X:+player.GetKey(KEY_LEFT) * -200 * deltaTime + player.GetKey(KEY_RIGHT) * 200 * deltaTime;
player.Y:+player.GetKey(KEY_UP) * -200 * deltaTime + player.GetKey(KEY_DOWN) * 200 * deltaTime;

'Position auf der X-Achse limitieren
If player.X < 0
player.X = 0
ElseIf player.X > 800
player.X = 800
EndIf

'Position auf der Y-Achse limitieren
If player.Y < 0
player.Y = 0
ElseIf player.Y > 600
player.Y = 600
End If
Next


'15 mal pro Sekunde die Position jedes Spielers an jeden Client senden
If MilliSecs() - lastPosUpdate > 1000 / numPosUpdates

'Paket erstellen mit Infos zu jedem Spieler
Local paket:TNetPacket = TNetPacket.Create()
paket.WriteByte(5) 'Nachricht-Nr. 5
paket.WriteInt(server.GetClients().Count()) 'Anzahl Spieler

'Spieler-Infos in das Paket schreiben
For Local peer:TNetPeer = EachIn server.GetClients()
Local player:TPlayer = TPlayer(peer.GetData())
paket.WriteString(player.Name)
paket.WriteFloat(player.X)
paket.WriteFloat(player.Y)
Next

'Paket an alle Spieler senden
'Diesmal als unwichtiges Paket auf Kanal 0, da die Positionen sehr häufig gesendet werden
'macht es in der Regel nichts wenn mal 1 Paket verloren geht
server.Broadcast(paket)

'Letztes Update = gerade eben
lastPosUpdate = MilliSecs()
EndIf


Flip -1
Until AppTerminate() Or KeyHit(KEY_ESCAPE)

'Grafik-Modus beenden
EndGraphics

'Server herunterfahren
server.Shutdown()

'Programm-Ende
End


'Verarbeitet von Clients empfangene Daten (wenn diese noch nicht Angemeldet sind)
'event:TNetServerEvent Event-Objekt mit dem Absender des Pakets und dem Paket selber
Function DatenVerarbeitung_NichtAngemeldet(event:TNetServerEvent)

'Mit dieser Zeile wird das zuvor zugewiesene Spieler-Objekt vom Client geholt
'Über dieses Objekt kann z.B. auf den Namen des Spielers zugegriffen werden
Local player:TPlayer = TPlayer(event.Peer.GetData())

'Ein Byte vom Paket lesen
'In diesem Beispiel fängt jede Nachricht mit diesem Byte an
'Anhand dieses Bytes wird unterschieden welche Daten vom Paket gelesen werden
Local msgId:Byte = event.Packet.ReadByte()

'Je nach dem Wert des Bytes unterschiedliche Daten lesen/senden etc.
Select msgId

'1 = Name (Der Spieler sendet seinen gewünschten Namen)
Case 1
'Name vom Paket lesen
Local name:String = event.Packet.ReadString()

'Name überprüfen
If name = Null Or name.Length < 3 Then
'Spieler-Name ist zu KURZ -> Nachricht zurück senden
Local paket:TNetPacket = TNetPacket.Create()
paket.WriteByte(1) 'Nachricht-Nr. 1
paket.WriteByte(0) 'Anmeldung fehlgeschlagen
paket.WriteString("Dieser Name ist zu kurz.") 'Fehlermeldung

'Das Antwort-Paket zurücksenden, als wichtige Nachricht und auf dem Kanal 1 (nicht komprimiert)
server.Send(event.Peer, paket, True, 1)

ElseIf FindPlayer(name) <> Null
'Es existiert bereits ein Spieler mit diesem Namen
Local paket:TNetPacket = TNetPacket.Create()
paket.WriteByte(1) 'Nachricht-Nr. 1
paket.WriteByte(0) 'Anmeldung fehlgeschlagen
paket.WriteString("Dieser Name wird bereits verwendet.") 'Fehlermeldung

'Das Antwort-Paket zurücksenden, als wichtige Nachricht und auf dem Kanal 1 (nicht komprimiert)
server.Send(event.Peer, paket, True, 1)

Else
'Spieler-Name ist OK -> Name setzen
'(Dadurch gilt der Spieler als angemeldet und darf nun auch andere Nachrichten senden)
player.name = name

'Antwort senden
Local paket:TNetPacket = TNetPacket.Create()
paket.WriteByte(1) 'Nachricht-Nr. 1
paket.WriteByte(1) 'Anmeldung erfolgreich (keine Fehlermeldung notwendig)

'Infos über alle anderen Spieler in das Paket schreiben
paket.WriteInt(server.GetClients().Count() - 1)
For Local peer:TNetPeer = EachIn server.GetClients()
'Eigenen Spieler ignorieren
If peer = event.peer Then Continue

Local p:TPlayer = TPlayer(peer.GetData())
paket.WriteString(p.name)
paket.WriteFloat(p.X)
paket.WriteFloat(p.Y)
Next

'Das Antwort-Paket zurücksenden, als wichtige Nachricht und auf dem Kanal 1 (nicht komprimiert)
server.Send(event.Peer, paket, True, 1)

'Bereits verbundene Spieler erst jetzt über den neuen Spieler informieren (da nun erst der Name bekannt ist)
SendeVerbindungsNachricht(event)
EndIf

End Select

End Function


'Sucht nach einem TPlayer mit dem angegebenen Namen
'name:String Name des Spielers
'Gibt Null oder den Spieler zurück, wenn er gefunden wurde
Function FindPlayer:TPlayer(name:String)

'Gross-/Kleinschreibung des Namens spielt keine Rolle
name = name.ToLower()

For Local peer:TNetPeer = EachIn server.GetClients()
'TPlayer-Objekt vom Client holen
Local player:TPlayer = TPlayer(peer.GetData())

'Prüfen ob der Name übereinstimmt und falls ja, den Spieler zurückgeben
If player <> Null And player.name.ToLower() = name Then Return player
Next

'Kein Spieler gefunden
Return Null
End Function


'Verarbeitet von Clients empfangene Daten (wenn diese Angemeldet sind)
'event:TNetServerEvent Event-Objekt mit dem Absender des Pakets und dem Paket selber
Function DatenVerarbeitung_Angemeldet(event:TNetServerEvent)

'Mit dieser Zeile wird das zuvor zugewiesene Spieler-Objekt vom Client geholt
'Über dieses Objekt kann z.B. auf den Namen des Spielers zugegriffen werden
Local player:TPlayer = TPlayer(event.Peer.GetData())

'Ein Byte vom Paket lesen
'In diesem Beispiel fängt jede Nachricht mit diesem Byte an
'Anhand dieses Bytes wird unterschieden welche Daten vom Paket gelesen werden
Local msgId:Byte = event.Packet.ReadByte()

'Je nach dem Wert des Bytes unterschiedliche Daten lesen/senden etc.
Select msgId

'4 = Tasten-Status des Clients
Case 4
'Tasten-Code lesen
Local code:Byte = event.Packet.ReadByte()

'Status lesen (gedrückt/nicht gedrückt)
Local pressed:Byte = event.Packet.ReadByte()

'Taste beim Spieler-Objekt aktualisieren
player.SetKey(code, pressed)

End Select

End Function


'Sendet eine Verbindungsnachricht an alle anderen Spieler
'event:TNetServerEvent Event-Objekt mit dem Client der sich verbunden hat
Function SendeVerbindungsNachricht(event:TNetServerEvent)

'Diese Zeile holt das Spieler-Objekt
Local player:TPlayer = TPlayer(event.Peer.GetData())

'Paket erstellen, dass an alle anderen Spieler gesendet wird
Local paket:TNetPacket = TNetPacket.Create()
paket.WriteByte(2) 'Nachricht-Nr. 2
paket.WriteString(player.Name) 'Name des Spielers
paket.WriteFloat(player.X) 'X-Position des Spielers
paket.WriteFloat(player.Y) 'Y-Position des Spielers

For Local client:TNetPeer = EachIn server.GetClients()
'Nicht an den Client senden der sich verbunden hat
If client = event.Peer Then Continue

'Aber an alle anderen Senden
server.Send(client, paket, True, 1)
Next

End Function

'Sendet eine Trennungsnachricht an alle anderen Spieler
'event:TNetServerEvent Event-Objekt mit dem Client der die Verbindung getrennt hat
Function SendeTrennungsNachricht(event:TNetServerEvent)

'Diese Zeile holt das Spieler-Objekt
Local player:TPlayer = TPlayer(event.Peer.GetData())

'Paket erstellen, dass an alle anderen Spieler gesendet wird
Local paket:TNetPacket = TNetPacket.Create()
paket.WriteByte(3) 'Nachricht-Nr. 3
paket.WriteString(player.Name) 'Name des Spielers

For Local client:TNetPeer = EachIn server.GetClients()
'Nicht an den Client senden der die Verbindung getrennt hat
If client = event.Peer Then Continue

'Aber an alle anderen Senden
server.Send(client, paket, True, 1)
Next

End Function


ExampleClient.bmx
BlitzMax: [AUSKLAPPEN]
SuperStrict

'Modul importieren
Import mzehr.net

AppTitle = "Client"
SeedRnd(MilliSecs())

'Kleine Klasse für den eigenen und die anderen Spieler
Type TPlayer
'Liste mit allen Spielern
Global List:TList = New TList

'Name des Spielers
Field Name:String

'Position des Spielers
Field X:Float
Field Y:Float

Field ZielX:Float
Field ZielY:Float

Field Initialisiert:Int

Method New()
'Neue Spieler in die Liste einfügen
List.AddLast(Self)
End Method

Method Update(deltaTime:Float)
X = Interpoliere(X, ZielX, 2.0 * deltaTime)
Y = Interpoliere(Y, ZielY, 2.0 * deltaTime)
End Method

'Zeichnet den Spieler
Method Draw()
Local px:Int = Int(X)
Local py:Int = Int(Y)

DrawText(Name, px - TextWidth(Name) / 2, py - 40 - TextHeight(Name))
DrawOval(px - 10, py - 40, 20, 20)
DrawLine(px, py - 30, px, py + 10)
DrawLine(px, py - 10, px - 20, py - 30)
DrawLine(px, py - 10, px + 20, py - 30)
DrawLine(px, py + 10, px - 20, py + 30)
DrawLine(px, py + 10, px + 20, py + 30)
End Method
End Type


'Einen neuen Client erstellen
Global client:TNetClient = TNetClient.Create()

'Den lokalen Spieler erstellen
Local myPlayer:TPlayer = New TPlayer
myPlayer.Name = "Guest" + Rand(1000, 9999)

'Verbindung herstellen
If client.Connect("localhost", 50000)
'Login-Paket erstellen
Local paket:TNetPacket = TNetPacket.Create()
paket.WriteByte(1) 'Nachricht-Nr. 1
paket.WriteString(myPlayer.Name) 'Name des eigenen Spielers

'Paket an den Server senden (wichtige Nachricht auf Kanal 1)
client.Send(paket, True, 1)

'Maximal 2 Sekunden auf die Antwort des Servers warten
Local event:TNetClientEvent = client.Update(2000)
If event <> Null And event.event = TNetClientEvent.DATA
'Daten empfangen

Local msgId:Byte = event.Packet.ReadByte()
If msgId = 1
'Antwort auslesen
Local loggedIn:Int = event.Packet.ReadByte()
If loggedIn
'Bestehende Spieler einlesen
Local numPlayers:Int = event.Packet.ReadInt()
For Local i:Int = 0 To numPlayers - 1
'Spielerdaten lesen
Local name:String = event.Packet.ReadString()
Local x:Float = event.Packet.ReadFloat()
Local y:Float = event.Packet.ReadFloat()

'Spieler erstellen
Local player:TPlayer = New TPlayer
player.name = name
player.X = x
player.Y = y
player.ZielX = x
player.ZielY = y
Next
Else
'Login fehlgeschlagen
Local error:String = event.Packet.ReadString()
client.Disconnect()
Notify(error)
End
End If
Else
'Ungültige Antwort
client.Disconnect()
Notify("Der Server hat eine unerwartete Antwort gesendet. Protokoll-Fehler?")
End
End If
Else
'Keine Antwort
client.Disconnect()
Notify("Der Server hat innerhalb von 2 Sekunden keine Antwort gesendet. Verbindung fehlgeschlagen.")
End
End If
Else
'Connect schlug fehl
Notify "Es konnte keine Verbindung hergestellt werden."
End
End If


'An dieser Stelle ist die Verbindung hergestellt und der Spieler eingeloggt

'Grafik initialisieren
Graphics 800, 600, 0

'Variable zum Zwischenspeichern von Tasten-Stati
Local keyState:Byte[] = New Byte[256]

'Variable zum Beenden des Programms aus der Hauptschleife heraus
Local quitApp:String = Null

'Variablen für die Zeitmessung eines Frames (Frame-Unabhängige Programmierung)
Local deltaTime:Float = 0
Local lastFrame:Int = MilliSecs()

Repeat
deltaTime = (MilliSecs() - lastFrame) / 1000.0
lastFrame = MilliSecs()
Cls

'Status-Änderungen von Tasten an den Server senden
UpdateKey(KEY_UP, keyState[KEY_UP])
UpdateKey(KEY_DOWN, keyState[KEY_DOWN])
UpdateKey(KEY_LEFT, keyState[KEY_LEFT])
UpdateKey(KEY_RIGHT, keyState[KEY_RIGHT])

'Client aktualisieren (empfangen/senden)
Local event:TNetClientEvent = client.Update()
While event <> Null
Select event.event
'Der Server hat die Verbindung getrennt
Case TNetClientEvent.DISCONNECT
quitApp = "Der Server hat die Verbindung getrennt."

'Daten vom Server empfangen
Case TNetClientEvent.DATA
'Daten verarbeiten
DatenVerarbeitung(event)

End Select

'Nächstes Ereignis abrufen
event = client.Update()
Wend


'Alle Spieler aktualisieren und zeichnen
For Local player:TPlayer = EachIn TPlayer.List
player.Update(deltaTime)
player.Draw()
Next

Flip -1
Until AppTerminate() Or KeyHit(KEY_ESCAPE) Or quitApp <> Null

EndGraphics

'Client schliessen
client.Disconnect()

'Grund für das Beenden des Programms anzeigen (falls gegeben)
If quitApp <> Null Then Notify(quitApp)

End


'Findet einen Spieler per Name
Function FindPlayer:TPlayer(name:String)
'Gross-/Kleinschreibung spielt keine Rolle
name = name.ToLower()

For Local player:TPlayer = EachIn TPlayer.List
'Namen überprüfen, falls sie identisch sind den Spieler zurückgeben
If player.name.ToLower() = name Then Return player
Next

'Kein Spieler gefunden
Return Null
End Function


'Sendet Status-Änderungen einer bestimmten Taste (falls notwendig)
Function UpdateKey(code:Int, lastState:Byte Var)
'Update erforderlich?
Local update:Int = False

If KeyDown(code) And Not lastState Then
lastState = True
update = True 'Update veranlassen
Else If lastState And Not KeyDown(code) Then
lastState = False
update = True 'Update veranlassen
End If

'Update falls erforderlich
If update Then
'Paket erstellen
Local paket:TNetPacket = TNetPacket.Create()
paket.WriteByte(4) 'Nachricht-Nr. 4
paket.WriteByte(code) 'Taste
paket.WriteByte(lastState) 'Status

'Paket senden
client.Send(paket, True, 1)
End If
End Function


'Verarbeitet die vom Server empfangenen Daten
'event:TNetClientEvent Event-Objekt welches den Absender der Nachricht und das Paket selbst enthält
Function DatenVerarbeitung(event:TNetClientEvent)

'Ein Byte vom Paket lesen
'In diesem Beispiel fängt jede Nachricht mit diesem Byte an
'Anhand dieses Bytes wird unterschieden welche Daten vom Paket gelesen werden
Local msgId:Byte = event.Packet.ReadByte()

'Je nach dem Wert des Bytes unterschiedliche Daten lesen/senden etc.
Select msgId
'2 = Neuer Mitspieler
Case 2
'Daten lesen
Local name:String = event.Packet.ReadString()
Local x:Float = event.Packet.ReadFloat()
Local y:Float = event.Packet.ReadFloat()

'Spieler erstellen
Local player:TPlayer = New TPlayer
player.Name = name
player.X = x
player.Y = y
player.ZielX = x
player.ZielY = y

'3 = Spieler verlässt das Spiel
Case 3
'Daten lesen
Local name:String = event.Packet.ReadString()

'Spieler entfernen
Local player:TPlayer = FindPlayer(name)
If player <> Null Then TPlayer.List.Remove(player)

'5 = Positions-Updates
Case 5
'Anzahl Spieler im Update lesen
Local numPlayers:Int = event.Packet.ReadInt()

For Local i:Int = 0 To numPlayers - 1
'Daten von einem Spieler lesen
Local name:String = event.Packet.ReadString()
Local x:Float = event.Packet.ReadFloat()
Local y:Float = event.Packet.ReadFloat()

'Spieler suchen und Position aktualisieren
Local player:TPlayer = FindPlayer(name)
If player <> Null Then
If Not player.Initialisiert
player.X = x
player.Y = y
player.Initialisiert = True
End If
player.ZielX = x
player.ZielY = y
End If
Next
End Select
End Function

Function Interpoliere:Float(aktuell:Float, neu:Float, faktor:Float)
faktor = Clamp01(faktor)
Return ((1.0 - faktor) * aktuell) + (faktor * neu)
End Function

Function Clamp01:Float(wert:Float)
If wert < 0.0 Then Return 0.0
If wert > 1.0 Then Return 1.0
Return wert
End Function


Ausserdem wird natürlich der Quellcode von ENet benötigt, zu finden unter https://github.com/lsalzman/enet.

Arrow Die .c-Dateien müssen in den Modul-Unterordner "enet" kopiert werden.
Arrow Die .h-Dateien müssen in den Modul-Unterordner "enet/enet" kopiert werden. (NICHT enet/include/enet)
Arrow Die .h-Dateien müssen nachträglich editiert werden: Bei allen #include "enet/datei.h" muss der enet-Ordner aus dem Pfad entfernt werden, sprich #include "datei.h"
 

funkheld

BeitragDi, März 20, 2018 10:11
Antworten mit Zitat
Benutzer-Profile anzeigen
Gut gemacht.

Gruss

Thunder

Betreff: Re: mzehr.net - Netzwerkmodul basierend auf enet

BeitragDi, März 20, 2018 11:33
Antworten mit Zitat
Benutzer-Profile anzeigen
Hi,

kannte dein Modul gar nicht, bin also auch kein Benutzer.
+1 für MIT-Lizenz! (bnetex ist ja LGPL Sad )
Werde ich mir sicher anschauen.

Ist dein Modul ein Wrapper um ENet oder machst du higher-level Sachen auch?
Meine Sachen: https://bitbucket.org/chtisgit https://github.com/chtisgit

Jolinah

BeitragDi, März 20, 2018 17:16
Antworten mit Zitat
Benutzer-Profile anzeigen
Hallo,

Danke, hauptsächlich ist es nur ein OOP-Wrapper. Es gibt zusätzlich noch die Möglichkeit Pakete mit pub.zlib zu komprimieren (das Entstand in der Version 1, als auch noch pub.enet verwendet wurde). Ich habe jedoch gesehen, dass ENet selber auch eine Kompression der Pakete erlaubt. Daher werde ich mir das noch genauer anschauen und evtl. das Modul anpassen, so dass direkt die Komprimierung von ENet verwendet wird Wink

Falls ich genug Zeit und Motivation finde, erstelle ich vielleicht noch ein High-Level-Modul dass auf mzehr.net aufbaut. Die Idee wäre, dass Benutzer nur noch Objekte erstellen müssten, welche ähnlich wie bei Unity Komponenten angehängt bekommen. Dann gibt es vordefinierte Netzwerk-Komponenten die z.B. die Position automatisch synchronisieren inkl. simpler Interpolation oder so. Ausserdem könnten eigene Netzwerk-Komponenten mittels Vererbung und Write/Read- bzw. Serialize/Deserialize-Methoden erstellt werden.

Neue Antwort erstellen


Übersicht BlitzMax, BlitzMax NG Codearchiv & Module

Gehe zu:

Powered by phpBB © 2001 - 2006, phpBB Group