Einzelnen Beitrag anzeigen

Benutzerbild von Zacherl
Zacherl

Registriert seit: 3. Sep 2004
4.629 Beiträge
 
Delphi 10.2 Tokyo Starter
 
#1

Konzept: Netzwerkprotokoll

  Alt 18. Sep 2012, 18:55
Hallo zusammen,

ich habe bereits schon einige Entwürfe für ein (auf TCP aufgesetztes) Netzwerkprotokoll angefertigt, bin aber überzeugt, dass noch einiges verbessert werden kann.
Hat jemand von euch ein paar gute Konzeptvorschläge für mich? Bin dankbar für alles!

Die Hauptfunktion des Protokolls soll es sein, multiple Daten(transfers) über ein einziges Socket ablaufen zu lassen. Beispielsweise 2 Dateiübertragungen und 3 Steuerbefehle. Hierbei soll mindestens eine einfache Priorisierung möglich sein. Sprich: Auch wenn die maximale Anzahl an gleichzeitigen Transfers erreicht ist, sollen die Steuerbefehle trotzdem noch zusätzlich gesendet werden.

Folgende Grundfunktionen möchte ich zusätzlich unterstützen:
Blockweise Übertragung
Verschlüsselung & Kompression einzelner Blöcke
Suspend, Resume und Cancel einer Übertragung
Meta Daten, die zusammen mit dem Info Header sofort geschickt werden

Für meine bisherige Implementation verwende ich folgende Header:
Delphi-Quellcode:
type
  PdxIDTPMainHeader = ^TdxIDTPMainHeader;
  TdxIDTPMainHeader = packed record
    TransferID: Word; // ID der Übertragung
    PacketSize: Word; // Größe des aktuellen Datenblocks
    Flags: Byte; // optionale Flags
  end;

  TdxIDTPInfolHeader = packed record
    MetaSize: Word; // Größe der Metadaten
    DataSize: UInt64; // Größe der gesamt zu übertragenden Daten
    BlockSize: TdxIDTPBlockSize; // Info: angepeilte Einzelblockgröße
    Priority: Boolean; // Info: priorisierte Übertragung
    Encrypted: Boolean; // Info: verschlüsselte Übertragung
    Compressed: Boolean; // Info: komprimierte Übertragung
  end;
Auf Senderseite läuft ein Thread, welcher vorerst inaktiv wartet, bis mindestens eine Übertragung initialisiert wird. Ist dies der Fall, geht der Thread die komplette Liste mit Transfer Objekten durch und sendet in den vorgegebenen Parametern jeweils den nächsten Datenblock. Diese Schleife wird wiederholt, bis keine ausstehende Übertragung mehr vorhanden ist.

Delphi-Quellcode:
procedure TdxIDTPSendThread.Execute;
var
  List: TList;
  ListCopy: array of TdxIDTPOTransfer;
  Transfer: TdxIDTPOTransfer;
  I, NormalCount: Integer;
begin
  while (not Terminated) do
  begin
    // Transfer Objekte aus der gesicherten Liste kopieren. Von außerhalb
    // können danach zwar Übertragungen hinzugefügt oder deren Reihenfolge
    // geändert werden, was aber keinen Einfluss auf den aktuellen Durchlauf
    // hat.
    // ACHTUNG: Niemals Transfer Objekte außerhalb dieses Threads manuell
    // freigeben!
    List := FIOHandler.OutgoingTransfers.LockList;
    try
      // Abgeschlossene Übertragungen entfernen
      for I := List.Count -1 downto 0 do
      begin
        if (TdxIDTPOTransfer(List.Items[I]).TransferState = tsFinished) then
        begin
          TdxIDTPOTransfer(List.Items[I]).Free;
          List.Delete(I);
        end;
      end;
      // Kopie der Liste anfertigen
      // TODO: Hier suspendierte Transfers nicht kopieren!
      // Ausnahme: PendingStatusUpdate = true oder HeaderSent = false
      SetLength(ListCopy, List.Count);
      for I := 0 to List.Count - 1 do
      begin
        ListCopy[I] := TdxIDTPOTransfer(List.Items[I]);
      end;
    finally
      FIOHandler.OutgoingTransfers.UnlockList;
    end;
    // Thread suspendieren, wenn keine Transfers aktiv sind
    if (Length(ListCopy) = 0) then
    begin
      ResetEvent(FWaitEvent);
      WaitForSingleObject(FWaitEvent, INFINITE);
    end;
    if Terminated then
    begin
      Break;
    end;
    // Transferliste abarbeiten
    NormalCount := 0;
    for I := 0 to High(ListCopy) do
    begin
      Transfer := ListCopy[I];
      // Maximale gleichzeitige Transfer Anzahl erreicht.
      // Transfer überspringen, wenn es kein priorisierter Transfer ist,
      // der Info Header schon gesendet wurde und kein Update
      // des Transfer Status gesendet werden muss
      if ((NormalCount >= FIOHandler.OutgoingTransferCountLimit) and
        (not Transfer.Priority)) and (Transfer.HeaderSent) and
        (not Transfer.PendingStatusUpdate) then
        Continue;
      // Transfer ist inaktiv
      if (Transfer.TransferState = tsSuspended) and
        (not Transfer.PendingStatusUpdate) then
        Continue;
      // Transfer ist abgeschlossen
      if (Transfer.TransferState = tsFinished) then
        Continue;
      // Transferzahl erhöhen
      if (not Transfer.Priority) then
      begin
        Inc(NormalCount);
      end;
      // anstehende Daten senden
      Transfer.SendNextPacketData;
    end;
    Sleep(1);
  end;
end;
Hier ist auch schon die erste Sache, die mich etwas stört: Der Thread verbraucht, durch die Schleife bedingt, relativ viel CPU Zeit. Das Sleep(1) schafft schon etwas Abhilfe, verringert natürlich aber auch deutlich die Übertragungsgeschwindigkeit.

Vor jeder Art von Daten wird ein TdxIDTPMainHeader gesendet, welcher die ID der Übertragung, die Größe des Aktuellen Blocks und weitere Flags beinhaltet. Beim Start eines Transfers enthält der erste Block den TdxIDTPInfoHeader. Dort enthalten sind Informationen, wie beispielsweise die Gesamtgröße der zu übertragenden Daten. Sind Meta Informationen angegeben, so werden auch diese direkt beim Start des Transfers an den Empfänger geschickt. Damit ist die Initialisierung abgeschlossen.
Jetzt wird je nach eingestellter Priorisierung und maximaler Transfer Anzahl jeweils der nächste Datenblock gesendet, bis alle Übertragungen abgeschlossen sind.

Die Zuordnung auf Empfängerseite erfolgt über die TransferID. Hier habe ich momentan ein statisches Array mit 1024 * 64 Elementen, in dem ich dann jeweils der Transfer ID entsprechend ein Objekt anlege, welches für das Sammeln der Daten zuständig ist.
Hier ist die zweite unschöne Sache. "Theoretisch" wäre es möglich, dass hier eine Art Überlauf stattfindet, wenn mehr als 2^16 Übertragungen gleichzeitig aktiv sind (oder zumindest in der Liste). Hier könnte ich zwar das Transfer ID Feld auf ein DWord erweitern. Das hätte aber zur Folge, dass bei jedem Datenpaket 2 zusätzliche Bytes gesendet werden und ich außerdem auf der Empfängerseite auf ein dynamisches Array umsteigen müsste (was von der Performance her sehr viel langsamer durchsucht und verwaltet werden kann).

Das dritte Problem liegt in der Verschlüsselung & Kompression begründet. Sind hier beispielsweise die Schlüssel unterschiedlich, bekommt der Decompressor Daten, mit denen er nichts anfangen kann, was zu einer Exception führt. Meine Frage ist nun, wie ich diese Exception am besten signalisieren soll? Über ein Event vielleicht? Außerdem müsste der Senderseite im Optimalfall gesagt werden, dass der Transfer abzubrechen ist.

Ich hoffe ihr habt ein paar nützliche Ideen für mich. Das Protokoll werde ich selbstverständlich hier zur Verfügung stellen, sobald alles zu meiner Zufriedenheit funktioniert.

Viele Grüße
Zacherl
Projekte:
- GitHub (Profil, zyantific)
- zYan Disassembler Engine ( Zydis Online, Zydis GitHub)
  Mit Zitat antworten Zitat