Entwickler-Ecke

Sonstiges (Delphi) - TStream aufteilen


Dolognio - Fr 03.11.06 11:26
Titel: TStream aufteilen
Hi @ all.

Ich habe hier im Forum [http://www.delphi-forum.de/viewtopic.php?t=46409] gelesen wie ich in einen Stream ein Identifikationsmerkmal schreiben kann, um zu erkennen, was für ein Datentyp sich in dem Stream befindet. Das klappt auch ohne Compilerfehler.

Meine Frage ist jetzt allerdings: Wie kann ich den Stream nach Empfang wieder so aufteilen, dass erst der erste Teil ausgelesen wird mit dem Merkmal, und dann der Rest dem Datentyp entsprechend? Ich kann mir da absolut nichts drunter vorstelln.

Thx schonmal.


Udontknow - Fr 03.11.06 11:58

Hallo!

Du musst nicht aufteilen. Du ermittelst mit einem Aufruf von


Delphi-Quelltext
1:
Stream.ReadBuffer(ID,SizeOf(ID));                    


das Identifikationsmerkmal. Anschliessend reagierst du.


Delphi-Quelltext
1:
2:
3:
4:
if ID=ID_JPG then 
  Picture.LoadFromStream(Stream);
if ID=ID_Text then
  Memo.Lines.LoadFromStream(Stream);


Erst wenn du "hinter" den JPG-Daten auch noch andere Blöcke sendest, wird es schwieriger. Dann musst du einen temporären Stream hinzunehmen und zusätzlich in den Originalstream auch noch die Größe des Blocks schreiben, damit die Blöcke auseinanderzuhalten sind.

Cu,
Udontknow


Dolognio - Fr 03.11.06 16:17

OK, thx, ich probier des mal so. Mla schauen ob ich des hinkrieg^^.

Cya.


PS: Thread lass ich für evtl weitere Fragen zum Thema ma noch offen bzw. unmarkiert.


Dolognio - Fr 03.11.06 18:11

Hmm, also funzt doch noch nicht wie gewünscht, ka was jetzt wieder los ist. Erst ma der Code:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
const ID_String = 0;
const ID_Stringlist = 1;

procedure Testsenden;
var sl: TStringlist;
    stream1, stream2: TMemoryStream;
    ID: integer;
begin
  sl:= TStringlist.Create;
  Stream2:= TMemoryStream.Create;
  sl.Sorted:= false;
  sl.Clear;
  sl.BeginUpdate;
  try
      sl.add('string1');
      sl.add('string2');
      sl.add('string3');
      sl.add('string4');
      sl.add('string5');
      sl.add('string6');
      sl.add('string7');
      sl.add('string8');
      sl.EndUpdate;
      sl.SaveToStream(stream2);

      stream1:= TMemoryStream.Create;
      try
      //Identifikationsmerkmal in Stream schreiben
      ID:=ID_Stringlist;
      Stream1.WriteBuffer(ID,SizeOf(ID));
          try
            stream1.CopyFrom(stream2,stream2.Size)          //hier ist der Fehler "Stream Read Error"
          finally
            stream2.Free;
            MainFrm.TCPClient.SendStream(stream1);
          end;
      finally
      stream1.Free;
      end;
  finally
      sl.Free;
  end;
end;


Ich dachte egtl ich hab alles richtig gemacht, find kein Fehler, außer mn muss im Zusammenhang mit einer Stringliste noch was andres beachten beim Speichern in Stream. Jedenfalls bringt der Debugger zur Laufzeit den Fehler "Stream read error!" in der oben markierten Zeile.

Hoffentlich weißt du noch weiter :( :?:


Narses - Fr 03.11.06 18:25

Moin!

Du mußt Stream2 auch wieder "zurückspulen". ;) So: Stream2.Position := 0;

cu
Narses


Martok - Fr 03.11.06 18:31

Oder statt Stream2.Size 0 angeben:

Delphi-Quelltext
 
32:
{ ... }
      stream1.CopyFrom(stream2,0); //gesamten Stream2 kopieren


Dolognio - Fr 03.11.06 23:38

Danke, jetzt klappts endlich, hätt ich in der Delphi-Hilfe geschaut über stream.copyfrom(), hätt ich gewusst dass ich ne 0 einfügen muss^^.

Wenn ich jetzt allerdings 2 verschiedene Merkmale in einen Stream einbauen muss, gilt dann das, was UDontKnow vorhin ma gesagt hat?

user profile iconUdontknow hat folgendes geschrieben:

Erst wenn du "hinter" den JPG-Daten auch noch andere Blöcke sendest, wird es schwieriger. Dann musst du einen temporären Stream hinzunehmen und zusätzlich in den Originalstream auch noch die Größe des Blocks schreiben, damit die Blöcke auseinanderzuhalten sind.



Und wenn ja, wie sieht dass denn dann aus? Was für Blöcke??? Und temporärer Stream ist TMemoryStream oder?


Udontknow - Sa 04.11.06 21:59

Hier die grobe Vorgehensweise. Ich gehe davon aus, daß jeder "Block" eine ID und eine Size mitgibt. Für jeden Block wird dann ein Stream bereitgestellt und die Funktion VerarbeiteBlock aufgerufen


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
procedure VerarbeiteBlock(ID:Integer; Size:Integer; Stream:TStream);
begin
  //Hier Stream verarbeiten
end;

procedure VerarbeiteBloecke(Stream:TStream);
var ID:Integer;
var Size:Integer;
var TempStream:TMemoryStream;
begin
  //Für alle Blöcke Verarbeitungsroutine aufrufen
  While Stream.Position<Stream.Size do
  begin
    //Identifikation des nun folgenden Blocks auslesen
    Stream.ReadBuffer(ID,SizeOf(ID));
    //Grösse des nun folgenden Blocks auslesen
    Stream.ReadBuffer(Size,SizeOf(Size));

    //Temporären Stream erzeugen
    TempStream:=TMemoryStream.Create;
    try
      //Größe des Streams einstellen
      TempStream.Size:=Size;
      //Daten des Blocks aus Originalstream kopieren
      TempStream.CopyFrom(Stream,Size);
      //Position zurücksetzen für Lesezugriff
      TempStream.Position:=0;
      //Aufruf der Verarbeitungsroutine
      VerarbeiteBlock(ID,Size,TempStream);
    finally
      TempStream.Free;
    end;
  end;
end;


Cu,
Udontknow


Dolognio - Sa 04.11.06 23:38

Also sry, aber ich werd aus deinem Code nich schlau.

Was versteht man denn überhaupt unter einem "Block"?
Und ich denk ma dass deine beiden Prozeduren zum lesen des gesendeten Streams da sind.

Wie aber speicher ich einen solchen Stream ab?

Einfach


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
var sl: TStringlist;
    TempStream1, TempStream2, SLstream: TMemoryStream;
    ID: integer;
begin
  sl:= TStringlist.Create;
  SLstream:= TMemoryStream.Create;
  try
      //sl bearbeiten
      sl.SaveToStream(SLstream);

      TempStream2:= TMemoryStream.Create;
      try
      //Identifikationsmerkmal in Stream schreiben
      ID:= ID_Stringlist;
      Stream1.WriteBuffer(ID,SizeOf(ID));
        
          try
            TempStream2.CopyFrom(SLstream,0);
            TempStream1:= TMemoryStream.Create;
            
            try
              TempStream1.CopyFrom(TempStream2,0);
            finally
              SLStream.Free;

          finally
            TempStream2.Free;


          end;
      finally
      MainFrm.TCPClient.SendStream(TempStream1);
      TempStream1.Free;
      end;
  finally
      sl.Free;
  end;

end;


?? Oder wie sonst?
Und wie gibt man einem "Block" (was auch immer das jetzt ist) ID und Size mit?


Udontknow - So 05.11.06 17:33

Nun, ein Block ist für mich eben ein Paket zusammenhängender Daten.

Beispiel:

Du hast ein Chatprogramm geschrieben, über das du mal Text, mal aber auch ein Bild senden kannst. Nun tritt es ab und zu auf, daß Pakete aneinanderhängen, diese müssen also von dir auseinandergehalten werden. Das erreichst du, indem du die Struktur eben wie beschrieben aufbaust. Immer wenn der User ein Bild senden will, werden zuerst das Merkmal für "Bild", dann die Anzahl der Bytes für das Bild und anschliessend die Daten des Bildes gesendet. Die Empfangsroutine kann anhand des Headers (ID und Groesse) eben erkennen, wo ein Block endet und der nächste beginnt.

Cu,
Udontknow


Dolognio - Mo 06.11.06 00:30

Hmm, also mit Streams muss ich mich glaub erst ma richtig befassen. Hab da noch nich so den Durchblick.
Hab mir daher jetzt auch was andres überlegt, wie ich 2 Merkmale in einen Stream schreiben kann.
Hier der Code:

Sender:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
procedure Testsenden;
var sl: TStringlist;
    stream1, stream2: TMemoryStream;
    ID: integer;
begin
  sl:= TStringlist.Create;
  Stream2:= TMemoryStream.Create;
  sl.Sorted:= false;
  sl.Clear;
  sl.BeginUpdate;
  try

//Stringlist füllen

      sl.EndUpdate;
      sl.SaveToStream(stream2);

      stream1:= TMemoryStream.Create;
      try
      //Identifikationsmerkmal in Stream schreiben
      ID:= 15;

      Stream1.WriteBuffer(ID,SizeOf(ID));
          try
            stream1.CopyFrom(stream2,0)
          finally
            stream2.Free;
            MainFrm.TCPClient.SendStream(stream1);
          end;
      finally
      stream1.Free;
      end;
  finally
      sl.Free;
  end;
end;


und
Empfänger:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
const ID_StringList = '1';
const ID_String = '2';

const ID_Frame1 = '1';
const ID_Frame2 = '2';
const ID_Frame3 = '3';
const ID_Frame4 = '4';
const ID_Frame5 = '5';
//...

procedure TForm1.TCPServerInput(Connection: TSimpleTCPConnection;
  Stream: TStream; var DisposeStream: Boolean);
var sl: TStringList;
    TypID, FrameID: char;
    ID: integer;
    h: string;
begin
   stream.ReadBuffer(ID,sizeof(ID));
   h:= inttostr(ID);
   TypID:= h[1];
   FrameID:= h[2];

   if TypID = ID_StringList then
   begin
      sl:= TStringList.Create;
      try
        sl.LoadFromStream(stream);
        if FrameID = ID_Frame5 then
           Frame5.ListBox1.Items.Assign(sl);
      finally
        sl.Free;
      end;
   end;
end;


Ist vielleicht nicht so sauber, aber funktioniert. Trotzdem DANKE für eure Geduld mir das alles näherzubringen!! :wink: :lol: