![]() |
Stream über TCP - Blockaufteilung ?
Ich möchte über TCP Streams übertragen. Nun ist es ja bei TCP nicht so einfach wie bei UDP. Bei UDP kann ich einfach einen Stream senden und dieser kommt beim UDP-Emfänger in einem Stück wieder so an. Bei TCP - soweit ich das bisher richtig verstanden habe - werden die Daten(also der Stream) Blockweise übertragen und ich muß den Stream beim Emfänger aus den einzelnen Blöcken wieder zusammensetzen. Nun ist es nach meinen Tests so, dass größere Streams in mehrere Blöcke aufgeteilt und so Stück für Stück übertragen werden. Hingegen bei vielen kleinen Streams(Streamgröße ~ 5 bis 10 Byte), die schnell hintereinander gesendet werden (über socket.sendstream(Stream)) kommen auf der Emfängerseite mehrere Streams zu einem Block zusammengefasst an. Bei einer langsamen Netzwerkverbindung, z.B. über Modem bzw. ISDN tritt die Zusammenfassung von Streams zu größeren Blöcken deutlich stärker auf als bei einer schnellen internen LAN-Verbindung.
Nun meine Frage(n): Kann ich irgenwo festlegen, dass die kleinen Streams nicht zu mehreren Blöcken zusammengefasst und gesendet werden, bzw. kann ich die Blockgröße festlegen. Gibt es eine verständliche Logik, wie die Blockgröße bei TCP festgelegt wird. Weis ich schon irgendwoher schon beim Senden, in welche Blöcke mein Stream aufgeteilt bzw. zusammengefasst wird ? |
Re: Stream über TCP - Blockaufteilung ?
Guten Morgen,
die "Blockgröße" wird durch die Maximal Transfer Unit ( ![]() Diese kann auf dem Interface (tcpDoctor) eingestellt werden. Default MTU-Size ist, wenn ich micht nicht irre, 1500 Byte. Grüße Klaus |
Re: Stream über TCP - Blockaufteilung ?
Ich glaube du verwechselst da TCP mit UDP. Mit TCP kommt alles in einem Stück an in der Reihenfolge wie du es gesendet hast. Bei UDP kannst du Blöcke versenden, kannst aber nicht sicher sein, dass die jeweiligen Blöcke auch ankommen und schon gar nicht, dass sie in der selben Reihenfolge ankommen.
|
Re: Stream über TCP - Blockaufteilung ?
Danke Klaus,
ich hatte gehofft, dass die Blockgröße eine Einstellung beim SocketClient bzw. SocketServer ist und ich diese je nach größe des zu sendenden Streams anpassen(erzwingen) kann. So macht das aber keinen rechten Sinn, die Blockgröße zu erzwingen. Das die MTU die Fragmentierung der Daten eigenständig übernimmt ist ja soweit OK, aber dass die MTU auf der "Gegenstelle" die Daten nicht wieder defragmentiert verwundert mich überhaupt. Aber warum werden dann kleine Streams zu großen Blöcken zusammengefasst? Einen großen Stream wieder aus mehreren Blöcken zusammensetzen ist einfach und ich brauch nur die Größe des Streams. Einen Block in mehrere kleine Streams wieder aufspalten ist nur mit irgendeiner unique Erkennung am Anfang & Ende des Streams möglich. Das bedeutet zusätzliche Bytes am Anfang & Ende des Streams. Das möchte ich vermeiden, den bei einer Streamgröße von z.B. 6 Bytes überhaupt würde das die kleine Datenmenge unnötig aufblähen. Habt ihr eine Idee für mein Problem ? Zitat:
|
Re: Stream über TCP - Blockaufteilung ?
Ich habe das Gefühl wir reden mit gleichen Begriffen von verschiedenen Dingen.
Ein Stream ist für mich eine beliebige anzahl von Zeichen die übertragen werden soll. Wenn Alice über TCP Daten an Bob sendet, dann wird der Stream sehr wohl in einzelne IP-Datagramme zerlegt und verschickt. Bei Bob werden diese Pakete aber wieder zusammengesetzt und in der Reihenfolge wie sie gesendet wurden. Dafür ist ja TCP da sonst müsste man alles mühsam mit UDP, welches nicht sicherstellt dass ein Paket auch ankommt und auch nicht in der richtigen Reihenfolge. Wenn du TCP benutzt musst du dich nicht um Paketgrössen oder Reihenfolge kümmern, das macht TCP für dich. Wenn du das nicht möchtest, dann ist TCP das falsche Protokoll dafür. Beim Streaming von Audio- und Video-Daten z.B. (und das hat z.B. mit einem Stream wie er in Delphi definiert ist gar nichts zu tun) wird UDP verwendet. Das eine oder andere Paket, dass verloren geht, ist dabei nicht relevant (dafür gibt es Qualitätseinbussen). Oder ich habe nicht verstanden worum es geht... |
Re: Stream über TCP - Blockaufteilung ?
Zitat:
Bei >=1Gbit gibt es Jumboframes Wenn du PPPOE nutzen ist die Paketgröße noch kleiner 1492. |
Re: Stream über TCP - Blockaufteilung ?
Was interessiert dich das eigentlich? Du befindest dich doch im Application Layer vom OSI Referenzmodell mit deiner Anwendung. was die untergeordneten Protokolle machen, ist für dich doch gar nicht von Interesse. Deswegen gibt es ja diese Abstraktion, dass man sich auf Anwendungsebene nicht darum kümmern muss.
|
Re: Stream über TCP - Blockaufteilung ?
Zitat:
Grüße Klaus |
Re: Stream über TCP - Blockaufteilung ?
er meinte wohl MBit,
aber ja, sowas gibt es schon lange 'ne G1-Leitung hatte ich mir schon lange gewünscht, wobei es ja heutzutage schonwieder was Schnelleres gibt. |
Re: Stream über TCP - Blockaufteilung ?
Zitat:
![]() |
Re: Stream über TCP - Blockaufteilung ?
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 |
Re: Stream über TCP - Blockaufteilung ?
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:
|
Re: Stream über TCP - Blockaufteilung ?
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: ![]() |
Re: Stream über TCP - Blockaufteilung ?
Zitat:
Wenn Du im Zeilenmodus arbeitest hast Du ja i.d.R. #13#10 oder sowas... Zitat:
Also wenn ich von der Komponente den Event DataAvailable erhalten, liegen da meisten schon ca. 70.000 byte... Mavarik |
Re: Stream über TCP - Blockaufteilung ?
Hallo Stefanie.
Stefanie hat geschrieben.
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:
Für das obige Beispiel ist auch vollständiger Source vorhanden.
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; 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 |
Re: Stream über TCP - Blockaufteilung ?
![]() 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:
Delphi-Quellcode:
Dann beginnt die Suche, ob im Puffer eine vollständige Message steckt.
buffer := buffer + newdata;
Für den Fall der Variante 2.) sähe das so aus:
Delphi-Quellcode:
Die Message könnte nun über ein Event verarbeitet werden.
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; 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] |
Re: Stream über TCP - Blockaufteilung ?
Hallo sx2008, anbei einiges was mir unklar ist.
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"!?
Messages, die kleiner sind werden mit Leerzeichen oder #0 aufgefüllt. Ist das nichgt sehr ineffizient, nicht notwendige Daten zu übertragen?
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?
Meinst du hier eine Art Header?. Was ist aber, wenn jede Message aber mit einem Byte beginnen??
Geht das auch bei einem Webserver?
Verstehe ich auch nicht, kann ja auch Binär Dateien debugen, kannst du das genauer erklären?
Der HTTP Header macht das ja ähnlich, mit content length, und da spielet die Größe ja auch keine Rolle? Wie meinst Du das?
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?
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?
"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.
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 |
Alle Zeitangaben in WEZ +1. Es ist jetzt 19:07 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz