Netzwerspiel Probleme
-
Hallo,
da ich schon eine Chatprogramm geschrieben hatte, dachte ich mir, hey, probierste mal nen Spiel aus. Also habe ich mir das einfachste vorgenommen, was mir eingefallen ist: Pong. (2 Schläger, ein Ball, kennt ja Jeder)
Das Spiel war in 30 Minuten fertig und ich hatte es so geplant, dass ich die sachen mit den Sockets gut einbauen konnte. Und jetzt beginnen die Probleme.
Ich habe die Sache mit den Sockets in einen eigenen Thread gepackt. Ungefehr so:
DWORD WINAPI ThreadFuncServer(LPVOID data) //Hier das Objekt annehmen { //Initialisieren while(rc!=SOCKET_ERROR) { //In dieser Schleife immer senden und empfangen } //Hier alles abbauen. }Die Fehler sind nun:
1. Das Spiel, selbst hat nur 1 % Prozessorauslastung, aber mit dem Thread 100%
(Wie umgehe ich das, dass ich permanent abfragen muß, ob Daten angekommen
sind?)2. Obwohl dieser Thread einige 1000 mal pro Sekunde durchlaufen wird, schafft
er es komischerweise nur ca 10 Integerwerte pro Sekunde zu empfangen
(Senden deutlich mehr) und das bei ner 100Mbit leitung. (LAN)
Dadurch ruckelt das Spiel natürlich beim Client wie bekloppt.
Wie umgeht man das Problem? Soll der Client sebständig "zwischenwerte
berechnen"? Aber wie mache ich das dann bei den Schlägern, da man ja nicht
vorraussagen kann wie der gegenspieler sie bewegt.3. Für jeden übermittelden Wert muss ich mir von Hand ne Codierung überlegen,
Wie ich die Werte zuordne. z.B Übersetzte ich die Double werte der
Ballposition in Char[x], hänge "Ballx" bzw. "bally" an und sende es dann.
Auf der anderen Seite muß ich dies wieder auseinanderfummeln in
double übersetzen und richtig zuordnen (anhand der Textanhänge).
Gibt es da nen besseren Weg?4. Selbst wenn es schnell genug übermittelt wird ist ja ne verzögerung geben
und der Ball wird beim Client kurzzeitig die Spielfeldgrenze
überschreiten. Wie verhindert man sowas.5. Wenn es schon solche Probleme bei sowas Einfachem gibt, wie machen es dann die Programmieren von Spelen wie WoW, oder Doom3. Gibt es da irgendwelche Tricks? (Gibt es also ne Anleitung oder Tutorial, was auf diese Art von konkreten Problemen eingeht?)
Bitte nur ernst gmeinte hilfreiche Antworten.
-
zu 1) Senden brauchst du nicht in einer Schleife, sondern nur wenn sich Werte ändern und das empfangen (recv) blockiert doch normalerweise ehh asynchron, verbraucht also keine Rechenzeit.
zu 2) Übertrage mal per UDP
zu 3) Ich habe immer Strukturen versendet, wobei der erste Wert immer ein int/enum war, der den Typ angibt. Ein cast auf die passende Struktur "decodiert", und verbraucht keine Laufzeit! Interpolieren musst du schon, sonst wird das bei Action-Spielen nichts.
zu 4) Wieso sollte der Ball rausfliegen? Der Client weiss doch auch, das die Wand da ist und lässt den Ball einfach genau so abprallen.
zu 5) Ja. Die machen das. Auf jeden Fall.
Bye, TGGC (Fakten)
-
Andreas XXL schrieb:
1. Das Spiel, selbst hat nur 1 % Prozessorauslastung, aber mit dem Thread 100%
(Wie umgehe ich das, dass ich permanent abfragen muß, ob Daten angekommen
sind?)indem du events verwendest, oder hast du für den maus und key input auch jeweils nen eigenen thread?
Andreas XXL schrieb:
2. Obwohl dieser Thread einige 1000 mal pro Sekunde durchlaufen wird, schafft
er es komischerweise nur ca 10 Integerwerte pro Sekunde zu empfangen
(Senden deutlich mehr) und das bei ner 100Mbit leitung. (LAN)
Dadurch ruckelt das Spiel natürlich beim Client wie bekloppt.
Wie umgeht man das Problem? Soll der Client sebständig "zwischenwerte
berechnen"?bei so trivialen spielen überträgt man öfters nur die inputevents der tastatur bzw maus. wenn der spieler bei einem client auf <- oder -> klickt oder die taste loslässt, überträgst du das signal, in der zwischenzeit können die programme die , wie du es so schön sagtest, "zwischenwerte berechnen" da ohne inputänderung beide das selbe berechnen sollten... btw. dabei ist es wichtig nen "timestamp" bei jeder nachricht mitzuverschicken und bei den berechnungen zu berücksichtigen.
Andreas XXL schrieb:
3. Für jeden übermittelden Wert muss ich mir von Hand ne Codierung überlegen,
Wie ich die Werte zuordne. z.B Übersetzte ich die Double werte der
Ballposition in Char[x], hänge "Ballx" bzw. "bally" an und sende es dann.
Auf der anderen Seite muß ich dies wieder auseinanderfummeln in
double übersetzen und richtig zuordnen (anhand der Textanhänge).
Gibt es da nen besseren Weg?Man kann sich pro übertragenen wert ein byte mit der typinformation mitschicken. in strings umwandeln und zurück sollte man nicht, das ist suboptimal. einfach z.b. ein byte z.b. 1 für ballX, 2 für bally usw. und dann die daten im anhang daran.
für meinen vorschlag den input zu versenden, würde es dann reichen grundsätzlich immer den keyboardinput weiter zu schicken.Andreas XXL schrieb:
4. Selbst wenn es schnell genug übermittelt wird ist ja ne verzögerung geben
und der Ball wird beim Client kurzzeitig die Spielfeldgrenze
überschreiten. Wie verhindert man sowas.indem du auf allen clients das spiel simulierst. wie schonmal gesagt, nur den input austauschen. das spiel solltest du dabei nur auf dem server als "richtig" simulieren. das heißt, dass auch wenn ein client sieht dass der ball beim gegner "einschlägt", wenn der server es anders simuliert, müssen alle clients die simulation für sich updaten und weiter darstellen was der server als richtig ansieht.
Andreas XXL schrieb:
5. Wenn es schon solche Probleme bei sowas Einfachem gibt, wie machen es dann die Programmieren von Spelen wie WoW, oder Doom3. Gibt es da irgendwelche Tricks? (Gibt es also ne Anleitung oder Tutorial, was auf diese Art von konkreten Problemen eingeht?)
klar gibt es da tricks, und jeder netzwerkprogrammierer hat da seine eigenen, manche schreiben sich dafür die ganze protokollverwaltung über IP selbst. dazu kommen geschickte extrapolations algorithmen und fehlerbereinigungs bzw tolleranz mit denen du dich noch nichtmal befassen mußtest

auf www.gamasutra.com gibt es nen schöne artikel vom netzwerkproblem von xwing vs. tieFighter.[/quote]
-
Danke,
Ich habe immer Strukturen versendet
aber wie versedet man den was Anderes als Text?
Jetzt nicht lachen aber ich habe das immer so gemacht:
senden
float zahl=pong->get_ballpositionx(); char aktuell[10];sprintf(aktuell, "%f", zahl); Sleep(33); send(s,aktuell,strlen(aktuell),0);empfangen:
rc=recv(connectedSocket,buf,256,0); buf[rc]='\0'; stringstream Str; Str << buf; double d; Str >> d; pong->set_ballpositionx(d);Könnt ihr mal nen bischen Code hinschreiben, wie man das richtig macht?
-
struct ballInfo { int MsgType; // muss in jeder struktur ganz oben stehen int x; int y; ballInfo(int x_, int y_) : MsgType(1) { x = x_; y = y_; }; }; ballInfo i; send(&i, sizeof(ballInfo)); // empfangen: int* ptr; ptr = new int[getrecvsize]; recv(ptr); if(ptr[0] == 1) // ballInfo ballInfo* info = (ballInfo)i; /7 dnan hast du wieder dein ballInfo
-
Hallo, irgendwie der code von "Maxi" nicht so ganz.
(Ich benutze MS Visual c++ 6.0)
Ich habe mir jetzt nach langem ausprobieren folgendes zusammengebastelt:#include <winsock2.h> #include <windows.h> #include <stdio.h> int startWinsock(void); struct ballInfo { int MsgType; // muss in jeder struktur ganz oben stehen int x; int y; ballInfo(int x_, int y_) : MsgType(1) { x = x_; y = y_; }; }; ballInfo i(8,2); //////////////SERVER///////////////// int main() { long rc; SOCKET acceptSocket; SOCKET connectedSocket; SOCKADDR_IN addr; char buf[256]; // Winsock starten rc=startWinsock(); // Socket erstellen acceptSocket=socket(AF_INET,SOCK_STREAM,0); // Socket binden memset(&addr,0,sizeof(SOCKADDR_IN)); addr.sin_family=AF_INET; addr.sin_port=htons(12345); addr.sin_addr.s_addr=ADDR_ANY; rc=bind(acceptSocket,(SOCKADDR*)&addr,sizeof(SOCKADDR_IN)); // In den listen Modus rc=listen(acceptSocket,10); // Verbindung annehmen connectedSocket=accept(acceptSocket,NULL,NULL); // Daten austauschen while(rc!=SOCKET_ERROR) { rc=recv(connectedSocket,buf,256,0); buf[rc]='\0'; printf("Client sendet: %s\n",buf); rc=send(connectedSocket,(const char*)&i, sizeof(ballInfo),0); } closesocket(acceptSocket); closesocket(connectedSocket); WSACleanup(); return 0; } int startWinsock(void) { WSADATA wsa; return WSAStartup(MAKEWORD(2,0),&wsa); }#include <winsock2.h> #include <windows.h> #include <stdio.h> //Prototypen int startWinsock(void); struct ballInfo { int MsgType; // muss in jeder struktur ganz oben stehen int x; int y; ballInfo(int x_, int y_) : MsgType(1) { x = x_; y = y_; }; }; ////////////Client int main() { long rc; SOCKET s; SOCKADDR_IN addr; char buf[256]; // Winsock starten rc=startWinsock(); if(rc!=0) { printf("Fehler: startWinsock, fehler code: %d\n",rc); return 1; } else { printf("Winsock gestartet!\n"); } // Socket erstellen s=socket(AF_INET,SOCK_STREAM,0); if(s==INVALID_SOCKET) { printf("Fehler: Der Socket konnte nicht erstellt werden, fehler code: %d\n",WSAGetLastError()); return 1; } else { printf("Socket erstellt!\n"); } // Verbinden memset(&addr,0,sizeof(SOCKADDR_IN)); // zuerst alles auf 0 setzten addr.sin_family=AF_INET; addr.sin_port=htons(12345); // wir verwenden mal port 12345 addr.sin_addr.s_addr=inet_addr("127.0.0.1"); // zielrechner ist unser eigener rc=connect(s,(SOCKADDR*)&addr,sizeof(SOCKADDR)); if(rc==SOCKET_ERROR) { printf("Fehler: connect gescheitert, fehler code: %d\n",WSAGetLastError()); return 1; } else { printf("Verbunden mit 127.0.0.1..\n"); } // Daten austauschen while(rc!=SOCKET_ERROR) { gets(buf); // nur um auf eine Eingabe zu warten... send(s,buf,strlen(buf),0); ballInfo i(5,2); recv (s,( char*)&i,sizeof(ballInfo),0); ballInfo info=(ballInfo)i; printf( "%d\n", i.x ); printf( "%d\n", i.y ); } closesocket(s); WSACleanup(); return 0; } int startWinsock(void) { WSADATA wsa; return WSAStartup(MAKEWORD(2,0),&wsa); }Es scheint so als ob die Werte 2 und 8 tatsächlich vom Server an den Client gesendet werden, denn dieser gibt 8,2 richtig aus.
Jetzt habe ich noch 3 Fragen:
1. Ist da irgend etwas (total) falsch?
2. Bei meiner Version kann ich leider nicht genau bestimmen welches Objekt gesendet wurde, wenn es verschiedene gibt. Wie baue ich das ein?
3. Kann es eigentlich passieren, dass man bei recv(...) nur Teile des gesendeten Objektes erhält (es also zerstückelt ist?) oder das mittlerweile mehrere im Buffer sind?
Wenn ja wie gehe ich mit diesem dann um?
-
zu 1) Ja. Du testest nicht, ob die Daten wirklich ein ballinfo sind. Ausserdem kannst du nur Daten mit 12 Byte Länge empfangen.
zu 2) Indem du anschaust, was bei MsgType steht. Ich habe immer eine Basisstruktur benutzt (von der ich alle Nachrichten ableite), dann kann man sicher in diese casten und Daten die immer übertragen werden sollen (z.b. Typ, Länge) dort rein schreiben.
zu 3) Steht in der Doku zu send und recv. Musst dann halt zusammenstückeln und auseinanderfriemeln.
Bye, TGGC (Fakten)
-
Danke TGGC.
Zu 1. Ist dies 12 Byte so wegen dem dritten Pararmeter von
rc=send(connectedSocket,(const char*)&i, sizeof(ballInfo),0);Was muß ich denn da hinschreiben. Denn selbst wenn ich eine Basisklasse mache und von dieser Ableite, weiß ich doch nicht genau, wie groß das Objekt ist(vorher meine ich) weil ich ja nicht weiß welches abgeleitete Objekt es im Endeffekt ist .Oder verstehe ich da etwas falsch?
zu 2. Super Idee, mache ich jetzt auch so!!!
Zu 3 Reicht es nachzusehen, ob der Buffer genug Daten für ein Objekt zusammen habe und erst dan recv aufrufen?
Wie bestimme ich denn wieviel im Buffer ist ohne diese daten mit recv ausgelesen zu haben?Wenn im Buffer zu viel steht, kann man ja das Objekt auslesen und den Rest danach , oder. Aber auch hier habe ich das selbe Problem wie in 1. Wie kann ich den genauen Typ bestimmen, denn um das Objekt auszulesen, brauche ich ja die Länge. Diese kann ich aber erst bestimmen, wennn ich das Objekt ausgelsen habe und den Typ durch "MsgType" abgelsen habe.
Ich verstehe nicht ganau wie man dieses macht, da jedes für das andere vorrausetzung ist. Also ohne auslesen kein Typ, ohne Laänge aus Typ gewonnen kein auslesen!?