AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Programmierung allgemein Netzwerke Delphi Stream über TCP - Blockaufteilung ?
Thema durchsuchen
Ansicht
Themen-Optionen

Stream über TCP - Blockaufteilung ?

Ein Thema von Stefanie · begonnen am 3. Dez 2009 · letzter Beitrag vom 8. Dez 2009
Antwort Antwort
Seite 2 von 2     12   
Benutzerbild von Mavarik
Mavarik

Registriert seit: 9. Feb 2006
Ort: Stolberg (Rhld)
4.130 Beiträge
 
Delphi 10.3 Rio
 
#11

Re: Stream über TCP - Blockaufteilung ?

  Alt 3. Dez 2009, 13:46
Du musst Dir eine Schleife bauen...

Beispiel..

Ich will 10289327 Bytes übertragen...

Dann sende als erstes Deine Große als Longint/DWord...

und dann am Zielrechner

While no error und size < DeineGröße do
Daten lesen und anfügen

if Daten gelesen = DeineGröße dann alles OK

Mavarik
  Mit Zitat antworten Zitat
Stefanie

Registriert seit: 12. Aug 2004
21 Beiträge
 
#12

Re: Stream über TCP - Blockaufteilung ?

  Alt 3. Dez 2009, 14:15
Ja Mavarik,

so muss man es machen bei großen Streams. Bei kleinen Streams die zu einem Block zusammengefasst werden bleibt scheinbar auch nichts anderes übrig als am Anfang des Streams die Größe zu übertragen. Beim Empfänger dann das erste Byte des Blocks auslesen und wenn der Block größer als meine Streamgröße einfach den Rest in einen weiteren neuen Stream schreiben. Nur so kann ich es mir momentan vorstellen. Aber das wollte ich vermeiden. Mir wäre es lieber, wenn es eine Möglichkeit beim Senden gäbe evtl. die Fragmentierung(die ja in meinen Fall das Zusammenfassen kleiner Streams zu einem Block scheinbar auch ünernimmt) auszuschalten. Einfach, dass ich einen Stream mit z.B. 10 Bytes sende und nur genau das wird als ein Block übertragen, nichts Fragmentiert bzw. zusammengefasst.


Zitat:
die "Blockgröße" wird durch die Maximal Transfer Unit (MTU-Size) bestimmt.
Diese kann auf dem Interface (tcpDoctor) eingestellt werden.
Wenn man die Blockgröße verändern kann, kann man evtl. in der "Maximal Transfer Unit" die Fragmentierung kpl. deaktivieren ?
  Mit Zitat antworten Zitat
Benutzerbild von sirius
sirius

Registriert seit: 3. Jan 2007
Ort: Dresden
3.443 Beiträge
 
Delphi 7 Enterprise
 
#13

Re: Stream über TCP - Blockaufteilung ?

  Alt 3. Dez 2009, 15:54
Was Maverik meinte (und worauf die Luckie stoßen wollte): Du musst dir ein eigenes Protokoll überlegen (auf Anwendungsebene). So etwas wie Http, FTP o.ä.
Das kann man zum Beispiel machen, indem man die Größe am Anfang jeder NAchricht sendet (wie maverik vorschlug), oder indem man ein bestimmtes Zeichen/Zeichenkette (was sonnst nicht benötigt wird) als Signal für das ende einer Nachricht sieht (wie bei http).
Und mit dieser Information musst du beim Empfänger deine Nachrichten trennen bzw. zusammenfügen.

Edit: Hier siehst du beispielsweise in Beitrag 8, wie man http Pakete zusammenbaut die irgendwie ankommen.
Dieser Beitrag ist für Jugendliche unter 18 Jahren nicht geeignet.
  Mit Zitat antworten Zitat
Benutzerbild von Mavarik
Mavarik

Registriert seit: 9. Feb 2006
Ort: Stolberg (Rhld)
4.130 Beiträge
 
Delphi 10.3 Rio
 
#14

Re: Stream über TCP - Blockaufteilung ?

  Alt 4. Dez 2009, 07:45
Zitat von Stefanie:
Ja Mavarik,

so muss man es machen bei großen Streams. Bei kleinen Streams die zu einem Block zusammengefasst werden bleibt scheinbar auch nichts anderes übrig als am Anfang des Streams die Größe zu übertragen.
Ne immer... Besonders wenn Du auf Byte-Streams arbeitest... Weil Du ja keine Endmarke hast.

Wenn Du im Zeilenmodus arbeitest hast Du ja i.d.R. #13#10 oder sowas...

Zitat von Stefanie:
Mir wäre es lieber, wenn es eine Möglichkeit beim Senden gäbe evtl. die Fragmentierung(die ja in meinen Fall das Zusammenfassen kleiner Streams zu einem Block scheinbar auch ünernimmt) auszuschalten. Einfach, dass ich einen Stream mit z.B. 10 Bytes sende und nur genau das wird als ein Block übertragen, nichts Fragmentiert bzw. zusammengefasst.
10 Byte gehen immer in einem Rutsch... Bei meinen Test waren es auch immer mehr als Die MTU... Liegt sicherlich an meiner Komponente...
Also wenn ich von der Komponente den Event DataAvailable erhalten, liegen da meisten schon ca. 70.000 byte...

Mavarik
  Mit Zitat antworten Zitat
Astat

Registriert seit: 2. Dez 2009
Ort: München
320 Beiträge
 
Lazarus
 
#15

Re: Stream über TCP - Blockaufteilung ?

  Alt 5. Dez 2009, 07:00
Hallo Stefanie.


Stefanie hat geschrieben.

  • Mir wäre es lieber, wenn es eine Möglichkeit beim Senden gäbe evtl. die Fragmentierung(die ja in meinen Fall das Zusammenfassen kleiner Streams zu einem Block scheinbar auch ünernimmt) auszuschalten. Einfach, dass ich einen Stream mit z.B. 10 Bytes sende und nur genau das wird als ein Block übertragen, nichts Fragmentiert bzw. zusammengefasst.

Naja, das funktioniert leider nicht in allen Fällen.

Du sendest in einem Netzwerk irgendwelche Daten zu einem Server.
Protokoll ist IPPROTO_TCP.

Du übergibst normalerweise die zu sendenden Daten dem OSI-Layer 1. = Socket.Send!!
Danach brauchst du dir um den Datentransport keine Sorgen mehr zu machen.
Der Sendebuffer und MTU Size ist je nach Einstellung des OS, Treiber bzw, Netzwerkkarte unterschiedlich.
Wenn du kein Performance Freak bist, brauchst du dir darüber keinerlei Gedanken machen.
Die gesendeten Daten werden nun sicher (TCP) zum Server übertragen.
Der Server (OSI-Layer 1. = Socket.recv) hat einen Empfangsbuffer, der je nach OS, und Treiber und oder
Netzwerkkarte, natürlich auch unterschiedlich gross ist.

Also folgendes Szenario:
Client 1 sendet 10 Rote Kugeln, und Client 2 sendet zur gleichen Zeit 9 Grüne Kugeln.

Der Server Buffer hat Platz für 6 Kugeln (MTU * Socet Buffer Size)!

Im Buffer landen 2 Rote und 1 grüne Kugel, du wirst benachrichtigt, dass Daten empfangen wurden.

Du hast dir beim Connect des Clients am Server einen Empfangsbuffer für einen Grünen und einen Roten Client generiert.

Nun sortierst du die Grünen und die Roten Kugeln solange, bis alle Kugeln übertragen wurden.

Hier kommt nun das Problem das schon von Luckie, sirius, Mavarik angesprochen wurde.

Wenn du keine Informationen hast, wieviele Kugeln für einen bestimmten Client eigentlich übertragen werden müssen,
bist du auf verlorenem Posten. Du wirst es nie schaffen die genau gewünschte Anzahl der Kugeln zu übertragen.

Hier brauchst du einen Header (Info Kugel, die sagt dass für Rot 10 Kugeln kommen,
und eine Info Kugel, die sagt, dass für Grün 9 Kugeln kommen),
Also, Informationen wieviele Kugeln für einen bestimmten Climet übertragen werden müssen.

D.h. Wenn die Info Kugel übertragen wurde, müssen die Roten und die Grünen Kugeln solange getrennt werden, bis
die Anzahl der Kugeln für jeden Client erreicht ist.

Wo man nun richtig Hirnschmalz hineinstecken muss, sind die Sonderfälle, die auftreten können.

Der Empfangsbuffer enthält mehrere vollständig übertragene Datensätze (nur Rote Kugeln)

Der Empfangsbuffer enthält mehrere vollständig übertragene Datensätze,
inclusive Fragment des noch nicht vollständig gesendeten Datensatzes. (nur Rote Kugeln)

Der Empfangsbuffer enthält mehrere unvollständig übertragene Datensätze. (Rote und Grüne Kugeln)


Der Empfangsbuffer enthält mehrere vollständig übertragene Datensätze (Rote und Grüne Kugeln)

Der Empfangsbuffer enthält mehrere vollständig übertragene Datensätze,
inclusive Fragment des noch nicht vollständig gesendeten Datensatzes. (von Roten und Grünen Kugeln)

Der Empfangsbuffer enthält mehrere unvollständig übertragene Datensätze. (nur Rote Kugeln)

usw....

Hier ein Beispiel wie dies korrekt für Client und Server implementieren werden kann:

Clients wurden zuvor im Connect Evewnt in einer Hashliste gespeichet.
Jeder Client beinhaltet einen eigenen RecvBuffer = CliCon.FMemBuf = Aufteilung der Kugeln.
Socket Read Speichert die Daten in einem Ringbuffer, und der TServerObj.Thread
liest den Ringbuffer threadsave aus, und bereitet die Daten auf.

Delphi-Quellcode:
procedure TServerObj.Execute;
const
  HEADER_LENGTH = SizeOf(byte) + SizeOf(DWORD);
var
  cbRcv: integer;
  cbWritten: integer;
  cbRest: integer;
  ptrByte: ^Byte;
  nLen: integer;
  nDataSize: integer;
  cb: integer;
  nMsgID: Byte;
  p: pointer;
  CliCon: TCliCon;
  RcvBuf: array [0..WSOCK_READ_BUFFER_SIZE * 8] of byte;
  RcvBufItem: TRcvBufItem;
begin
  while not terminated do begin

    if FRecvBuffer.CountBytes > 0 then begin

      RcvBufItem := TRcvBufItem(FRecvBuffer.Peek(nLen)^);

      if FRecvBuffer.Remove(SizeOf(TRcvBufItem)) <> SizeOf(TRcvBufItem) then
        raise exception.Create('nLen <> FRecvBuffer.Remove(nLen)');

      cbRcv := RcvBufItem.cbData;
      if cbRcv <= 0 then
        raise exception.create('cbRcv <= 0');

      move(RcvBufItem.ptrData^, RcvBuf[0], cbRcv);

      CliCon := FIntHash.ValueOf(RcvBufItem.Socket);
      if CliCon = nil then begin
        if GlobalFree(RcvBufItem.hMemData) <> 0 then
          raise exception.create('GlobalFree(RcvBufItem.hMemData)) <> 0');

        if GlobalFree(RcvBufItem.hMemStruct) <> 0 then
          raise exception.create('GlobalFree(RcvBufItem.hMemStruct) <> 0');
        Continue;
      end;

      cbWritten := CliCon.FMemBuf.Write(@RcvBuf[0], cbRcv);
      cbRest := cbRcv - cbWritten;
      if cbRest <> 0 then begin
        CliCon.FMemBuf.SetBufSize((CliCon.FMemBuf.BufSize + cbRcv) * 2);

        if CliCon.FMemBuf.Write(@RcvBuf[cbWritten], cbRest) <> cbRest then
          raise exception.Create('you should never see this');
      end;

      while true do begin
        nLen := HEADER_LENGTH;
        ptrByte := CliCon.FMemBuf.Peek(nLen);
        if ptrByte <> nil then
        begin
          move(ptrByte^, nMsgID, SizeOf(Byte));
          inc(ptrByte, SizeOf(Byte));
          move(ptrByte^, nDataSize, SizeOf(DWORD));
        end else begin
          Break;
        end;

        if nMsgID > MSG_ID_KEEP_ALLIVE then begin
          CliCon.FMemBuf.Remove(CliCon.FMemBuf.BufSize);
          raise exception.Create('not (nMsgID in [MSG_ID_LOGON..MSG_ID_KEEP_ALIVE])');
        end;

        if CliCon.FMemBuf.CountBytes >= nDataSize + HEADER_LENGTH then
        begin //-- Mindestens ein Datensatz ist vorhanden
          if CliCon.FMemBuf.Remove(HEADER_LENGTH) = 0 then
            raise exception.Create('Buffer.Remove(HEADER_LENGTH) = 0');

          cb := nDataSize;
          p := CliCon.FMemBuf.Peek(cb);

          CliCon.ProcessMessage(nMsgID, p, nDataSize);

          if CliCon.FMemBuf.Remove(nDataSize) = 0 then
            raise exception.Create('Buffer.Remove(nDataSize) = 0');
        end else //-- if CliCon.FMemBuf.CountBytes >= nDataSize + HEADER_LENGTH
          Break;
      end; //-- while true do begin

      if GlobalFree(RcvBufItem.hMemData) <> 0 then
        raise exception.create('GlobalFreeII(RcvBufItem.hMemData)) <> 0');

      if GlobalFree(RcvBufItem.hMemStruct) <> 0 then
        raise exception.create('GlobalFreeII(RcvBufItem.hMemStruct) <> 0');
    end else begin
      DoGarbageCollection;
      Waitforsingleobject(FMsgDispatchEvent, INFINITE);
    end;
  end;
end;
Für das obige Beispiel ist auch vollständiger Source vorhanden.

Also, wenn du Asynchrone und oder Synchrone Socket brauchst, sag einfach bescheid, hab da fix und fertige
DLL's (Synchron, Asynchron) Client und Server incl. Source, hochlast getestet.
Asynchrone Client Server Anwendung mit ~10000 Client Verbindungen.
Synchrone Client Server Anwendung mit 2 X Quad Xeon mit ca. 3000 Requests/s bei ~ 25Kb Daten in einem 1 GB Netz.

Können dir auch als Tutorial dienen, um das Hirnschmalz Problem in den Griff zu bekommen.

Hoffe etwas geholfen zu haben.

lg. Astat
Lanthan Astat
06810110811210410503210511511603209711003210010110 9032084097103
03211611111604403209711003210010110903210010510103 2108101116122
11610103209010110510810103206711110010103210511003 2068101108112
10410503210310111509910411410510109810111003211910 5114100046
  Mit Zitat antworten Zitat
Benutzerbild von sx2008
sx2008

Registriert seit: 15. Feb 2008
Ort: Baden-Württemberg
2.332 Beiträge
 
Delphi 2007 Professional
 
#16

Re: Stream über TCP - Blockaufteilung ?

  Alt 5. Dez 2009, 14:11
Message Framing wäre das Suchwort für Google.
Der Absender sendet einzelne Messages (=Botschaften, Befehle, Commands) in einen TCP/IP Stream und der Empfänger hat die Aufgabe
die einzelnen Messages auch dann sauber zu trennen, wenn er immer nur einzelne Bruchstücke bekommt.
Im Extremfall tröpfeln die Bytes einzeln ein.

Dazu kenne ich drei Varianten
1.) feste Blockgrösse, Sender und Emfänger vereinbaren eine feste Blockgrösse
Messages, die kleiner sind werden mit Leerzeichen oder #0 aufgefüllt
2.) Jede Message wird mit CR/LF abgeschlossen
3.) Jede Message beginnt mit einem 4-Byte Int, in dem die Anzahl der folgenden Nutzdaten vermerkt ist

Variante 1.) ist sehr unflexibel, Variante 2.) hat Vorteile beim Debuggen aber problematisch für binäre Daten
und Variante 3.) eignet sich am Besten für grosse binäre Daten

Das Grundprinzip wie man die Messages beim Empfänger bildet ist aber immer das Gleiche.
Man braucht einen globalen Puffer für die Verbindung.
Ich bevorzuge AnsiStrings, weil man sich so nicht um die Reservierung und Freigabe von Speicher kümmern braucht.

Wenn ein neues Stück des TCP/IP Stream eintrifft, hängt man es an die bisherigen Daten an:
buffer := buffer + newdata; Dann beginnt die Suche, ob im Puffer eine vollständige Message steckt.
Für den Fall der Variante 2.) sähe das so aus:
Delphi-Quellcode:
p := Pos(#13#10, buffer)
if p <> 0 then
begin
  // Message gefunden
  Message := Copy(buffer, 1, p-1); // Message rauskopieren
  Delete(buffer, 1, p+1); // und im Puffer löschen
end;
Die Message könnte nun über ein Event verarbeitet werden.
Aber halt, wir sind noch nicht fertig!
Es könnten weitere Messages im Puffer stecken.
Mit einer Repeat....Until Schleife prüft man so lange, bis man feststellt, dass keine weiteren Messages im Puffer stecken.
Danach ist der Puffer leer oder es steckt noch eine unvollständige Message im Puffer.

Man sollte eine maximale Message - Grösse definieren, um sich vor Denial-of-Service Angriffen zu schützen.
Wird die max. Grösse überschritten, wird die Verbindung getrennt.

Da bei einer TCP/IP-Verbindung Daten üblicherweise in beide Richtungen verschickt werden,
müssen beiden Seiten dieses Verfahren zum Message Framing implementieren.

[edit]link hinzu[/eddit]
fork me on Github
  Mit Zitat antworten Zitat
Astat

Registriert seit: 2. Dez 2009
Ort: München
320 Beiträge
 
Lazarus
 
#17

Re: Stream über TCP - Blockaufteilung ?

  Alt 8. Dez 2009, 05:02
Hallo sx2008, anbei einiges was mir unklar ist.
  • sx2008 hat geschrieben
Message Framing wäre das Suchwort für Google.
Der Absender sendet einzelne Messages (=Botschaften, Befehle, Commands) in einen TCP/IP Stream und der Empfänger hat die Aufgabe
die einzelnen Messages auch dann sauber zu trennen, wenn er immer nur einzelne Bruchstücke bekommt.
Im Extremfall tröpfeln die Bytes einzeln ein.


Meinst du mit "Message Framing" den Socket API Parameter "SOCK_SEQPACKET"!?
  • sx2008 hat geschrieben
1.) feste Blockgrösse, Sender und Emfänger vereinbaren eine feste Blockgrösse
Messages, die kleiner sind werden mit Leerzeichen oder #0 aufgefüllt.


Ist das nichgt sehr ineffizient, nicht notwendige Daten zu übertragen?
  • sx2008 hat geschrieben
2.) Jede Message wird mit CR/LF abgeschlossen


Warum brauch ich dann eine mit "werden mit Leerzeichen oder #0 aufgefüllt." großen Block?
Ich hab ja ein ASCII Protokoll, das wenn CR/LF kommt mir einen ganzen Datensatz anzeigt!?
Hier verstehe ich etwas nicht, kannst Du das genauer ausführen?
  • sx2008 hat geschrieben
3.) Jede Message beginnt mit einem 4-Byte Int, in dem die Anzahl der folgenden Nutzdaten vermerkt ist.
Meinst du hier eine Art Header?. Was ist aber, wenn jede Message aber mit einem Byte beginnen??

  • sx2008 hat geschrieben
Variante 1.) ist sehr unflexibel.

Geht das auch bei einem Webserver?
  • sx2008 hat geschrieben
Variante 2.) hat Vorteile beim Debuggen aber problematisch für binäre Daten
Verstehe ich auch nicht, kann ja auch Binär Dateien debugen, kannst du das genauer erklären?
  • sx2008 hat geschrieben
und Variante 3.) eignet sich am Besten für grosse binäre Daten.
Der HTTP Header macht das ja ähnlich, mit content length, und da spielet die Größe ja auch keine Rolle?
Wie meinst Du das?
  • sx2008 hat geschrieben
Das Grundprinzip wie man die Messages beim Empfänger bildet ist aber immer das Gleiche.
Man braucht einen globalen Puffer für die Verbindung.


Wäre nicht ein lokaler Buffer, jeder Client erhält einen eigenen Buffer, so wie es ein Webserver macht, zweckmäßiger?

  • sx2008 hat geschrieben
Ich bevorzuge AnsiStrings, weil man sich so nicht um die Reservierung und Freigabe von Speicher kümmern braucht.

D2009 oder D2010 verwenden Unicode Strings, da muss man sicher was umcodieren, oder?. Könnte man hier nicht mit Pointerarithmetik am Heap arbeiten, um die Datenfragmente effektiv aufzubereiten?
  • sx2008 hat geschrieben
Man sollte eine maximale Message - Grösse definieren, um sich vor Denial-of-Service Angriffen zu schützen.

"Denial-of-Service Angriffen" werden von vielen Rechnern, auf einen Opfer-Server durchgeführt, auch werden Schwächen der IPV4 Implementierung aktiv ausgenutzt, W2k RAW-Sockets aber auch Ring0 Treiber in der OSI 1-2 Schicht.
Hier verstehe ich nicht wie man sich vor solchen Angriffen schützem kann, kannst du das bitte genauer erklären,
Wäre sehr interessant, da dahingehend einige Forschungsarbeiten bei uns laufen.

  • sx2008 hat geschrieben
Wird die max. Grösse überschritten, wird die Verbindung getrennt.

Dies sollte man mit einem dementspechenden Protokoll, zB. ID + Len + ID + Auswertung doch hinbekommen?
Hast du da einen neuen Ansatz, bitte erklär das mal!

Würde mich auf rege Konversation freuen.

Danke für Deine Bemühungen


lg. Astat
Lanthan Astat
06810110811210410503210511511603209711003210010110 9032084097103
03211611111604403209711003210010110903210010510103 2108101116122
11610103209010110510810103206711110010103210511003 2068101108112
10410503210310111509910411410510109810111003211910 5114100046
  Mit Zitat antworten Zitat
Antwort Antwort
Seite 2 von 2     12   


Forumregeln

Es 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

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 06:40 Uhr.
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz