Spiel übers Internet synchron halten
-
Eines ist mir noch an deinem Code aufgefallen:
recv(data_socket , data_buffer , 500 , 0); send(data_socket , data_buffer , 500 , 0);
Dies macht überhaupt keinen Sinn, daß du die empfangenen Daten gleich wieder zurückschickst!
Jeder Spieler (egal ob Client oder Server) muß seine eigenen Aktionen verschicken und die des anderen Spielers empfangen.
-
Sozusagen kommen dann alle auf einmal an, aber in der richtigen Reihenfolge, d.h. zweites Paket fängt direkt nach dem ersten an. Du musst die Nachrichten so aufbauen, dass du weißt, wo die erste aufhört und die nächste anfängt.
-
Ich empfehle Dir asynchrone Sockets, da wird sich sozusagen intern um die parallele Verarbeitung gekümmert.
Hier zwei Links, schau Dir die mal an:
http://johnnie.jerrata.com/winsocktutorial/
http://www.gamedev.net/reference/articles/article1297.aspViel Erfolg noch!
-
Th69 schrieb:
Eines ist mir noch an deinem Code aufgefallen:
recv(data_socket , data_buffer , 500 , 0); send(data_socket , data_buffer , 500 , 0);
Dies macht überhaupt keinen Sinn, daß du die empfangenen Daten gleich wieder zurückschickst!
Ja, mir fiel vorhin schon auf, dass das wenig Sinn macht. In meinem Programm hatte ich verschiedene Buffer dafür, diese direkte Aneinanderreihung habe ich gemacht, um die Block-Funktion auszunutzen. Ich dachte, der Fehler fällt vielleicht niemandem auf, es handelt sich ja noch immer nur um "Pseudocode" ;).
Nanyuki schrieb:
Sozusagen kommen dann alle auf einmal an, aber in der richtigen Reihenfolge, d.h. zweites Paket fängt direkt nach dem ersten an. Du musst die Nachrichten so aufbauen, dass du weißt, wo die erste aufhört und die nächste anfängt.
Ok, das bringt mich zur nächsten Frage (schonmal danke für die erste Klärung).
//Server send(s , char_buffer , 10 , 0); _char_buffer_manipulieren(char_buffer); send(s , char_buffer , 10 , 0); _char_buffer_manipulieren(char_buffer); send(s , char_buffer , 10 , 0); _char_buffer_manipulieren(char_buffer); send(s , char_buffer , 10 , 0); //soweit, so gut //Client recv(s , neuer_char_buffer , 10 , 0); //Auf die Nachrichtenlänge achten
Was macht der Client hier? Holt er sich genau die erstern 10 Byte aus dem "Verbindungs-Puffer" und kann sich den Rest später holen? Oder "verfällt" der Rest? Denn in diesem Moment müsste ich ja zwecks Synchronität eher das letzte als das erste Datenpaket annehmen und verarbeiten. Vermutlich könnte man das umgehen:
//Client memset(neuer_char_buffer , 127 , 10001); recv(s , neuer_char_buffer , 10001 , 0); for(i = 0 ; neuer_char_buffer[i] != 127 ; i ++); //wichtige Nachricht beginnt jetzt an neuer_char_buffer[i - 10]
Aber vielleicht kann man das ja umgehen.
iop schrieb:
Ich empfehle Dir asynchrone Sockets, da wird sich sozusagen intern um die parallele Verarbeitung gekümmert.
Hier zwei Links, schau Dir die mal an:
http://johnnie.jerrata.com/winsocktutorial/
http://www.gamedev.net/reference/articles/article1297.aspViel Erfolg noch!
Vielen Dank für die Links, bin schon am Lesen.
Es kommt mir so vor, als würde ich von der einen Baustelle in die nächste stolpern...
-
Was macht der Client hier? Holt er sich genau die erstern 10 Byte aus dem "Verbindungs-Puffer" und kann sich den Rest später holen? Oder "verfällt" der Rest? Denn in diesem Moment müsste ich ja zwecks Synchronität eher das letzte als das erste Datenpaket annehmen und verarbeiten.
Der Rest verfällt nicht, sondern wartet, bis du ihn irgendwann mal abholst.
Warum das letzte zuerst? Die Nachrichten sollten ausnahmslos in der Reihenfolge abgearbeitet werden, in der sie gesendet wurden, sonst bringt das nur Probleme. Wenn du eine andere Reihenfolge benötigst, dann sende sie eben in dieser.
Anstatt deine Nachrichten mit einem bestimmten Zeichen abzuschließen, bietet es sich vielleicht eher an, am Anfang jeder Nachricht deren Größe mit 1-4 Bytes anzugeben, sonst kannst du nicht alle Bytewerte in deinen Nachrichten verwenden, was gleichzeitig ausschließt, dass du deine Nachrichten binär kodieren kannst, was wiederum bedeuten würde, dass du jede Menge Bandbreite und CPU-Zeit aus dem Fenster werfen musst.
-
Ich habe jetzt eine ganze Weile mit select() und WsaAsyncselect() herumprobiert. Ersteres funktionierte zwar, allerdings war noch immer alles etwas ruckelig. Bei letzterem scheine ich entweder nicht an die Nachricht zu kommen oder es meldet mir nicht, wann ein Socket Zeit zur Arbeit hat.
Hat vielleicht jemand ein Code-Beispiel für den Umgang mit der Nachricht? MSDN und die von iop geposteten Links habe ich schon hoch- und runtergewälzt, aber irgendwie hat es nicht geholfen.
-
Was funktioniert denn genau nicht? Welche Fehlermeldung bekommst Du? Fragst Du generell alle Fehler ab?
Zeig doch mal Deinen Code, also das Aufsetzen der Sockets, die Windows Nachrichtenschleife und das Senden und Empfangen.
-
EDIT: Ich habe den Beitrag mal überarbeitet, da ich mittlerweile scheinbar die richtigen Nachrichten Abfange. Den Quelltext lasse ich hier, da sicher noch ein paar Dinge zu klären sein werden:
WNDCLASS wc; HWND hWnd; HDC hDC; HGLRC hRC; MSG msg; SOCKET data_socket; SOCKET accept_socket; WSADATA wsa; //... //Netzwerk-Initialisierung if(server == 0) //Client { SOCKADDR_IN saddr; if(WSAStartup(MAKEWORD(2 , 0) , & wsa)) quit = 1; data_socket = socket(AF_INET , SOCK_STREAM , IPPROTO_TCP); if(data_socket==INVALID_SOCKET) quit = 1; saddr.sin_family = AF_INET; saddr.sin_port = htons(port); saddr.sin_addr.s_addr = inet_addr(server_ip); saddr.sin_zero[0] = '\0'; connect(data_socket , (SOCKADDR*)&saddr , sizeof(SOCKADDR)); if(WSAAsyncSelect(data_socket , hWnd , SOCKET_EVENT , FD_READ | FD_WRITE)) quit = 1; } if(server == 1) //Server { SOCKADDR_IN saddr; if(WSAStartup(MAKEWORD(2 , 0) , & wsa)) quit = 1; accept_socket = socket(AF_INET , SOCK_STREAM , IPPROTO_TCP); if(accept_socket==INVALID_SOCKET) quit = 1; saddr.sin_family = AF_INET; saddr.sin_port = htons(port); saddr.sin_addr.s_addr = ADDR_ANY; saddr.sin_zero[0] = '\0'; bind(accept_socket , (SOCKADDR*)&saddr , sizeof(SOCKADDR_IN)); listen(accept_socket , 2); data_socket = accept(accept_socket , NULL , NULL); if(data_socket==INVALID_SOCKET) quit = 1; if(WSAAsyncSelect(data_socket , hWnd , SOCKET_EVENT , FD_READ)) quit = 1; }
Soweit zu den Sockets, evt. fehlt da jetzt die Erstellung einiger Variablen, aber das sollte wohl keine Rolle spielen.
Hier Teile meiner Programmschleife:
while (!quit) { round_beg = _win_exact_time_64(); if (PeekMessage(& msg , NULL , 0 , 0 , PM_REMOVE)) { if(msg.message == WM_QUIT) quit = 1; if(msg.message == SOCKET_EVENT) //Hier meine Abfrage über die Lesbarkeit des Sockets data_received = 1; else { TranslateMessage(& msg); DispatchMessage(& msg); } } if(server == 0) { player_action(player2x , player2y , & ctrl_buf_own , 0 , 0); if(data_received && t_buffer_send_recv + 0.05 < _win_exact_time_64()) { printf("Client in action\n"); cp.p.player2x = player2x[0]; cp.p.player2y = player2y[0]; cp.p.ctrl_buffer = ctrl_buf_own; t_buffer_send_recv = _win_exact_time_64(); send(data_socket , cp.text , sizeof(struct cdata) , 0); recv(data_socket , sp.text , sizeof(struct sdata) , 0); ctrl_buf_other = 0; data_received = 0; _extr_server_package(ballx , bally , ballv , player1x , player1y , player2x , player2y , & ctrl_buf_other); _calculate_pos(player1x , player1y , player2x , player2y , ballx , bally); traffic += sizeof(struct sdata) + sizeof(struct cdata); } else { if(!ball_redrawn) ball_repositioning(ballx , bally , ballv , player1x , player1y , player2x , player2y); player_action(player1x , player1y , & ctrl_buf_other , 1 , 1); } _control_sound(); _correct_points(pointsP1_i , pointsP2_i , pointsP1 , pointsP2); redraw_scenario(player1x , player1y , player2x , player2y , ballx , bally , ballv , 0 , hDC); }
Dieser Teil enthält das Senden und Empfangen (es wird auf die Variable "data_received" geprüft.
Ich muss erstmal ein wenig weitertesten, bevor neue Fragen kommen ;). Vielen Dank auf jeden Fall für die bisher geleistete Hilfe :).
-
Ich habe jetzt noch einige Zeit investiert und das Spiel scheint mehr oder minder gut über das Internet zu laufen (ich sende aktuell alle 30ms ein Datenpaket vom Server zum Clienten und umgekehrt).
Realisiert habe ich das ganze mit einer Kombination aus select() und WSAAsyncSelect(), damit bin ich ganz zufrieden.Nun mal ein etwas neueres Problem, vielleicht kann man dagegen etwas tun:
Momentan läuft das Spiel im Prinzip komplett auf dem Server, dieser sendet die Spieldaten an den Clienten, der "entscheidet" sich für eine Reaktion und sendet diese zurück. Daraufhin baut der Server die Clientenaktion in seinen Ablauf ein.
Das Problem an dieser Vorgehensweise ist, dass mir effektiv der doppelte Ping im Weg steht (sowie evt. ein Aufschlag wegen meiner Pakete, die nur alle 30ms gesendet werden).
Gibt es hierfür ein effizienteres Verfahren? Das Spiel ist relativ zeitkritisch und eine Verzögerung von 100ms (die aktuell ja durchaus entstehen kann) ist deutlich zu merken.
-