|
Registriert seit: 7. Jun 2008 708 Beiträge Delphi 10.2 Tokyo Professional |
#1
Hallo!
Ich bastel gerade am Netzwerkcode für mein kleines Spiel und nutze dafür Indy 10. Hab noch nie was mit Indy gemacht und in den letzten Tagen viel gelesen, viele Demos angeschaut (an dieser Stelle herzlichen Dank an bernhard_LA für seine Demos) und versucht, viel zu verstehen. Und so ganz grob vom Prinzip her funktioniert es schonmal, dass es eine Serveranwendung gibt, wo sich ein Game-Client problemlos verbinden kann (Betonung liegt auf ein). Problematisch wird's, wenn ein zweiter Client hinzukommt und damit die Kommunikation etwas komplexer wird. Mir gehts nicht um das grundlegende theoretische Prinzip einer solchen Kommunikation, sondern um den technischen Aspekt, besonders in Zusammenhang mit Indy 10. Zum Beispiel: Jeder Client stellt sich nach der Connection und dem Login beim Server vor, wie sich's für Gentlemen gehört: "Ich bin Charakter Müller, habe das Sprite A, besitze 100 HP und stehe an Position X, Y". Damit weiß der Server bescheid. Er prüft nun in regelmäßigen Abständen die Distanz zwischen den Clients. Wenn ein Client in Sichtweite des anderen kommt, wird dieser erstmal in eine Liste aufgenommen, wenn er noch nicht drin ist, und dabei wird eine Nachricht an den sichtenden Client vorbereitet "Hey, hier kommt Client X in Sicht" und in eine Nachrichtenschleife gesteckt. Das Problem, was diesem konkreten Fall deutlich wird: Sobald der 2. Client sich beim Server vorgestellt hat, wird er von der Sichtbarkeits-Routine erfasst, es wird eine Nachricht erstellt und diese wird sofort gesendet. Der 2. GameClient erhält nun diese "Hey, hier kommt Client X in Sicht" noch bevor er überhaupt mit der Anmeldung richtig durch war, was ihn gegen die Wand fahren lässt, weil er damit nicht rechnet. Wie oben schon gesagt: Was hier deutlich wird und auch an anderen Stellen auftreten kann, dass sich Nachrichten überlappen können. Dass der Server eine Mitteilung zu machen hat und damit eine laufende Kommunikation zw. Client und Server unterbricht, bzw. stört. Wie kann ich das verhindern? Der Server darf nichts rausschicken, solange wie eine Clientanfrage noch nicht beantwortet ist. Mit "Prüfvariablen" kann ich scheinbar wegen der Threadsicherheit nicht arbeiten. Zumindest habe ich das schon versucht und es hat nicht geklappt. Jeder Klient hat eine Variable "IsWaitingForResponse" bekommen. Die wurde am Anfang von "OnExecute" auf TRUE gesetzt und am Ende wieder auf FALSE. In der Nachrichtenschleife wurde diese dann geprüft, mit der Feststellung im Debugger, dass sie irgendwie ständig auf TRUE steht, obwohl keine Kommunikation stattfand ... ![]() Message-Handler
Delphi-Quellcode:
OnConnect/OnDisconnect/OnExecute-Behandlung
unit UMessageHandler;
interface uses Classes, IdTask, IdContext, IdTCPServer, IdSchedulerOfThreadDefault, IdYarn, IdThread, UServerTypes; type TMessageItem = record Id: Integer; Delete: Boolean; Context: TIdContext; Data: TNetworkDataRecord; end; PMessageItem = ^TMessageItem; TMessageHandler = class(TIdThread) constructor Create; destructor Destroy; override; protected FMessageList: TList; procedure Run; override; public function AddMessage(AMessage: TMessageItem): Integer; procedure RemoveMessage(Id: Integer); end; var MessageHandler: TMessageHandler; implementation uses SysUtils, Windows, Forms, IdSync, UEventLogHandler, UServerClasses, UServerFunctions; procedure ClearMessageList(AList: TList); var i: Integer; Item: PMessageItem; begin for i := 0 to AList.Count - 1 do begin Item := PMessageItem(AList[i]); Dispose(Item); end; AList.Clear; end; { TMessageHandler } function TMessageHandler.AddMessage(AMessage: TMessageItem): Integer; var Item: PMessageItem; begin New(Item); Item^ := AMessage; Item^.Delete := FALSE; Item^.Id := FMessageList.Add(Item); Result := Item^.Id; end; constructor TMessageHandler.Create; begin inherited Create; FMessageList := TList.Create; end; destructor TMessageHandler.Destroy; begin ClearMessageList(FMessageList); FreeAndNil(FMessageList); inherited; end; procedure TMessageHandler.Run; var Buffer: TBytes; MsgIndex: Integer; Item: PMessageItem; Client: TClientContext; begin MsgIndex := 0; while not Terminated do begin if MsgIndex > FMessageList.Count then MsgIndex := 0; if (FMessageList.Count = 0) then begin Sleep(1); Continue; end; Item := PMessageItem(FMessageList[MsgIndex]); if Item^.Delete then begin RemoveMessage(Item^.Id); MsgIndex := 0; Continue; end; Client := TClientContext(Item^.Context.Data); if not Assigned(Client) then Continue; Buffer := DataRecordToByteArray(Item^.Data); try if (Item^.Context.Connection.IOHandler.Connected) and not Client.WaitingForResponse then // Client.WaitingForResponse ergab immer TRUE, obwohl es keinen Sinn machte begin if not SendBuffer(Item^.Context, Buffer) then TIdNotify.NotifyMethod(EventLogHandler.SendBufferError) else Item^.Delete := TRUE; end; finally Finalize(Buffer); end; //Inc(MsgIndex); end; end; procedure TMessageHandler.RemoveMessage(Id: Integer); var i: Integer; Item: PMessageItem; begin for i := 0 to FMessageList.Count - 1 do begin Item := PMessageItem(FMessageList[i]); if Item^.Id = Id then begin FMessageList.Delete(i); Dispose(Item); Exit; end; end; end; end.
Delphi-Quellcode:
TClientContext.ProcessData
procedure TMyGameServer.OnClientConnect(AContext: TIdContext);
var Context: TClientContext; begin FStatus := ssClientConnected; Context := TClientContext.Create(AContext); Context.ClientsList := FClientsList; AContext.Data := Context; AddClient(AContext); end; procedure TMyGameServer.OnClientDisconnect(AContext: TIdContext); begin FStatus := ssClientDisconnected; if Assigned(AContext.Data) then begin if AContext.Data is TClientContext then begin RemoveClient(AContext); TClientContext(AContext.Data).Free; end; AContext.Data := nil; end; end; procedure TMyGameServer.OnServerExecute(AContext: TIdContext); var ClientContext: TClientContext; begin AContext.Connection.IOHandler.ReadTimeout := 10000; FStatus := ssProcessingData; try ClientContext := TClientContext(AContext.Data); except ClientContext := nil; end; if Assigned(ClientContext) then try if not ClientContext.ProcessData(AContext) then begin FStatus := ssError; TIdNotify.NotifyMethod(EventLogHandler.GetBufferError); end; FStatus := ssIdle; except FStatus := ssError; TIdNotify.NotifyMethod(EventLogHandler.ProcessError); end; end;
Delphi-Quellcode:
Ich hoffe, es ist nicht zu viel und nicht zu wenig Code und dass ihr durchblickt.
unit UServerClasses;
interface ... type TClientContext = class(TIdThreadSafe) ... public function ProcessData(AContext: TIdContext): Boolean; overload; end; implementation ... function TClientContext.ProcessData(AContext: TIdContext): Boolean; var Buffer: TBytes; begin FIsWaitingForResponse := FALSE; // TRUE ergibt, dass der Wert bei Prüfung in Nachrichtenschleife immer auf TRUE steht, obwohl er unten wieder auf FALSE gesetzt wird :( Result := FALSE; try if not ReceiveBuffer(AContext, Buffer) then Exit; if Length(Buffer) = 0 then Exit; Lock; try try FData := ByteArrayToDataRecord(Buffer); finally Finalize(Buffer); end; ProcessData; finally Unlock; end; Buffer := DataRecordToByteArray(ResponseData); try if not SendBuffer(AContext, Buffer) then Exit; finally Finalize(Buffer); end; Result := TRUE; finally FIsWaitingForResponse := FALSE; end; end; procedure TClientContext.UpdateClients; var i: Integer; Context: TIdContext; ClientContext: TClientContext; DistanceBetween: Double; begin for i := 0 to FClientsList.Count - 1 do begin if not FCanUpdateClients then Exit; Context := TIdContext(FClientsList[i]); if Context = FContext then Continue; ClientContext := (Context.Data as TClientContext); DistanceBetween := GetDistance( FCharacterPosition.X, FCharacterPosition.Y, ClientContext.PositionData.X, ClientContext.PositionData.Y); // Kommt ein Character in den Sichtbereich? if DistanceBetween <= CLIENT_VISIBILITY_DISTANCE then begin if not HasNearClient(Context) and IsValidClient(ClientContext) then AddNearClient(Context); end else begin // Verlässt ein sichtbarer Character den Sichtbereich? if HasNearClient(Context) then begin if DistanceBetween > CLIENT_VISIBILITY_DISTANCE then RemoveNearClient(Context); end; end; end; end; procedure TClientContext.AddNearClient(AContext: TIdContext); begin FNearClientsList.Add(AContext); HandleNearClient(clAppear, AContext); end; procedure TClientContext.HandleNearClient(ACommand: TCommandsList; AContext: TIdContext); var Data: TNetworkDataRecord; CharacterData: TIntroductionRecord; Buffer: TBytes; i: Integer; begin // ... Broadcast(Data, FNearClientsList); end; procedure TClientContext.Broadcast(AData: TNetworkDataRecord; AClientsList: TList); var i: Integer; Msg: TMessageItem; Context: TIdContext; List: TList; begin List := AClientsList; for i := 0 to List.Count - 1 do begin Context := TIdContext(List[i]); if Context = FContext then Continue; Msg.Context := Context; Msg.Data := AData; MessageHandler.AddMessage(Msg); end; end; end. Bin für jede Anregung und Hilfe dankbar. ![]() |
![]() |
Themen-Optionen | Thema durchsuchen |
Ansicht | |
ForumregelnEs ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.
BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus. Trackbacks are an
Pingbacks are an
Refbacks are aus
|
|
Nützliche Links |
Heutige Beiträge |
Sitemap |
Suchen |
Code-Library |
Wer ist online |
Alle Foren als gelesen markieren |
LinkBack |
![]() |
![]() |