TServerSocket/TClientSocket Timing Problem
-
Hallo,
ich habe das Problem, dass das OnClientDisconnected Event mitten in der Telegrammbehandlung ausgelöst wird und mir den Boden (bzw. den Lesepuffer) unter den Füßen wegzieht.
Hier die relevanten Ausschnitte aus meinem Code (nicht 1:1, diverse Checks fehlen, aber prinzipiell stimmt´s).class TMyServer { // Socket zum Annehmen von Verbindungen TServerSocket* ServerSocket_; // map mit Lesepuffern für jeden Client Socket map<unsigned int, vector<char> > SocketBuffers_; public: void OnClientConnect( TObject* Sender, TCustomWinSocket* Socket ) { // Adresse des Socket als ID benutzen unsigned int SocketID = reinterpret_cast<unsigned int>( Socket ); // vector<char> als Empfangspuffer für den Socket bereitstellen SocketBuffers_[SocketID] = vector<char>(); } void OnClientDisconnect( TObject* Sender, TCustomWinSocket* Socket ) { // Adresse des Socket als ID benutzen unsigned int SocketID = reinterpret_cast<unsigned int>( Socket ); // Socket wurde geschlossen, Empfangspuffer aus map entfernen SocketBuffers_.erase( SocketID ); } void OnClientRead( TObject* Sender, TCustomWinSocket* Socket ) { // Adresse des Socket als ID benutzen unsigned int SocketID = reinterpret_cast<unsigned int>( Socket ); // zugehörigen Lesepuffer bestimmen vector<char>& Buffer = SocketBuffers_[SocketID]; // Daten lesen und verteilen read( Socket, Buffer ); dispatch( Buffer ); } void read( TCustomWinSocket* Socket, vector<char>& Buffer ) { // aktuelle Größe des Lesepuffers und Anzahl bereitstehender Bytes bestimmen unsigned int Size = Buffer.size(); unsigned int Waiting = Socket->ReceiveLength(); // Puffergröße anpassen, damit anstehende Daten gelesen werden können Buffer.resize( Size + Waiting ); // Daten lesen Socket->ReceiveBuf( &Buffer[Size], Waiting ); } void dispatch( vector<char>& Buffer ) { while( true ) { // stehen genügend Daten für den Telegrammkopf bereit? if( Buffer.size() < TELEGRAM_HEADER_SIZE ) return; TelegramHeader* Header = reinterpret_cast<TelegramHeader*>( &Buffer[0] ); // stehen genügend Daten für das komplette Telegramm bereit? if( Buffer.size() < Header->TelegramSize ) return; // Telegrammdaten aus Puffer entfernen Buffer.erase( Buffer.begin(), Buffer.begin() + Header->TelegramSize ); } } };
Jetzt habe ich ein Timing Problem:
Wenn der Server die Daten eines Clients bearbeitet und in gerade diesem Moment der Client die Verbindung trennt steckt der Server in der Methodedispatch
. Lustigerweise wirddispatch
unterbrochen und das EventOnClientDisconnect
ausgelöst und bearbeitet. ImOnClientDisconnect
Handler wird jedoch der Lesepuffer der Socket aus der map entfernt und ist damit ungültig. Nach der Ereignisbehandlung kehrt die CPU in diedispatch
Methode zurück und erzeugt da UB, weil der Buffer nicht mehr existiert. Ich habe da jetzt einiges debuggt und festgestellt, dass kein Threadwechsel stattfindet, ich weiß nicht wie die VCL das hinbekommt. Die Ausführung vondispatch
undOnClientDisconnected
passiert im gleichen Thread, trotzdem wirddispatch
kurz vonOnClientDisconnected
unterbrochen.
Ich hätte ja erwartet, dass das Disconnect Event erst behandelt wird, wenn der Server das OnClientRead Event vollständig abgearbeitet hat, aber das ist wohl nicht der Fall.
Ich könnte mir natürlich den Status jeder ClientSocket merken (z.B. IsDispatching und IsClosed) und in einem Timer prüfen, ob der Socket tot ist, aber das ist Gefrickel.
-
DocShoe schrieb:
Ich habe da jetzt einiges debuggt und festgestellt, dass kein Threadwechsel stattfindet, ich weiß nicht wie die VCL das hinbekommt. Die Ausführung von
dispatch
undOnClientDisconnected
passiert im gleichen Thread, trotzdem wirddispatch
kurz vonOnClientDisconnected
unterbrochen.Ist ja verrückt. Was sagt denn der Call-Stack? Speziell dann, wenn du mit der Debug-Version der VCL linkst (dann kannst du in VCL-Code hineinsteppen)?
DocShoe schrieb:
Ich könnte mir natürlich den Status jeder ClientSocket merken (z.B. IsDispatching und IsClosed) und in einem Timer prüfen, ob der Socket tot ist, aber das ist Gefrickel.
Ja. Allgemein habe ich mich nach einigen Problemen damit zum Eindruck durchgerungen, daß TClientSocket/TServerSocket allgemein Gefrickel sind, und meine Probleme stattdessen mit Indy gelöst. Es ist allerdings schon geraume Zeit (d.h., ~3 Jahre) her, daß ich mich mit Netwerkprogrammierung beschäftigt hätte.