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