Client/Server Echtzeit per UDP - Architekturproblem



  • Hoi,

    Hoffe das Unterforum ist richtig, Spieleforum hätte auch gepasst, aber das funktioniert nur mit Registrierung.

    Ich habe ein Problem mit der Logik eines Client/Server Systems, das ganze ist definitiv Echtzeit und zeitkritisch, daher bezieht sich alles hier auf UDP, TCP bringt mich da nicht weiter. Ferner handelt es sich um eine 2-Thread Lösung mit Java.nio, aber das ist eigentlich für folgendes Problem eher uninteressant.

    Das Bildchen hier soll mein Problem mal verdeutlichen:

    http://serge.xxl-hoster.com/coax/serverprob.gif

    Oben befindet sich Client 1, aus Client-Sicht, unten Client 2, aus Client-Sicht und in der Mitte der Server, so wie er die Sache sieht.

    Angenommen jetzt, Client 1 schickt gerade fröhlich Paket 2 (eine Zusammenfassung je 10 ms) auf die Reise, welches im Gegensatz zu seinen Nachfolgern grad noch so röchelnd ans Ziel ankommt. Per Dead-Reckoning berechnet der Server unsere Bewegung weiter, da kein anderes Paket seitens Client 1 mehr eintrifft (rote Linie).

    Bei Client 2 läuft alles prima, denn der Server gehört seinem besten Kumpel, der zufällig 3 Häuser weiter wohnt. Da hat Client 1 aus Amerika wenig Chancen, und wird in TimeStamp 4 von Client 2 abgeschossen. Wohlgemerkt, der vom Server angenommene Client 1 wird abgeschossen, nicht aber aufgrund des tatsächlichen Weges von Client 1 (wurde ja aufgrund Paketverlust berechnet).

    Meine Frage also:
    Wie kann ich so was verhindern? Bzw. was ist hier die richtige Behandlung dieses Problems?

    Im Detail:
    Der Server schickt mir mit seiner nächsten Nachricht immer auch die Nummer des letzten ClientPakets mit. Ich als Client sende nun immer den logischen Nachfolger auf dieser „letztenPaketId“. Selbstverständlich muss ich dazu die vom Client ausgehenden Pakete puffern.
    Ebenso müsste ich aber auch das Paket vom Server in den Eingangspuffer speichern, um dem Server etwaiges Neusenden zu ersparen. Bedeutet:
    Der Server schickt mir immer aktuelle Pakete mit meiner letzten PaketID (hier die 2). Wie aber kann ich jetzt diese Diskrepanz wieder auflösen, wie also quasi den Server in die Vergangenheit schicken, oder den Client in die Zukunft? Ist das überhaupt der richtige Weg?

    Ein anderes Problem wäre da noch, welche Information ich in den Paketen selber speichern müsste, um keinen Informationsverlust zu bekommen.

    Angenommen ich sende als Client alle 10 ms ein Paket zum Server, so müsste ich ja die komplette Bewegung, die außerhalb meiner DR-Spezifikation liegt speichern, und zwar mit einem Time-Delta zwischen den Aktionen, damit der Server die genaue Zeit der jeweiligen Aktion nachvollziehen kann. Ich sende also Events + TimeDelta, zum Beispiel einen Schussbefehl oder Richtungsänderungen etc. Ich kann die maximale Anzahl möglicher Aktionen natürlich Clientseitig begrenzen, um die Pakete kleiner zu halten (Delta Compression etc. ist natürlich drin), aber irgendeinen Mechanismus, um Aktionen Serverseitig aufschlüsseln zu können, brauche ich dann doch.

    Ich denke nicht, dass nach jeder Aktion ein Paket versendet wird, da so bei Hektik schnell die Bandbreite zusammenschrumpft.

    Programmiert wird das mit Java.NIO, aber ich erwarte hier keinen Quellcode, mir gehts nur darum, einen sauberen Weg für meine Probleme zu finden. Entlanglatschen kann ich den (hoffentlich) alleine.

    P.S.
    Möglichkeiten wie mehrere Pakete in einem zu versenden sind mir bekannt, lösen das Problem aber nicht vollständig.

    mfG,
    Coax



  • was genau du schickst ist eigentlich deine sache, hauptsache du kannst die clients synchron halten.

    du kanst z.b. in jedem packet die aktuelle ID, Timestamp, position und einen richtungsvector für jedes objekt abschicken. du kannst auch wie z.b bei x-wing vs. tie-fighter jeden spielerinput und id abschicken worüber sich dann jeder die bewegungen errechnen kann...

    gegen packetloss hilft es, wenn du immer zwei packete zusammen schickst,

    (packet1|packet2)
    (packet2|packet3)
    (packet3|packet4)
    .
    .
    .
    

    so hast du sehr selten das problem das du nen lag wegen packetlost bekommst.

    btw. die größe der packete die per dsl verschickt werden (mittels pppoe) können bis zu 1492byte gross sein(hängt von der client-config ab), es sollte also nicht viel ausmachen wenn du per send doppeltsoviele daten verschickt wie eigentlich nötig.

    du solltest dir auch vielleicht angewöhnen beim berechnen der bandbreite nicht in bytes sondern in packeten zu rechnen, denn ein 2mbit server der 2k packete mit je 24byte bekommt kann trotzdem dicht sein.

    rapso->greets();



  • rapso schrieb:

    was genau du schickst ist eigentlich deine sache, hauptsache du kannst die clients synchron halten.
    ...
    gegen packetloss hilft es, wenn du immer zwei packete zusammen schickst,

    (packet1|packet2)
    (packet2|packet3)
    (packet3|packet4)
    .
    .
    

    so hast du sehr selten das problem das du nen lag wegen packetlost bekommst.
    ...

    Gut, aber was passiert WENN ein Packet-Loss auftritt?
    Das war eigentlich meine zentrale Frage, da ein Packet-Loss ja zu einem Sync-Verlust führt...

    Ignoriere ich einfach die verlorenen Pakete?
    Das heisst wenn ich als Client 10 Pakete absende, die nicht ankommen, setze ich dann einfach den ClientState = Serverstate?

    Oder verwalte ich serverseitig mehrere Serverstates parallel zu unterschiedlichen Zeiten?

    Ich habe ein Problem damit, die verlorenen Client-Pakete zu resenden, weil ich nicht weiss, wie ich diese "veralteten" Pakete Serverseitig behandeln soll. Für diese Pakete ist der Zug ja schon abgefahren...
    Mehrere alte States zu verwalten erscheint mir aber irgendwie genauso unpassend. Oder ist Packet-Loss so selten? Wohl eher weniger, wenn ich deine Gamasutra-XWing Antwort Revue passieren lasse (Hatte den Artikel gelesen) 🙂

    Soweit ich weiss ist Packet-Loss wohl das Hauptproblem...

    bis hier,
    Coax



  • rapso schrieb:

    gegen packetloss hilft es, wenn du immer zwei packete zusammen schickst

    Wuerde ich nur tun, wenn es nicht Online, sondern nur im LAN gespielt wird, denn du bekommst schnell Traffic Probleme.

    CCoax schrieb:

    rapso schrieb:

    was genau du schickst ist eigentlich deine sache, hauptsache du kannst die clients synchron halten.
    ...
    gegen packetloss hilft es, wenn du immer zwei packete zusammen schickst,

    (packet1|packet2)
    (packet2|packet3)
    (packet3|packet4)
    .
    .
    

    so hast du sehr selten das problem das du nen lag wegen packetlost bekommst.
    ...

    Gut, aber was passiert WENN ein Packet-Loss auftritt?
    Das war eigentlich meine zentrale Frage, da ein Packet-Loss ja zu einem Sync-Verlust führt...

    Ignoriere ich einfach die verlorenen Pakete?
    Das heisst wenn ich als Client 10 Pakete absende, die nicht ankommen, setze ich dann einfach den ClientState = Serverstate?

    Oder verwalte ich serverseitig mehrere Serverstates parallel zu unterschiedlichen Zeiten?

    Ich habe ein Problem damit, die verlorenen Client-Pakete zu resenden, weil ich nicht weiss, wie ich diese "veralteten" Pakete Serverseitig behandeln soll. Für diese Pakete ist der Zug ja schon abgefahren...
    Mehrere alte States zu verwalten erscheint mir aber irgendwie genauso unpassend. Oder ist Packet-Loss so selten? Wohl eher weniger, wenn ich deine Gamasutra-XWing Antwort Revue passieren lasse (Hatte den Artikel gelesen) 🙂

    Soweit ich weiss ist Packet-Loss wohl das Hauptproblem...

    bis hier,
    Coax

    Naja - je nachdem wie der Netzwerkaufbau ist - ich wuerde in Reliable, Unreliable und Immediate Packages unterscheiden.

    Die wichtigen Packete, muessen in eine Queue, sonst wirst du Probleme nicht in den Griff. Ohne ACK kommst du nicht weit.

    Immediate -> Muessen sofort gesendet werden - egal was kommt ( Darf ich schiessen? Darf ich ... )

    Reliable -> Muessen ankommen - also musst du ACK Packages am Server zuruck schicken, wenn diese nicht ankommen, dann schickst du das Packet nochmal.
    Bei UDP hast du ja eh das Problem, dass du die Reihenfolge beachten musst, und darauf achten, dass sie ankommen.

    Unreliable -> Sowas wie Position changes, also da ists egal, wenn ein Packet nicht ankommt, denn das naechste kommt bald.

    Immediate sind eigentlich nur bei Spielen, bei denen es stressig wird wichtig. Aber bei einem Strategiespiel faehst du eh einen anderen Netzwerkcode, als bei einem Shooter.

    Darf man fragen, wass fuer ein Spiel es wird?



  • CCoax schrieb:

    rapso schrieb:

    was genau du schickst ist eigentlich deine sache, hauptsache du kannst die clients synchron halten.
    ...
    gegen packetloss hilft es, wenn du immer zwei packete zusammen schickst,

    (packet1|packet2)
    (packet2|packet3)
    (packet3|packet4)
    .
    .
    

    so hast du sehr selten das problem das du nen lag wegen packetlost bekommst.
    ...

    Gut, aber was passiert WENN ein Packet-Loss auftritt?
    Das war eigentlich meine zentrale Frage, da ein Packet-Loss ja zu einem Sync-Verlust führt...

    Ignoriere ich einfach die verlorenen Pakete?
    Das heisst wenn ich als Client 10 Pakete absende, die nicht ankommen, setze ich dann einfach den ClientState = Serverstate?

    Oder verwalte ich serverseitig mehrere Serverstates parallel zu unterschiedlichen Zeiten?

    Ich habe ein Problem damit, die verlorenen Client-Pakete zu resenden, weil ich nicht weiss, wie ich diese "veralteten" Pakete Serverseitig behandeln soll. Für diese Pakete ist der Zug ja schon abgefahren...
    Mehrere alte States zu verwalten erscheint mir aber irgendwie genauso unpassend. Oder ist Packet-Loss so selten? Wohl eher weniger, wenn ich deine Gamasutra-XWing Antwort Revue passieren lasse (Hatte den Artikel gelesen) 🙂

    Soweit ich weiss ist Packet-Loss wohl das Hauptproblem...

    bis hier,
    Coax

    das hängt ganz vom spiel und von den daten ab die du versendest.

    wenn du z.b. einen shooter machst und immer die aktuelle position und den richtungsvector schickst, dann kanst du bei packetloss drauf verzichten ein verlorenes packet nochmal zu schicken, das bringt zwar ein paar kleine fehler mitsich, aber bei netzwerk dingen bei denen es sehr auf ping ankommt, muss man fehlertollerante protokolle nutzen.

    wenn es sehr wichtig ist, dass du alle packete übermittelt hast, dann mußt du einen cache einbauen in dem du dir die gesendeten packete hällst.
    Die verbindungspartner müssen sich dann in ihren packeten auch immer schicken welches die größte ID der nach der reihe empfangenen packete ist, die packete mit der ID udn kleinere kannst du dann sorglos löschen.
    natürlich könnte es bei schlechter verbindung passieren dass beide ihre packete nicht empfangen, dafür mußt du ein timeout machen.
    wenn du z.b einen shooter hast, wäre ein timeout von vielleicht 500ms schon gross genug. dann mußt du die letzten packete entweder neu schicken oder meldest dem user ein connection-loss.

    dass es mehrere verbindungen gibt, davon geh ich eh aus. die meißten spiele haben eine UDP verbindung für schnell zu übermittelnde daten, die aber auf einem fehlertolleranten protokoll basieren (z.b. aktuelle position, richtungsvector und timestamp).
    und es gibt eine tcp verbindung, z.b. für chat, textureupload, trading-system usw.

    aber ich SnorreDev hat scho räscht, ohne zu wissen welche art von anforderungen dein spiel hat, kann man keine spezialisierte optimierung empfehlen.

    rapso->greets();



  • Yupp, Sorry.

    Hätte wohl eher zeitkritisch anstatt Echtzeit im Threadtitel schreiben sollen, das Spiel soll zwar mal kein Shooter im eigentlichen Sinne werden, hat aber auf jeden Fall die gleichen Anforderungen. Hier groß etwas dazu zu sagen wäre aber sinnlos, denn die unterste Ebene bildet hier nun mal networking, ohne ein funktionierendes Client/Server System brauch ich das Spiel noch garnicht anpacken...
    Vielleicht als Stichwort(e):
    Oldschool Iso-Shooter in Pseudo2D (OGL)

    Zum Thema:

    Naja - je nachdem wie der Netzwerkaufbau ist - ich wuerde in Reliable, Unreliable und Immediate Packages unterscheiden.

    Schließe ich nicht aus, allerdings finde ich die Möglichkeit unpraktisch. Dann wäre wahrscheinlich Rapsos Idee besser, per TCP/IP wirklich relevante Daten wie Verbindungsaufbau oder Leveldownload u.ä. zu verwalten.

    ***** Betrifft mehrere Pakete in einem *****
    Wuerde ich nur tun, wenn es nicht Online, sondern nur im LAN gespielt wird, denn du bekommst schnell Traffic Probleme.

    Gerade im Lan ist es eigentlich sinnlos, weil da korrekte Lieferung schon fast garantiert wird, IMO 😉

    wenn du z.b. einen shooter machst und immer die aktuelle position und den richtungsvector schickst, dann kanst du bei packetloss drauf verzichten ein verlorenes packet nochmal zu schicken,[...]

    Genau das tue ich, nachdem ich die Pakete delta-komprimiert habe. Die werden also wahrscheinlich ziemlich klein, weshalb ich auch deshalb schon mehrere in ein Paket packen könnte. Das verlorene Paket wird auch größtenteils durch Dead-Reckoning "verschleiert". Also müsste ich für diesen Ansatz wirklich die emulierte Version des Servers nehmen, um Interaktion zu berechnen. Dieses würde natürlich nur für kurze Zeiträume gelten, und evtl. ein Problem darstellen, wenn geschossen wird, da hat SnorreDev absolut Recht.

    wenn es sehr wichtig ist, dass du alle packete übermittelt hast, dann mußt du einen cache einbauen in dem du dir die gesendeten packete hällst.
    Die verbindungspartner müssen sich dann in ihren packeten auch immer schicken welches die größte ID der nach der reihe empfangenen packete ist, die packete mit der ID udn kleinere kannst du dann sorglos löschen.

    ACK, bis auf eine gleiche ID, die könnte man evtl. verwenden um den Puffer zurückzusetzen (Clientseitig). Es tauschen eh beide Parteien (Server/Client) die jeweils empafangenen Paket-IDs aus, quasi Huckepack im regulären Paket. Ich will es auf jeden Fall vermeiden, eine "angekommen" Nachricht zum Client zu schicken, nur um der Nachricht willen.

    Aber genau in deinem angesprochenen Cache liegt mein Verständnisproblem. Wenn ein Client Probleme hat, darf ich IMO nicht den Server bremsen, um die verlorenen Pakete nachzuholen. Das wäre ziemlich exakt der TCP-Effekt den ich vermeiden will. Wie aber muss ich den Server aufbauen, bzw. das Cache-Handling, um dem "verlorenen" Client wieder korrekt einzuweisen, ohne das die anderen was davon merken. Player-Bouncing des betreffenden Clients wie man das aus Internetpartien in Shootern kennt lässt sich nicht vermeiden, aber ich kann mir den internen Ablauf nicht wirklich vorstellen.

    Den wenn ein Client auf den anderen (gelossten) Client feuert, aber nur die Server-Interpretierte Version trifft, ist der gelosste Player für den Server tot (und damit auch für alle anderen Spieler). Nun kommt das Packet an, welches vermisst wurde, der Server kann die korrekte Position des gelossten Clients ermitteln. Und jetzt gibts einen Konflikt:

    Ich kann ja wohl schlecht die Zeit wieder zurückdrehen und der gelosste Client lebt auf einmal wieder, weil die korrekte Position nicht in der Schussbahn des anderen Clients liegt.

    Ich hoffe ich konnte die Problematik jetzt klarer darstellen 🙂



  • das läuft schon immer so, dass nur die serverdaten als die verlässlichen gelten. wenn also ein client einen lag hat und abgeschossen wird obwohl er in wirklichkeit 'bei sich' rumläuft, dann ist der client halt tod. das lässt sich nicht ändern, das war schon bei doom1 so 😉
    bei diesen spielen wird ja auch nicht udp benutzt um tcp nachzubilden, das sind dann die fehlertolleranten protokolle (wie schon mehrfach erwähnt, z.b. position, richtungsvector, timestamp). dort ist es dir egal, ob du nen packetloss hast, das wirkt sich nur zum nachteil für den einen spieler aus, der eine schlechte verbindung hat, alle anderen können spielen.

    rapso->greets();



  • rapso schrieb:

    das läuft schon immer so, dass nur die serverdaten als die verlässlichen gelten. wenn also ein client einen lag hat und abgeschossen wird obwohl er in wirklichkeit 'bei sich' rumläuft, dann ist der client halt tod. das lässt sich nicht ändern, das war schon bei doom1 so 😉
    bei diesen spielen wird ja auch nicht udp benutzt um tcp nachzubilden, das sind dann die fehlertolleranten protokolle (wie schon mehrfach erwähnt, z.b. position, richtungsvector, timestamp). dort ist es dir egal, ob du nen packetloss hast, das wirkt sich nur zum nachteil für den einen spieler aus, der eine schlechte verbindung hat, alle anderen können spielen.

    rapso->greets();

    Wenn dem tatsächlich so ist, funktioniert das Internet wohl zuverlässiger als ich dachte.
    Dann werde ich erst mal ein redundantes System aufbauen und ggf. später erweitern.

    THX,
    Coax



  • zogg einfach mal doom auf einem sehr langsamen rechner gegen einen fixen. Ich hab miterlebt wie jemand bei mir schon tod war und auf dem nachbarrechner das neue frame noch nicht dargestellt wurde in dem er tod ist 🙂

    du kannst dich auch auf nen server mit nem ping von 500ms verbinden, da wirst du auch miterleben dass du relativ oft gekillt wirst, nicht weil die anderen so gut zielen, sondern weil du für den server für 500ms eine konstante bewegung durchläufst und dann natürlich getroffen wirst.

    rapso->greets();



  • Mhhh...
    Spontan habe ich nur Q3A im Kopf, und dort zischen nach einem Lag auf einmal alle wie wild im Zeitraffer durch die Gegend, oder es gibt einen deconnect. Bei schlechter Verbindung im allgemeinen gibts massives Player-Bouncing.

    Ich kenne nur leider die ganz grobe Arbeitsweise des Q3-Servers...

    rapso schrieb:

    du kannst dich auch auf nen server mit nem ping von 500ms verbinden, da wirst du auch miterleben dass du relativ oft gekillt wirst, nicht weil die anderen so gut zielen, sondern weil du für den server für 500ms eine konstante bewegung durchläufst und dann natürlich getroffen wirst.

    Ich sterb' wohl eher, weil ich so schlecht bin 🙄

    Aber ich denke du hast da Recht...bin schon wahnsinnig oft durch solche "seltsamen" Verhältnisse gestorben. Jetzt muss ich nur noch dazu kommen, den Kram in diesem Leben umzusetzen. Böse knappe Zeit.

    Coax



  • CCOAX schrieb:

    Ich sterb' wohl eher, weil ich so schlecht bin 🙄

    *chrrrr* Is' klar, Meister der Railgun und Trickjump-Guru!! 😃



  • Sgt. Nukem schrieb:

    CCOAX schrieb:

    Ich sterb' wohl eher, weil ich so schlecht bin 🙄

    *chrrrr* Is' klar, Meister der Railgun und Trickjump-Guru!! 😃

    du hast "verführer all unserer (ex)freundinen vergessen" *hehe*

    rapso->greets();



  • rapso schrieb:

    Sgt. Nukem schrieb:

    CCOAX schrieb:

    Ich sterb' wohl eher, weil ich so schlecht bin 🙄

    *chrrrr* Is' klar, Meister der Railgun und Trickjump-Guru!! 😃

    du hast "verführer all unserer (ex)freundinen vergessen" *hehe*

    Kennst Du den Kerl auch?!?! 😕

    😃 👍



  • rapso schrieb:

    Sgt. Nukem schrieb:

    CCOAX schrieb:

    Ich sterb' wohl eher, weil ich so schlecht bin 🙄

    *chrrrr* Is' klar, Meister der Railgun und Trickjump-Guru!! 😃

    du hast "verführer all unserer (ex)freundinen vergessen" *hehe*

    rapso->greets();

    😉 Und du hast vergessen, auch die zukünftigen Freundinnen zu erwähnen...



  • um wieviel langsamer ist TCP gegenüber UDP?

    Ich habe ein ähnliches problem (Client-Syncronisierung)
    habe aber mein Spiel auf TCP aufgebaut...

    Wie funktioniert das mit dem Timestamp in den Paketen?



  • Rodney schrieb:

    um wieviel langsamer ist TCP gegenüber UDP?

    Ich habe ein ähnliches problem (Client-Syncronisierung)
    habe aber mein Spiel auf TCP aufgebaut...

    Wie funktioniert das mit dem Timestamp in den Paketen?

    😕



  • Kann man nicht so genau sagen.
    Und kommt auf den Anwendungsfall an.
    Ein Schachspiel z.B. würde ich der Einfachheit halber nur auf TCP aufbauen.



  • Vor allem hätte UDP da keine Vorteile.



  • nman schrieb:

    Vor allem hätte UDP da keine Vorteile.

    Doch natürlich.
    Durch die geringe Paketgrösse und das "verbindungslose" kommen die Päckchen schneller an (falls sie ankommen).

    Was Du wahrscheinlich (richtigerweise) meinst, ist, daß man bei einem rundenbasierten Spiel wie Schach da jetzt nicht sooo drauf achten muß, und der Nutzen eher gering ist... 😉 🕶



  • Sgt. Nukem schrieb:

    Doch natürlich.
    Durch die geringe Paketgrösse und das "verbindungslose" kommen die Päckchen schneller an (falls sie ankommen).

    Was Du wahrscheinlich (richtigerweise) meinst, ist, daß man bei einem rundenbasierten Spiel wie Schach da jetzt nicht sooo drauf achten muß, und der Nutzen eher gering ist... 😉 🕶

    Naja, dass bei Schach die Latenz sch...egal ist, ist natürlich klar; aber UDP hätte hier den Nachteil dass man erst aufwändig eine Möglichkeit implementieren müsste, festzustellen, ob Pakete vollständig angekommen sind, da es doch verdammt unpraktisch wäre, wenn was verlorenginge. (Wow, so viele Beistriche in einem Satz...)

    Und Schachzüge brauchen ja nicht größer sein als "MV A:3 B:3" oä... 😉


Anmelden zum Antworten