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 Methode dispatch . Lustigerweise wird dispatch unterbrochen und das Event OnClientDisconnect ausgelöst und bearbeitet. Im OnClientDisconnect Handler wird jedoch der Lesepuffer der Socket aus der map entfernt und ist damit ungültig. Nach der Ereignisbehandlung kehrt die CPU in die dispatch 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 von dispatch und OnClientDisconnected passiert im gleichen Thread, trotzdem wird dispatch kurz von OnClientDisconnected 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 und OnClientDisconnected passiert im gleichen Thread, trotzdem wird dispatch kurz von OnClientDisconnected 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.


Log in to reply