Netzwerksynchronisation zwischen Clients mittels Server
Netzwerksynchronisation zwischen Clients mittels Server
Hallo liebe PBler.
ich habe eine Frage zu Netzwerkperformance und -synchronisation.
Folgendes Szenario (hier in einem Netzwerkspiel):
Jeder Client arbeitet grundsätzlich selbstständig. Alles was der User dort macht/verändert wird an den Server übertragen mittels string (64 Byte). Der Server verteilt diese Information dann an alle Clients.
Hierbei kommt es allerdings vor, dass Informationen vom Server beim Client verloren gehen - und zwar dann - wenn er offensichtlich gerade mit etwas anderem beschäftigt ist. Da der Client wie üblich alles (also ohne Threads) abarbeitet ist dieser natürlich mit vielem anderen beschäftigt und kann nicht immer auf den Server hören.
Gibt es hierfür bessere Lösungsansätze? Wie synchronisiert ihr Clients untereinander?
Da das Problem seitens des Servers auch schon aufgetreten war (Server durchläuft Clientliste und sendet an jeden während ein anderer versucht dem Server was zu senden wobei diese Information dann unter geht) bin ich hier nur über einen Trick weiter gekommen: Der Server fügt jede Information in eine List die er dann einzeln (wenn keiner was sendet) abarbeitet. Das führt aber zu Verzögerungen.
Insgesamt muss es doch besserer Lösungen geben.
ich habe eine Frage zu Netzwerkperformance und -synchronisation.
Folgendes Szenario (hier in einem Netzwerkspiel):
Jeder Client arbeitet grundsätzlich selbstständig. Alles was der User dort macht/verändert wird an den Server übertragen mittels string (64 Byte). Der Server verteilt diese Information dann an alle Clients.
Hierbei kommt es allerdings vor, dass Informationen vom Server beim Client verloren gehen - und zwar dann - wenn er offensichtlich gerade mit etwas anderem beschäftigt ist. Da der Client wie üblich alles (also ohne Threads) abarbeitet ist dieser natürlich mit vielem anderen beschäftigt und kann nicht immer auf den Server hören.
Gibt es hierfür bessere Lösungsansätze? Wie synchronisiert ihr Clients untereinander?
Da das Problem seitens des Servers auch schon aufgetreten war (Server durchläuft Clientliste und sendet an jeden während ein anderer versucht dem Server was zu senden wobei diese Information dann unter geht) bin ich hier nur über einen Trick weiter gekommen: Der Server fügt jede Information in eine List die er dann einzeln (wenn keiner was sendet) abarbeitet. Das führt aber zu Verzögerungen.
Insgesamt muss es doch besserer Lösungen geben.
Agent_Sasori
It's not a bug - it's a feature!
http://www.StephenKalisch.de | http://www.ria-tec.com | http://www.dirsync.de
It's not a bug - it's a feature!
http://www.StephenKalisch.de | http://www.ria-tec.com | http://www.dirsync.de
Re: Netzwerksynchronisation zwischen Clients mittels Server
Hi.
Bisher keine Posts zu meinem Problem. Hat keiner Ideen? Ist das Problem klar oder soll ich versuchen es zu präzisieren?
Es handelt sich dabei um ein Aufbau-Strategie-Spiel im Old-School.
Hier einfach nochmal der Aufbau:
Das Spiel (der Client) arbeitet eigenständig. Alle Berechnungen finden also clientseitig statt. NIcht nur die Grafik etc. Das heißt als Beispiel:
Spieler A baut eine Einheit, Procedure ADDUNIT() wird aufgerufen. Ebenso wenn er ein Gebäude baut ADDSTRUCTURE() oder ein Schuss abgibt ADDPROJECTILE() oder forscht ADDRESEARCH(). Keine Routine hat eine längere Schleife, alles wird mit LinkLists verwaltet (Units(), Structures(), Projectiles(), Researches(), Windows(), Explosions() etc).
Jede dieser Veränderungen (bauen, bewegen) wird per SendNetworkString (reduziert auf 64 bytes aus Performance-Gründen) an den Server geschickt. Dieser sendet den "Befehl" einfach an alle Clients weiter wieder per Sendnetworkstring (64 Bytes).
Jeder Client empfängt dies und ruft schlicht und ergreifend die entsprechende Procedure (ADDUNIT() etc) auf. Ich weiß das dies Zeitkritisch ist, da - falls es zu Verzögerungen kommt - zu Spieldifferenzen führen kann. Da die Berechnungen von Rohstoffen etc ja auf Clientseite erfolgen. Ich habe vor zeitweise eine Synchronisation laufen zu lassen, die Differenzen löst. Aber dazu muss das Spiel erstmal "synchron" laufen bis dato.
Ich hatte zuvor einen Lösungsansatz, der nach jeden Senden von Client nach Server der Client UNBEDINGT auf ein OK wartet. Der Server hat dann an alle Clients gesendet und ebenfalls UNBEDINGT auf OK gewartet damit er am Ende (nachallen OKs) dem eigentlichen Sender (der erste Client) das OK schicken kann damit dieser weiter machen kann. Das sichert natürlich die Zustellung der Information, führte aber zu extremen Lags da ja alles stehen bleibt solange. Zumal das Problem auftrat, wenn der SERVER auf OK wartet und ein anderer Client gerade einen Befehl schickt hängt sich alles auf, weil der SERVER ja auf OK wartet und ein ADDUNIT bekommt oder so.
Es ist alles ohne THREADS bisher gelöst, sodass die Hauptroutine des Spiels in etwa so aussieht:
Der Server macht nichts anderes als die Spieler in einer Liste verwalten und fast alles durchreichen. Lediglich ein paar wenige Befehle werden verarbeitet wie das Starten des Spiels oder das erzeugen des (zufälligen) Spielfeldes was an den Client übertragen wird.
Meine Kerfrage also:
Wie löst man diesen Datenaustausch so, dass es a) keine Lags gibt und b) kein Datenverlust
Hat da jemand Erfahrung?
Bisher keine Posts zu meinem Problem. Hat keiner Ideen? Ist das Problem klar oder soll ich versuchen es zu präzisieren?
Es handelt sich dabei um ein Aufbau-Strategie-Spiel im Old-School.
Hier einfach nochmal der Aufbau:
Das Spiel (der Client) arbeitet eigenständig. Alle Berechnungen finden also clientseitig statt. NIcht nur die Grafik etc. Das heißt als Beispiel:
Spieler A baut eine Einheit, Procedure ADDUNIT() wird aufgerufen. Ebenso wenn er ein Gebäude baut ADDSTRUCTURE() oder ein Schuss abgibt ADDPROJECTILE() oder forscht ADDRESEARCH(). Keine Routine hat eine längere Schleife, alles wird mit LinkLists verwaltet (Units(), Structures(), Projectiles(), Researches(), Windows(), Explosions() etc).
Jede dieser Veränderungen (bauen, bewegen) wird per SendNetworkString (reduziert auf 64 bytes aus Performance-Gründen) an den Server geschickt. Dieser sendet den "Befehl" einfach an alle Clients weiter wieder per Sendnetworkstring (64 Bytes).
Jeder Client empfängt dies und ruft schlicht und ergreifend die entsprechende Procedure (ADDUNIT() etc) auf. Ich weiß das dies Zeitkritisch ist, da - falls es zu Verzögerungen kommt - zu Spieldifferenzen führen kann. Da die Berechnungen von Rohstoffen etc ja auf Clientseite erfolgen. Ich habe vor zeitweise eine Synchronisation laufen zu lassen, die Differenzen löst. Aber dazu muss das Spiel erstmal "synchron" laufen bis dato.
Ich hatte zuvor einen Lösungsansatz, der nach jeden Senden von Client nach Server der Client UNBEDINGT auf ein OK wartet. Der Server hat dann an alle Clients gesendet und ebenfalls UNBEDINGT auf OK gewartet damit er am Ende (nachallen OKs) dem eigentlichen Sender (der erste Client) das OK schicken kann damit dieser weiter machen kann. Das sichert natürlich die Zustellung der Information, führte aber zu extremen Lags da ja alles stehen bleibt solange. Zumal das Problem auftrat, wenn der SERVER auf OK wartet und ein anderer Client gerade einen Befehl schickt hängt sich alles auf, weil der SERVER ja auf OK wartet und ein ADDUNIT bekommt oder so.
Es ist alles ohne THREADS bisher gelöst, sodass die Hauptroutine des Spiels in etwa so aussieht:
Code: Alles auswählen
Repeat
CaptureMouseInputs()
CaptureKeyboardInputs()
PaintPlayfield()
GetServerMessages()
...
Until bMainExit = #true
Der Server macht nichts anderes als die Spieler in einer Liste verwalten und fast alles durchreichen. Lediglich ein paar wenige Befehle werden verarbeitet wie das Starten des Spiels oder das erzeugen des (zufälligen) Spielfeldes was an den Client übertragen wird.
Meine Kerfrage also:
Wie löst man diesen Datenaustausch so, dass es a) keine Lags gibt und b) kein Datenverlust
Hat da jemand Erfahrung?
Agent_Sasori
It's not a bug - it's a feature!
http://www.StephenKalisch.de | http://www.ria-tec.com | http://www.dirsync.de
It's not a bug - it's a feature!
http://www.StephenKalisch.de | http://www.ria-tec.com | http://www.dirsync.de
- HeX0R
- Beiträge: 3070
- Registriert: 10.09.2004 09:59
- Computerausstattung: AMD Ryzen 7 5800X
96Gig Ram
NVIDIA GEFORCE RTX 3060TI/8Gig
Win11 64Bit
G19 Tastatur
2x 24" + 1x27" Monitore
Glorious O Wireless Maus
PB 3.x-PB 6.x
Oculus Quest 2 + 3 - Kontaktdaten:
Re: Netzwerksynchronisation zwischen Clients mittels Server
Also ohne Code schwelgen wir mal in der Theorie.
In der Theorie (und Praxis) geht da nichts verloren, egal ob du die Pakete ne Sekunde später abholst oder sofort.
Das einzige was passieren kann, ist dass der Ein- oder Ausgangspuffer voll läuft, dann gehen natürlich Informationen verloren.
Aber bei 64Byte-Paketen kann ich mir das nicht vorstellen, das müsste ja ewig dauern...
Oder benutzt du das UDP-Protokoll?
[Edit]
Ein Tipp vielleicht noch:
Überprüfe beim Senden auf jeden Fall den Rückgabewert!
Sollte der Eingangspuffer des Empfängers voll sein, wirst du eine -1 zurückbekommen.
Blöd nur, dass du auch eine -1 zurückbekommst, wenn der Server gestorben ist.
Aber naja, ich werde es nicht mehr erleben, dass die Netzwerk-Lib von PB mal vernünftig überarbeitet wird...
In der Theorie (und Praxis) geht da nichts verloren, egal ob du die Pakete ne Sekunde später abholst oder sofort.
Das einzige was passieren kann, ist dass der Ein- oder Ausgangspuffer voll läuft, dann gehen natürlich Informationen verloren.
Aber bei 64Byte-Paketen kann ich mir das nicht vorstellen, das müsste ja ewig dauern...
Oder benutzt du das UDP-Protokoll?
[Edit]
Ein Tipp vielleicht noch:
Überprüfe beim Senden auf jeden Fall den Rückgabewert!
Sollte der Eingangspuffer des Empfängers voll sein, wirst du eine -1 zurückbekommen.
Blöd nur, dass du auch eine -1 zurückbekommst, wenn der Server gestorben ist.
Aber naja, ich werde es nicht mehr erleben, dass die Netzwerk-Lib von PB mal vernünftig überarbeitet wird...
{Home}.:|:.{Codes}.:|:.{Downloads}.:|:.{History Viewer Online}.:|:.{Bier spendieren}
Re: Netzwerksynchronisation zwischen Clients mittels Server
Servus!
Tja, was soll man da raten? Die Übertragung von Information läuft nunmal nicht unendlich schnell ab. Mit einigen 100ms mußt Du schon rechnen, bis ein Datum transferiert ist - und das nur deutschlandweit. Wenn Du erwartest, weltweit halbwegs akzeptable Übertragungsgeschwindigkeiten zu erzielen, wirste ganz böse enttäuscht werden, denn etliche Länder haben gemietete Proxys in Drittstaaten dazwischengeschaltet. Ich weiß das, weil die Datenübertragung Deutschland-Paraguay desöfteren in Zeitüberschreitungen läuft. Pings brauchen bis zu 3s!
Am besten ist es wohl, alle klientenseitigen Möglichkeiten auszuschöpfen, die interne Verarbeitung zu beschleunigen. Ich fürchte, da kommste an Nebenläuferprozessen und verbesserter Signalverarbeitung innhalb des Programms nicht vorbei. Beschäftige Dich also am besten gleich mit Threads, Mutex, Semaphoren. So wird ein Schuh draus. Ob die sequentielle Abarbeitung auch der Weisheit letzter Schluß ist, bezweifle ich auch stark. Versiche es mit UDP. Die Wikipedia ist Dein Freund.
Gruß - WPö
Tja, was soll man da raten? Die Übertragung von Information läuft nunmal nicht unendlich schnell ab. Mit einigen 100ms mußt Du schon rechnen, bis ein Datum transferiert ist - und das nur deutschlandweit. Wenn Du erwartest, weltweit halbwegs akzeptable Übertragungsgeschwindigkeiten zu erzielen, wirste ganz böse enttäuscht werden, denn etliche Länder haben gemietete Proxys in Drittstaaten dazwischengeschaltet. Ich weiß das, weil die Datenübertragung Deutschland-Paraguay desöfteren in Zeitüberschreitungen läuft. Pings brauchen bis zu 3s!
Am besten ist es wohl, alle klientenseitigen Möglichkeiten auszuschöpfen, die interne Verarbeitung zu beschleunigen. Ich fürchte, da kommste an Nebenläuferprozessen und verbesserter Signalverarbeitung innhalb des Programms nicht vorbei. Beschäftige Dich also am besten gleich mit Threads, Mutex, Semaphoren. So wird ein Schuh draus. Ob die sequentielle Abarbeitung auch der Weisheit letzter Schluß ist, bezweifle ich auch stark. Versiche es mit UDP. Die Wikipedia ist Dein Freund.
Gruß - WPö
Ich glaube nur der Statistik, die ich selbst gefälscht habe!
Meine Netzpräsenz: WPö.de
PB5.31 auf LMDE und Pentium T7200 2,00GHz, 4GB DDR2, ATI X1400.
Meine Netzpräsenz: WPö.de
PB5.31 auf LMDE und Pentium T7200 2,00GHz, 4GB DDR2, ATI X1400.
Re: Netzwerksynchronisation zwischen Clients mittels Server
Hi.
Ich habe bereits überlegt auf Client-seite einen Thread zu starten, der permanent auf Netzwerk (Server) Infos hört und in eine To-Do-Liste schreibt. Dies dann, wenn das Hauptprogramm an der zu verarbeitenden Stelle ist eben die Liste abarbeitet.
Mutex und Co sind mir bekannt - allerdings entsprechend langsam. Dies wäre meine Momentane Lösung:
Thread der alle Netzwerkdaten einliest. solange ist dort ein Mutex für reserviert. Kommt das Hauptprogramm an die stelle wird der Thread angehalten (per stop oder mutex) und alles abgearbeitet. Danach sozusagen "zurückgeschaltet" und der Thread bekommt den Mutex. Alle To-Dos in einer Linklist (strukturiert mit ClientID, NetString$).
Das ganze basiert übrigens auf dem TCP-Protokoll.
Gerne würde ich etwas Code zur Verfügung stellen, aber ich kann wohl kaum die 5.900 Zeilen hier posten oder?
Aber wie wäre es hiermit:
Die Netzwerk-Procedures so far:
Die Client-Routine zum Auswerten der weitergeleiteten Befehle:
Ein Auszug aus dem Server (hier sind die Ankommenden Daten schon in Liste organisiert):
Ich habe bereits überlegt auf Client-seite einen Thread zu starten, der permanent auf Netzwerk (Server) Infos hört und in eine To-Do-Liste schreibt. Dies dann, wenn das Hauptprogramm an der zu verarbeitenden Stelle ist eben die Liste abarbeitet.
Mutex und Co sind mir bekannt - allerdings entsprechend langsam. Dies wäre meine Momentane Lösung:
Thread der alle Netzwerkdaten einliest. solange ist dort ein Mutex für reserviert. Kommt das Hauptprogramm an die stelle wird der Thread angehalten (per stop oder mutex) und alles abgearbeitet. Danach sozusagen "zurückgeschaltet" und der Thread bekommt den Mutex. Alle To-Dos in einer Linklist (strukturiert mit ClientID, NetString$).
Das ganze basiert übrigens auf dem TCP-Protokoll.
Gerne würde ich etwas Code zur Verfügung stellen, aber ich kann wohl kaum die 5.900 Zeilen hier posten oder?
Aber wie wäre es hiermit:
Die Netzwerk-Procedures so far:
Code: Alles auswählen
Procedure.s SK_ReceiveNetworkString64(connectionID, TimeOut = 250)
Protected lReceivedBytes.l, ReceivedData$
Protected *buffer
; Speicher reserverieren
*buffer = AllocateMemory(64)
If *buffer And ConnectionID
; Empfangsstring leeren
ReceivedData$ = ""
; Daten holen
Repeat
Delay(1)
TimeOut - 1
; auf Daten warten
lReceivedBytes = ReceiveNetworkData(ConnectionID, *buffer, 64)
; Haben wir was? Speichern!
If lReceivedBytes >0
ReceivedData$ + PeekS(*buffer, lReceivedBytes)
EndIf
Until lReceivedBytes > 0 Or TimeOut <= 0; da kommt nichts mehr or Timer abgelaufen
; Speicher wieder freigeben
FreeMemory(*buffer)
EndIf
ProcedureReturn RTrim(ReceivedData$)
EndProcedure
Procedure SK_SendNetworkString64(ConnectionID, Text$)
Protected sendBytes.w, TimeOut = 250, lTimer, counter, dummy$
If ConnectionID
sendBytes = SendNetworkString(ConnectionID, LSet(Text$, 64, Chr(32)))
EndIf
ProcedureReturn sendBytes
EndProcedure
Die Client-Routine zum Auswerten der weitergeleiteten Befehle:
Code: Alles auswählen
Procedure GetServerEvents()
Protected x, y, tox, toy, playerid, ammopower, speed, ammotype, playername$, uniqueID, targetuniqueID, spriteID, dummy, angle, attackeruniqueID
Protected DistanceX, DistanceY, attackerElementID, *targetSpriteHND, AttackerSpriteID, *buffer, *hndPackFile
Protected lFile
; get the event
Select NetworkClientEvent(netServerID)
Case #PB_NetworkEvent_Data
netServerString$ = SK_ReceivenetworkString(netServerID)
; netmessage interpreter
Select StringField(netServerString$, 1, Chr(32))
Case "SET"
Select StringField(netServerString$, 2, Chr(32))
Case "OWNER"
; "SET OWNER " + Str(\uniqueID) + Chr(32) + LocalPlayerName$
targetuniqueID = Val(StringField(netServerString$, 3, Chr(32)))
playername$ = StringField(netServerString$, 4, Chr(32))
; Get player ID
ForEach Players()
If Players()\Name$ = playername$
playerid = ListIndex(Players())
Break
EndIf
Next
; Find the right Unit to change owner
ForEach unit()
If unit()\uniqueID = targetuniqueID
unit()\OwnerID = playerid
AddSystemMessage("An Agent has hacked a " + unit()\Name$)
EndIf
Next
EndSelect
Case "+RH"
; NEWRESEARCH TypeofResearch Playername$
playername$ = StringField(netServerString$, 2, Chr(32))
If playername$ <> LocalPlayerName$
dummy = Val( StringField(netServerString$, 3, Chr(32)))
AddResearch(dummy, playername$)
EndIf
; Server is waiting, sending an OK so he can continue
SK_SendNetworkString64(netServerID, #server_ok)
Case "MOVE" ; OK
; MOVE playername uniqueID fromx fromy tox toy speed
; 1 2 3 4 5 6 7 8
playername$ = StringField(netServerString$, 2, Chr(32))
If playername$ <> LocalPlayerName$
uniqueID = Val(StringField(netServerString$, 3, Chr(32)))
x = Val(StringField(netServerString$, 4, Chr(32)))
y = Val(StringField(netServerString$, 5, Chr(32)))
tox = Val(StringField(netServerString$, 6, Chr(32)))
toy = Val(StringField(netServerString$, 7, Chr(32)))
speed = Val(StringField(netServerString$, 8, Chr(32)))
ForEach Unit()
With Unit()
; catch the right unit
If \uniqueID = uniqueID
; Clear VP
VirtualPlayfield(px2Coord(x), px2Coord(y)) = #False
; Calculate Distance
DistanceX = Abs(px2Coord(x) + MapPosX - px2Coord(\posX))
DistanceY = Abs(px2Coord(y) + MapPosY - px2Coord(\posY))
\Energy - \EnergyToMove * (DistanceX + DistanceY)
\MoveToX = tox
\MoveToY = toy
\OperationMode = #unit_op_moving
; Play sound
PlayEffect(\SpriteID, #PB_Sound_Loop)
Break
EndIf
EndWith
Next
EndIf
; Server is waiting, sending an OK so he can continue
SK_SendNetworkString64(netServerID, #server_ok)
Case "CHAT" ; OK
AddSystemMessage(Right(netServerString$, Len(netServerString$) - 5))
; Server is waiting, sending an OK so he can continue
SK_SendNetworkString64(netServerID, #server_ok)
Case "WELCOME" ; OK
AddSystemMessage(netServerString$)
; Server is waiting, sending an OK so he can continue
SK_SendNetworkString64(netServerID, #server_ok)
Case "+PLAYER"
playername$ = StringField(netServerString$, 2, Chr(32))
If playername$ <> LocalPlayerName$
; We think there is a new player
dummy = #True
; check if player is already listed
ForEach Players()
; Player already here
If Players()\Name$ = playername$
dummy = #False
Break
EndIf
Next
; Do we have a new player?
If dummy = #True
SelectElement(Players(), hndLocalPlayerElementID)
AddPlayer(#structure_research, playername$)
AddSystemMessage(playername$ + " has joined the battle")
EndIf
EndIf
; Server is waiting, sending an OK so he can continue
SK_SendNetworkString64(netServerID, #server_ok)
Case "STARTUNIT"
Debug netServerString$
; STARTUNIT SpriteID, PlayerName$, x, y, uniqueID
playername$ = StringField(netServerString$, 3, Chr(32))
; Get rest of information
spriteID = Val(StringField(netServerString$, 2, Chr(32)))
x = Val(StringField(netServerString$, 4, Chr(32)))
y = Val(StringField(netServerString$, 5, Chr(32)))
uniqueID = Val(StringField(netServerString$, 6, Chr(32)))
; Find the right player (for listID) and add the unit
ForEach Players()
If Players()\Name$ = playername$
If Players()\Name$ = LocalPlayerName$
AddSystemMessage("You begin with a " + UnitNames(spriteID) + " at " + px2CoordStr(x, y))
MapPosX = px2Coord(x) - PlayfieldViewWidth / 2
MapPosY = px2Coord(y) - PlayfieldViewHeight / 2
If MapPosX <0 : MapPosX = 0 : EndIf
If MapPosY <0 : MapPosY = 0 : EndIf
EndIf
AddUnit(spriteid, ListIndex(players()), x, y, 0, uniqueID)
Break
EndIf
Next
; Server is waiting, sending an OK so he can continue
SK_SendNetworkString64(netServerID, #server_ok)
Case "+UT" ; OK
; +UNT TypeOfUnit, OwnerPlayername, PositionX.l, PositionY.l, Angle.w = 0, uniqueID.l = 0
; 1 2 3 4 5 6 7
; We need the playername / owner
playername$ = StringField(netServerString$, 3, Chr(32))
; Owner should not the local player
If playername$ <> LocalPlayerName$
; Get rest of information
spriteID = Val(StringField(netServerString$, 2, Chr(32)))
x = Val(StringField(netServerString$, 4, Chr(32)))
y = Val(StringField(netServerString$, 5, Chr(32)))
angle = Val(StringField(netServerString$, 6, Chr(32)))
uniqueID = Val(StringField(netServerString$, 7, Chr(32)))
; Find the right player (for listID) and add the unit
ForEach Players()
If Players()\Name$ = playername$
AddUnit(spriteid, ListIndex(players()), x, y, angle, uniqueID)
EndIf
Next
EndIf
; Server is waiting, sending an OK so he can continue
SK_SendNetworkString64(netServerID, #server_ok)
Case "-PLAYER" ; OK
; PLAYERLOST Name$
playername$ = StringField(netServerString$, 2, Chr(32))
AddSystemMessage(playername$ + " has quit the game")
ForEach Players()
If Players()\Name$ = playername$
; Delete players units
ForEach Unit()
If Unit()\OwnerID = ListIndex(Players())
CreateSpriteExplosion(Unit()\PosX, Unit()\PosY, 200)
; Free coords on Virtualplayfield
VirtualPlayfield(px2Coord(Unit()\PosX), px2Coord(Unit()\PosY)) = #False
DeleteElement(unit())
EndIf
Next
; Delete Players Structures
ForEach Structures()
If Structures()\OwnerID = ListIndex(Players())
CreateSpriteExplosion(Structures()\PosX, Structures()\PosY, 400)
; Free coords on Virtualplayfield
; VirtualPlayfield(px2Coord(Structures()\PosX), px2Coord(Structures()\PosY)) = #False
For x = 0 To px2Coord(Structures()\SpriteWidth) -1
For y = 0 To px2Coord(Structures()\SpriteHeight) -1
VirtualPlayfield(px2Coord(Structures()\PosX) + x, px2Coord(Structures()\PosY) + y) = #False
Next y
Next x
DeleteElement(Structures())
EndIf
Next
; Delete Players Projectiles
ForEach Projectiles()
If Projectiles()\OwnerID = ListIndex(Players())
DeleteElement(Projectiles())
EndIf
Next
; Delete Player from List
DeleteElement(Players())
RenewPlayerInfos()
EndIf
Next
; Server is waiting, sending an OK so he can continue
SK_SendNetworkString64(netServerID, #server_ok)
Case "+SE" ; OK
; +STR " + Str(TypeOfUnit) + Chr(32) + Players()\Name$ + Chr(32) + Str(pxCoord(\PosX)) + Chr(32) + Str(pxCoord(\PosY)) + Chr(32) + Str(\Angle) + Chr(32) + Str(\uniqueID))
; 1 2 3 4 5 6 7
; We need the playername / owner
playername$ = StringField(netServerString$, 3, Chr(32))
; Owner should not the local player
If playername$ <> LocalPlayerName$
; Get rest of information
spriteID = Val(StringField(netServerString$, 2, Chr(32)))
x = Val(StringField(netServerString$, 4, Chr(32)))
y = Val(StringField(netServerString$, 5, Chr(32)))
angle = Val(StringField(netServerString$, 6, Chr(32)))
uniqueID = Val(StringField(netServerString$, 7, Chr(32)))
; Find the right player (for listID) and add the unit
ForEach Players()
If Players()\Name$ = playername$
AddStructure(spriteid, ListIndex(players()), x, y, angle, uniqueID)
EndIf
Next
EndIf
; Server is waiting, sending an OK so he can continue
SK_SendNetworkString64(netServerID, #server_ok)
Case "+PE" ; #### TODO
; "+PJT " + Str(SpriteID) + Chr(32) + Players()\Name$ + Chr(32) + Str(\AmmoPower) + Chr(32) + Str(\AmmoSpeed) + Chr(32) + Str(\uniqueID) + Chr(32) + Str(AttackerUniqueID) + Chr(32) + Str(TargetUniqueID))
; 1 2 3 4 5 6 7 8
playername$ = StringField(netServerString$, 3, Chr(32))
Debug "NET: " + netServerString$
; should not be hisself
If playername$ <> LocalPlayerName$
attackerElementID = 0
*targetSpriteHND = 0
; Get rest of information
spriteID = Val(StringField(netServerString$, 2, Chr(32)))
ammopower = Val(StringField(netServerString$, 4, Chr(32))) ; ammopower
speed = Val(StringField(netServerString$, 5, Chr(32))) ; ammospeed
uniqueID = Val(StringField(netServerString$, 6, Chr(32)))
attackeruniqueID = Val(StringField(netServerString$, 7, Chr(32)))
targetuniqueID = Val(StringField(netServerString$, 8, Chr(32)))
ForEach Unit()
; Do we have the attacker? Save Listid
If Unit()\uniqueID = attackeruniqueID
; save ListID
AttackerSpriteID = unit()\SpriteID
; save start-position
x = Unit()\PosX
y = Unit()\PosY
; Do we have the target? Save handle of list
ElseIf Unit()\uniqueID = targetuniqueID
; save Handle of target
*targetSpriteHND = @Unit()
; save end-position
tox = Unit()\PosX
toy = Unit()\PosY
EndIf
Next
ForEach Structures()
; Do we have the attacker? Save Listid
If Structures()\uniqueID = attackeruniqueID
; save ListID
AttackerSpriteID = Structures()\SpriteID
; save start-position
x = Structures()\PosX
y = Structures()\PosY
; Do we Structures the target? Save handle of list
ElseIf Structures()\uniqueID = targetuniqueID
; save Handle of target
*targetSpriteHND = @Structures()
; save end-position
tox = Structures()\PosX
toy = Structures()\PosY
EndIf
Next
; Do we have everything?
If IsSprite(AttackerSpriteID) And *targetSpriteHND
ForEach Players()
; Find the right owner for the projectile
If players()\Name$ = playername$
AddProjectile(spriteID, ListIndex(Players()), AttackerSpriteID, *targetSpriteHND, x, y, tox, toy, ammopower, speed, uniqueID, attackeruniqueID, targetuniqueID)
AddSystemMessage(playername$ + " fired a " + ProjectileName(spriteID) + " from " + px2CoordStr(x, y) + " To " + px2CoordStr(tox, toy))
EndIf
Next
Else
AddSystemMessage("ERROR: Cannot start projectile. Missing Units")
EndIf
EndIf
; Server is waiting, sending an OK so he can continue
SK_SendNetworkString64(netServerID, #server_ok)
; +PJT SpriteID.l, OwnerPlayerID, AttackerElementID, *TargetSpriteHND, FromX, FromY, ToX, ToY, AmmoPower, AmmoSpeed, owneruniqueid, targetuniqueid
; 1 2 3 4 5 6 7 8 9 10 11 12 13
Default
AddSystemMessage("SERVER: " + netServerString$)
SK_SendNetworkString64(netServerID, #server_ok)
EndSelect
Case #PB_NetworkEvent_File
; Receive network file
If ReceiveNetworkFile(netServerID, ExePath$ + LocalPlayerName$ + "_Map.pak")
Debug "received networkfile"
; open packed file
If OpenPack(ExePath$ + LocalPlayerName$ + "_Map.pak")
Debug "map-pak open"
; delete old file
DeleteFile(ExePath$ + LocalPlayerName$ + "_Map.txt")
; unpack file
*hndPackFile = NextPackFile()
If *hndPackFile
Debug "map unpacked"
; *buffer = AllocateMemory(PackFileSize())
; UnpackMemory(*hndPackFile, *buffer)
; create new map-file
lFile = CreateFile(#PB_Any, ExePath$ + LocalPlayerName$ + "_Map.txt")
If lFile
If WriteData(lFile, *hndPackFile, PackFileSize()) = PackFileSize()
Debug "Map saved, loading"
CloseFile(lFile)
ClosePack()
LoadPlayfield(ExePath$ + LocalPlayerName$ + "_Map.txt")
CreateVirtualPlayfield()
AddSystemMessage("New Map received and loaded. Size: " + Str(PlayfieldSizeX) + "x" + Str(PlayfieldSizeY))
Else
AddSystemMessage("ERROR unpacking and saving map")
EndIf
Else
AddSystemMessage("ERROR cannot create map-file (txt)")
EndIf
Else
AddSystemMessage("ERROR no file in pack")
EndIf
Else
AddSystemMessage("ERROR creating map (pakfile)")
EndIf
Else
AddSystemMessage("ERROR receiving map (network)")
EndIf
EndSelect
EndProcedure
Ein Auszug aus dem Server (hier sind die Ankommenden Daten schon in Liste organisiert):
Code: Alles auswählen
If netSE = #PB_NetworkEvent_Connect
netClientID = EventClient()
SysLog("Client " + Str(netClientID) + " connected.")
;
; Someone send DATA?
;
ElseIf netSE = #PB_NetworkEvent_Data
netClientID = EventClient()
ClientString$ = SK_ReceiveNetworkString64(netClientID)
If Left(ClientString$, 1) = "/" ; direkt server command
ServerCommand$ = ClientString$
ClientString$ = ""
EndIf
Select StringField(ClientString$, 1, Chr(32))
Case "BROADCAST"
; Server-Command?
If Left(StringField(ClientString$, 3, Chr(32)), 1) = "/"
ServerCommand$ = Right(ClientString$, Len(ClientString$) - FindString(ClientString$, "/"))
SysLog("Server-Command: " + ServerCommand$)
Else
; BROADCAST FROM TEXT
ForEach players()
If AddElement(NetJob())
Debug "adding job: " + Players()\Name$ + ": " + ClientString$
NetJob()\clientID = Players()\NetID
NetJob()\NetString$ = "CHAT " + Right(ClientString$, Len(ClientString$) - FindString(ClientString$, Chr(32), 1))
EndIf
Next
NetworkLog(ClientString$)
EndIf
Case "HELLO"
netClientID = EventClient()
dummy$ = StringField(ClientString$, 3, Chr(32))
If dummy$ = #sw_version
; Get right Player to add Name
If AddElement(Players())
Players()\NetID = netClientID
Players()\Name$ = StringField(ClientString$, 2, Chr(32))
EndIf
SysLog(Str(netClientID) + ": Client-Version verificated: " + dummy$ + ". Username: " + Players()\Name$ + " (" +Str(Players()\NetID) + ")")
; Welcome the new player
bytessend = SK_SendNetworkString64(netClientID, "WELCOME at " + ServerName$)
SysLog("Sended " + Players()\Name$ + "(" +Str(Players()\NetID) + ") a WELCOME. " + Str(bytessend))
; BROADCAST NEW PLAYER
ForEach Players()
; remember entry no
c = ListIndex(Players())
lastPlayer$ = players()\Name$
ForEach players()
If players()\Name$ <> lastPlayer$
If AddElement(NetJob())
Debug "adding job: " + Players()\Name$ + ": " + ClientString$
NetJob()\clientID = Players()\NetID
NetJob()\NetString$ = "+PLAYER " + lastPlayer$
SysLog("Sending " + Players()\Name$ + " (" +Str(Players()\NetID) + "): new player " + lastPlayer$ + ". " +Str(bytessend))
EndIf
EndIf
Next
; back to the previous
SelectElement(Players(), c)
Next
Else
; Wrong version, bye!
netClientID = EventClient()
SysLog("-ERROR- Wrong Client-Version: " + dummy$)
SK_SendNetworkString64(netClientID, "Wrong client version! Server is " + #sw_version)
Delay(250)
; Remove player from list
ForEach Players()
If Players()\NetID = netClientID
DeleteElement(Players())
Break
EndIf
Next
; Cut him off
CloseNetworkConnection(netClientID)
EndIf
; PLAYER needs the playfield, send it him!
Case "PLAYFIELD"
netClientID = EventClient()
SysLog("Sending " + Str(netClientID) + " Playfield...")
If SendNetworkFile(netClientID, ExePath$ + "Playfield.pak")
SysLog("...sended.")
Else
SysLog("...NOT SEND")
EndIf
Default
If ClientString$
; Who send?
netClientID = EventClient()
; Broadcast it
NetworkLog("BROADCASTING: '" + ClientString$ + "'")
Debug ClientString$
; Send unknown to everyone excluding the sender
ForEach Players()
If Players()\NetID <> netClientID
If AddElement(NetJob())
Debug "adding job: " + players()\Name$ + ": " + ClientString$
NetJob()\clientID = Players()\NetID
NetJob()\NetString$ = ClientString$
EndIf
EndIf
Next
EndIf
EndSelect
;
; Someone DISCONNECT
;
ElseIf netSE = #PB_NetworkEvent_Disconnect
netClientID = EventClient()
; detect lost player
ForEach Players()
If Players()\NetID = netClientID
LostPlayer$ = Players()\Name$
SysLog(LostPlayer$ + " lost connection")
DeleteElement(players())
Break
EndIf
Next
; broadcast to the others
ForEach players()
bytessend = SK_SendNetworkString64(Players()\NetID, "-PLAYER " + LostPlayer$)
SysLog("Informed " + Players()\Name$ + "(" + Str(Players()\NetID) + ") about disconnect of " + LostPlayer$ + "(" + Str(netClientID) + ")")
Next
SysLog("Client #" + Str(netClientID) + " disconnected.")
EndIf
EndIf
;
; JOBLIST
;
ForEach NetJob()
ClientString$ = NetJob()\NetString$
netClientID = NetJob()\clientID
Debug "Doing Job: Client: " + Str(netClientID) + ": "+ ClientString$
SK_SendNetworkString64(netClientID, ClientString$)
DeleteElement(NetJob())
Break
Next
Agent_Sasori
It's not a bug - it's a feature!
http://www.StephenKalisch.de | http://www.ria-tec.com | http://www.dirsync.de
It's not a bug - it's a feature!
http://www.StephenKalisch.de | http://www.ria-tec.com | http://www.dirsync.de
- NicTheQuick
- Ein Admin
- Beiträge: 8838
- Registriert: 29.08.2004 20:20
- Computerausstattung: Ryzen 7 5800X, 64 GB DDR4-3200
Ubuntu 24.04.2 LTS
GeForce RTX 3080 Ti - Wohnort: Saarbrücken
Re: Netzwerksynchronisation zwischen Clients mittels Server
Vielleicht helfen dir diese Artikel weiter, die ich mal so auf die schnelle ergoogelt habe. Sie sind allerdings in Englisch.
Network Game Programming und Simple online pong game network synchronization
Network Game Programming und Simple online pong game network synchronization
Re: Netzwerksynchronisation zwischen Clients mittels Server
Hi Nic.
Englisch ist nicht das Problem soweit.
Im Groben ist mir klar um was es geht. Teilweise von mir auch schon umgesetzt. Insbesondere (alte Version) die Bestätigung des Servers einen Befehl erhalten zu haben. Bisher ist der Client an dieser Stelle stehen geblieben und hat gewartet was definitiv zu Asynkronität geführt hat, da andere Proceduren weiter laufen müssen.
Vor- wie Nachteil hat die clientseitige Behandlung aller Ereignisse. Der Server ist "dumm" sozusagen. Damit schließe ich Probleme aus wie in dem ersten Artikel beschrieben.
Es wird sozusagen immer nur ein Startbefehl weiter gegeben und sobald dieser ankommt ausgeführt - wie z.B. das erzeugen eines Schusses oder setzen einer Einheit.
Ich bin dazu über gegangen, jedem relevanten Listeneintrag (Unit, Structure, Projectile) eine eindeutige ID zuzuordnen. Nur so ließ sich sicherstellen, dass die richtigen Einheiten angesprochen werden auf Seite der anderen Clients.
Vielleicht sollte die Übermittlung eines "Änderungsbefehls" wie "ADDUNIT()" etc zwar angelegt aber nicht ausgeführt werden bis ein OK kommt vom Server. Dies würde aber nochmehr Befehle bedeuten.
Ich habe versucht die Befehle so kurz wie möglich zu halten und nur wirklich relevante Daten zu übermitteln. Bei einer Bewegung zum Beispiel: MOVE playername UnituniqueID fromX fromY toX toY Speed
Gut, hier könnte noch gekürzt werden, da FromX/FromY klar sein sollte (wo die Unit gerade ist). Dies bedingt, dass vorher alles Synchron ist. Speed ebenso, da die Anfangswerte gleich sind. Sollte die Einheit ein Upgrade erhalten haben (ADDRESEARCH) wurde dies ja (wenn alles glatt geht) ebenfalls ordnungsgemäß ausgeführt. Hier liese sich also noch optimieren. Aber machen die paar Bytes es dann auch aus? Letztenendes übermittle ich so oder so 64 bytes. Weniger wirds nicht.
Englisch ist nicht das Problem soweit.
Im Groben ist mir klar um was es geht. Teilweise von mir auch schon umgesetzt. Insbesondere (alte Version) die Bestätigung des Servers einen Befehl erhalten zu haben. Bisher ist der Client an dieser Stelle stehen geblieben und hat gewartet was definitiv zu Asynkronität geführt hat, da andere Proceduren weiter laufen müssen.
Vor- wie Nachteil hat die clientseitige Behandlung aller Ereignisse. Der Server ist "dumm" sozusagen. Damit schließe ich Probleme aus wie in dem ersten Artikel beschrieben.
Es wird sozusagen immer nur ein Startbefehl weiter gegeben und sobald dieser ankommt ausgeführt - wie z.B. das erzeugen eines Schusses oder setzen einer Einheit.
Ich bin dazu über gegangen, jedem relevanten Listeneintrag (Unit, Structure, Projectile) eine eindeutige ID zuzuordnen. Nur so ließ sich sicherstellen, dass die richtigen Einheiten angesprochen werden auf Seite der anderen Clients.
Vielleicht sollte die Übermittlung eines "Änderungsbefehls" wie "ADDUNIT()" etc zwar angelegt aber nicht ausgeführt werden bis ein OK kommt vom Server. Dies würde aber nochmehr Befehle bedeuten.
Ich habe versucht die Befehle so kurz wie möglich zu halten und nur wirklich relevante Daten zu übermitteln. Bei einer Bewegung zum Beispiel: MOVE playername UnituniqueID fromX fromY toX toY Speed
Gut, hier könnte noch gekürzt werden, da FromX/FromY klar sein sollte (wo die Unit gerade ist). Dies bedingt, dass vorher alles Synchron ist. Speed ebenso, da die Anfangswerte gleich sind. Sollte die Einheit ein Upgrade erhalten haben (ADDRESEARCH) wurde dies ja (wenn alles glatt geht) ebenfalls ordnungsgemäß ausgeführt. Hier liese sich also noch optimieren. Aber machen die paar Bytes es dann auch aus? Letztenendes übermittle ich so oder so 64 bytes. Weniger wirds nicht.
Agent_Sasori
It's not a bug - it's a feature!
http://www.StephenKalisch.de | http://www.ria-tec.com | http://www.dirsync.de
It's not a bug - it's a feature!
http://www.StephenKalisch.de | http://www.ria-tec.com | http://www.dirsync.de
Re: Netzwerksynchronisation zwischen Clients mittels Server
Moin!
Die Datenübertragung dauert
Anfragezeit + Datenmenge / Datentransfergeschwindigkeit
und somit ist es bei heutigen DSL-Leitungen irrelevant, ob Du 10 Bytes, 100 Bytes oder 600 Bytes überträgst. Entscheidend ist die Anfragezeit, die Du bspw. mit ping sehr schnell ermitteln kannst. Frag einfach, wenn Du nicht weißt, wie's geht. Diese Zeit liegt bei z.B. 100ms oder 300ms und gibt die Dauer an, bis die beiden Teilnehmer im Netz über weiß der Geier wie viele Zwischenstationen Kontakt aufgenommen haben.
Die obige Formel ist übrigens stark vereinfacht, weil wir noch das MTU berücksichtigen müßten, was aber bei den paar Bytes keine Geige spielt. Also kann es für Dich nicht darum gehen, die 64 Bytes Daten noch weiter einzudampfen, sondern nur noch, der internen Signalbearbeitung Beine zu machen.
Gruß - WPö
Die Datenübertragung dauert
Anfragezeit + Datenmenge / Datentransfergeschwindigkeit
und somit ist es bei heutigen DSL-Leitungen irrelevant, ob Du 10 Bytes, 100 Bytes oder 600 Bytes überträgst. Entscheidend ist die Anfragezeit, die Du bspw. mit ping sehr schnell ermitteln kannst. Frag einfach, wenn Du nicht weißt, wie's geht. Diese Zeit liegt bei z.B. 100ms oder 300ms und gibt die Dauer an, bis die beiden Teilnehmer im Netz über weiß der Geier wie viele Zwischenstationen Kontakt aufgenommen haben.
Die obige Formel ist übrigens stark vereinfacht, weil wir noch das MTU berücksichtigen müßten, was aber bei den paar Bytes keine Geige spielt. Also kann es für Dich nicht darum gehen, die 64 Bytes Daten noch weiter einzudampfen, sondern nur noch, der internen Signalbearbeitung Beine zu machen.
Gruß - WPö
Ich glaube nur der Statistik, die ich selbst gefälscht habe!
Meine Netzpräsenz: WPö.de
PB5.31 auf LMDE und Pentium T7200 2,00GHz, 4GB DDR2, ATI X1400.
Meine Netzpräsenz: WPö.de
PB5.31 auf LMDE und Pentium T7200 2,00GHz, 4GB DDR2, ATI X1400.
Re: Netzwerksynchronisation zwischen Clients mittels Server
Hi Agent,
dein Empfangsbuffer des Servers (64 Byte??????) ist viel zu klein. Der wird sofort vollgeschossen. Deshalb gehen deine Daten von den Clients verloren. Erhöhe den auf 65535. Den Rest habe ich mir nicht so genau angesehen.
dein Empfangsbuffer des Servers (64 Byte??????) ist viel zu klein. Der wird sofort vollgeschossen. Deshalb gehen deine Daten von den Clients verloren. Erhöhe den auf 65535. Den Rest habe ich mir nicht so genau angesehen.
- alles was ich hier im Forum sage/schreibe ist lediglich meine Meinung und keine Tatsachenbehauptung
- unkommentierter Quellcode = unqualifizierter Müll
- unkommentierter Quellcode = unqualifizierter Müll
Re: Netzwerksynchronisation zwischen Clients mittels Server
Ich wollte mal eben einen neuen Stand durchgeben.
Das Problem ist im Wesentlichen gelöst indem der Server die größte Reaktionszeit (Ping) ermittelt. Sobald ein Client z.b. Ein MOVE oder BUILD oder SHOT befehl an den Server gibt, broadcastet der diesen an alle anderen inkl dem Sender mit einem "AuszuführenInMillisekunden" Zusatz, der dem der maximalen Ping-Zeit entspricht. Somit stelle ich sicher, dass alle Clients zur gleichen Zeit den Befehl ausführen. Jeder Client muss diesen Befehl an den Server bestätigen. Erfolgt dies nicht wird ein Warte-Befehl mit neuem Timer an alle für diesen Befehl geschickt.
Nachdem alles einige zeit recht gut lief kam ich beim Testen auf anderen PCs (Multiplayer) nun zu einem irreführenden Szenario im Bezug auf dem Befehl ElapsedMilliseconds()
Wie kann es sein, dass auf einem PC der Wert für ElapsedMilliseconds() negativ ist?
In Folge dessen werden in meinem Spiel die Einheiten GARNICHT gebaut, da der Timer negativ ist und niemals über den WarteTimer kommt (zb. erst in ein paar Jahren *lol)
Das Problem ist im Wesentlichen gelöst indem der Server die größte Reaktionszeit (Ping) ermittelt. Sobald ein Client z.b. Ein MOVE oder BUILD oder SHOT befehl an den Server gibt, broadcastet der diesen an alle anderen inkl dem Sender mit einem "AuszuführenInMillisekunden" Zusatz, der dem der maximalen Ping-Zeit entspricht. Somit stelle ich sicher, dass alle Clients zur gleichen Zeit den Befehl ausführen. Jeder Client muss diesen Befehl an den Server bestätigen. Erfolgt dies nicht wird ein Warte-Befehl mit neuem Timer an alle für diesen Befehl geschickt.
Nachdem alles einige zeit recht gut lief kam ich beim Testen auf anderen PCs (Multiplayer) nun zu einem irreführenden Szenario im Bezug auf dem Befehl ElapsedMilliseconds()
Wie kann es sein, dass auf einem PC der Wert für ElapsedMilliseconds() negativ ist?
In Folge dessen werden in meinem Spiel die Einheiten GARNICHT gebaut, da der Timer negativ ist und niemals über den WarteTimer kommt (zb. erst in ein paar Jahren *lol)
Agent_Sasori
It's not a bug - it's a feature!
http://www.StephenKalisch.de | http://www.ria-tec.com | http://www.dirsync.de
It's not a bug - it's a feature!
http://www.StephenKalisch.de | http://www.ria-tec.com | http://www.dirsync.de