AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Sprachen und Entwicklungsumgebungen Object-Pascal / Delphi-Language Delphi FileStream.WriteBuffer für binäre Daten in einem String
Thema durchsuchen
Ansicht
Themen-Optionen

FileStream.WriteBuffer für binäre Daten in einem String

Ein Thema von crowley · begonnen am 25. Mai 2009 · letzter Beitrag vom 29. Mai 2009
Antwort Antwort
crowley

Registriert seit: 7. Jun 2006
Ort: Emmerich
9 Beiträge
 
#1

FileStream.WriteBuffer für binäre Daten in einem String

  Alt 25. Mai 2009, 15:35
Servus und Hallo,

der Titel klingt schon gruselig, aber bevor ich hier zerrissen werde, lasst mich erklären, was es damit auf sich hat. Wir verwenden hier Client/Server-Applikationen, die über ClientDatasets/DataProvider miteinander kommunizieren. Über spezifische DataRequests erhalten wir Daten in einer Key/Value-StringList. Alles andere erfolgt unmittelbar über die Anbindung an die Datenbank.

Bislang haben wir, wenn wir Logdateien von Kundenrechnern zur Auswertung von Fehlern oder der allgemeinen Performance benötigten, eine Netzwerkverbindung zum Kundenserver aufgebaut und die Dateien mittels Windows zu uns kopiert. Dank Conficker, der sich auch über diese Ports verbreiten konnte, halten unsere Admins nun diese Ports geschlossen, dennoch benötigen wir immer wieder diese Dateien.

Nun ja, mein Ansatz ist nun, die Dateien auch über einen DataRequest zu kopieren. Auf Server-Seite öffne ich die Datei mittels FileStream, lese einen Block (Chunk) ein und schicke diesen mit dem DataRequest zurück an den Client, der schreibt die Daten in einen eigenen FileStream und das geschieht so lange, bis die komplette Datei gelesen ist.

Das Einlesen gelingt ohne Probleme:
Delphi-Quellcode:
procedure TMyDataModule.do_DRQ_CopyFile(const aCommand: String; const aParameters: TStringList; const aResult: TStringList);
var
  loc_fs: TFileStream;
  loc_s: String;
  loc_s2: String;
  loc_i: Integer;
  loc_chunk: Integer;
begin
  aResult.Values[aCommand] := 'F';

  loc_fs := TFileStream.Create(aParameters.Values['FileName'], fmOpenRead or fmShareDenyWrite);
  try
    aResult.Values['FileSize'] := IntToStr(loc_fs.Size);
    loc_fs.Seek(StrToIntDef(aParameters.Values['Position'], 0), soFromBeginning);

    loc_chunk := loc_fs.Size - loc_fs.Position;
    if (64 * ONE_KILOBYTE < loc_chunk) then
      loc_chunk := 64 * ONE_KILOBYTE;

    SetLength(loc_s, loc_chunk);
    loc_fs.Read(loc_s[1], loc_chunk);
    aResult.Values['BytesRead'] := IntToStr(loc_chunk);

    { Erste Möglichkeit: Kopieren als String }
    aResult.Values['Data'] := loc_s;

    { Zweite Möglichkeit: Kopieren der ASCII-Werte }
// for loc_i := 1 to loc_chunk do
// loc_s2 := loc_s2 + IntToStr(Ord(loc_s[loc_i])) + ';';
// loc_s2 := Copy(loc_s2, 1, Length(loc_s2) - 1);
// aResult.Values['Data'] := loc_s2;

    aResult.Values[aCommand] := 'T';
  finally
    FreeAndNil(loc_fs);
  end;
end;
Möglichkeit 1 war mein ursprünglicher Plan, der verursacht aber auf der Client-Seite Probleme, auf die ich später eingehe.
Möglichkeit 2 umgeht das Problem mit den im String "gespeicherten" Steuerzeichen, ist aber unglaublich zeitaufwändig.

Auf Clientseite sieht das dann wie folgt aus:
Delphi-Quellcode:
function TLogBrowser.DownloadDRQ: Boolean;
var
  loc_fs: TFileStream;
  loc_Position: Integer;
  loc_FileSize: Integer;
  loc_drq: String;
  loc_v: Variant;
  loc_sl: TStringList;
  loc_Result: Boolean;
begin
  FCancelled := False;

  loc_sl := TStringList.Create;
  try
    loc_FileSize := 0;
    loc_Position := 0;

    if FileExists(GetTempDir + ExtractFileName('C:\DummyFile.txt')) then
      loc_fs := TFileStream.Create(GetTempDir + ExtractFileName('C:\DummyFile.txt'), fmOpenWrite)
    else
      loc_fs := TFileStream.Create(GetTempDir + ExtractFileName('C:\DummyFile.txt'), fmCreate);
    try
      loc_fs.Seek(0, soBeginning);
      repeat
        loc_drq := 'CMD=' + DRQ_CopyFile + ',' +
                   'FileName=' + 'C:\DummyFile.txt' + ',' +
                   'Position=' + IntToStr(loc_Position);
        loc_v := cdsFiles.DataRequest(loc_drq);
        loc_Result := DataRequestResult(DRQ_CopyFile, loc_v, loc_sl);
        if (loc_Result) then begin
          loc_FileSize := StrToIntDef(loc_sl.Values['FileSize'], 0);
          loc_fs.WriteBuffer(Pointer(loc_sl.Values['Data'])^, StrToIntDef(loc_sl.Values['BytesRead'], 0));
          Inc(loc_Position, StrToIntDef(loc_sl.Values['BytesRead'], 0));
        end;
      until (not loc_Result) or (loc_Position >= loc_FileSize);
    finally
      FreeAndNil(loc_fs);
    end;
  finally
    FreeAndNil(loc_sl);
  end;
  Result := loc_Result;
end;
Bei dieser Variante scheint zunächst alles zu funktionieren, aber: beim Schreiben des FileStreams werden ab dem ersten Zeilenumbruch oder dem ersten #0 Zeichen nur noch #0 Zeichen geschrieben. Dadurch sind die "kopierten" Daten natürlich vollkommen "wertlos".

Bei der zweiten Variante, bei der ich die ASCII- Werte übertrage, funktioniert zwar alles wie geplant, aber es ist seeeeeeeeehr zeitraubend. (2 MB benötigen dann knapp 30 Sekunden).
Delphi-Quellcode:
function TLogBrowser.DownloadDRQ: Boolean;
var
  loc_j: Integer;
  loc_fs: TFileStream;
  loc_Position: Integer;
  loc_FileSize: Integer;
  loc_drq: String;
  loc_buffer: array [0.. 64 * ONE_KILOBYTE] of Byte;
  loc_sl2: TStringList;
  loc_v: Variant;
  loc_sl: TStringList;
  loc_Result: Boolean;
begin
  FCancelled := False;

  loc_sl := TStringList.Create;
  try
    loc_FileSize := 0;
    loc_Position := 0;

    if FileExists(GetTempDir + ExtractFileName('C:\DummyFile.txt')) then
      loc_fs := TFileStream.Create(GetTempDir + ExtractFileName('C:\DummyFile.txt'), fmOpenWrite)
    else
      loc_fs := TFileStream.Create(GetTempDir + ExtractFileName('C:\DummyFile.txt'), fmCreate);
    try
      loc_fs.Seek(0, soBeginning);
      loc_sl2 := TStringList.Create;
      try
        repeat
          loc_drq := 'CMD=' + DRQ_CopyFile + ',' +
                     'FileName=' + 'C:\DummyFile.txt' + ',' +
                     'Position=' + IntToStr(loc_Position);
          loc_v := cdsFiles.DataRequest(loc_drq);
          loc_Result := DataRequestResult(DRQ_CopyFile, loc_v, loc_sl);
          if (loc_Result) then begin
            loc_FileSize := StrToIntDef(loc_sl.Values['FileSize'], 0);
            StrToStrings(loc_sl.Values['Data'], CON_FieldSep, loc_sl2, False);
            for loc_j := 0 to loc_sl2.Count - 1 do
              loc_buffer[loc_j] := StrToInt(loc_sl2[loc_j]);
            loc_fs.WriteBuffer(loc_buffer, StrToIntDef(loc_sl.Values['BytesRead'], 0));
            loc_fs.WriteBuffer(Pointer(loc_sl.Values['Data'])^, StrToIntDef(loc_sl.Values['BytesRead'], 0));
            Inc(loc_Position, StrToIntDef(loc_sl.Values['BytesRead'], 0));
          end;
        until (not loc_Result) or (loc_Position >= loc_FileSize);
      finally
        FreeAndNil(loc_sl2);
      end;
    finally
      FreeAndNil(loc_fs);
    end;
  finally
    FreeAndNil(loc_sl);
  end;
  Result := loc_Result;
end;
Die Hilfsfunktion DataRequestResult schreibt den Inhalt der Variant-Variable in eine Key/Value-StringList und wertet das Ergebnis des Paares mit dem Namen der DataRequest-Konstante aus. Wenn dieses 'T' ist, gibt die Funktion True zurück.

Die Hilfsfunktion StrToStrings stammt aus der JCL-Library. Sie wandelt einen String in eine Stringliste um. Dabei wird der übergebene Separator als Trennzeichen genutzt.
procedure StrToStrings(S: String; Sep: String; const List: TStrings; const AllowEmptyString: Boolean); Hat von Euch jemand eine Idee, was in der ersten Variante verbessert werden kann, so dass diese nutzbar wird? Oder einen Vorschlag, was ich verändern kann, um die zweite Variante drastisch zu beschleunigen? (Ich habe schon mit unterschiedlich großen Datenmengen gearbeitet, was aber kaum einen Unterschied bewirkte).

Vielen Dank schon einmal im Voraus

C.
  Mit Zitat antworten Zitat
crowley

Registriert seit: 7. Jun 2006
Ort: Emmerich
9 Beiträge
 
#2

Re: FileStream.WriteBuffer für binäre Daten in einem String

  Alt 29. Mai 2009, 14:46
Oooookay... dummer Fehler meinerseits :
Wenn sich Steuerzeichen wie z.B. #13#10 in dem String befinden, werden diese natürlich auch in der Stringliste widergespiegelt. Dementsprechend sieht es nicht mehr Key/Value-technisch so aus:

Delphi-Quellcode:
  CopyFile=T
  FileSize=1234
  BytesRead=1234
  Data=abcdefg#13#10hijklmn
sondern mehr so:

Delphi-Quellcode:
  CopyFile=T
  FileSize=1234
  BytesRead=1234
  Data=abcdefg
  hijklmn
Daher hatte ich nun in einem ersten Ansatz so gearbeitet, dass ich den Wert aus Data genommen habe und mittels einer Schleife den Inhalt aller nachfolgenden Zeilen der Stringliste (inklusive #13#10) ergänzt.
Diese unsaubere Methode gefiel mir nicht so wirklich und dann wurde mir bewusst, dass ich doch den String auch direkt aus dem Variant abgreifen kann, den ich dank des DataRequests erhalte. Das bedeutet, meine Schreibmethode sieht nun so aus:

Delphi-Quellcode:
  [...]
  loc_FileSize := 0;
  loc_Position := 0;

  if FileExists(aDestDir + ExtractFileName(FFileList[loc_i])) then
    loc_fs := TFileStream.Create(aDestDir + ExtractFileName(FFileList[loc_i]), fmOpenWrite)
  else
    loc_fs := TFileStream.Create(aDestDir + ExtractFileName(FFileList[loc_i]), fmCreate);
  try
    loc_fs.Seek(0, soBeginning);
    repeat
      loc_drq := 'CMD=' + DRQ_TransferFile + ',' +
                 'FileName=' + 'C:\DummyFile.txt' + ',' +
                 'Position=' + IntToStr(loc_Position);
      loc_v := cdsLogFiles.DataRequest(loc_drq);
      loc_Result := GnDataRequestResult(DRQ_TransferFile, loc_v, loc_sl);
      if (loc_Result) then begin
        loc_FileSize := StrToIntDef(loc_sl.Values['FileSize'], 0);
        loc_BytesRead := StrToIntDef(loc_sl.Values['BytesRead'], 0);

        loc_s := Copy(VarToStr(loc_v), Pos('DATA=', VarToStr(loc_v)) + 5, loc_BytesRead);
{$WARN UNSAFE_CODE OFF}
        loc_c := @loc_s[1];
{$WARN UNSAFE_CODE ON}
        try
          loc_fs.WriteBuffer(loc_c^, loc_BytesRead);
        except
          loc_Error := GetLastError;
          MessageDlg(
                     'Can not write file. Error ' + IntToStr(loc_Error),
                     mtError, [mbOk], 0
                    );
          loc_Result := False;
        end;
        Inc(loc_Position, loc_BytesRead);
      end;
    until (FCancelled) or (not loc_Result) or (loc_Position >= loc_FileSize);
  finally
    FreeAndNil(loc_fs);
  end;
So funktioniert das ganze seeeeehr gut und dann klappt's auch mit dem Nachbarn
  Mit Zitat antworten Zitat
shmia

Registriert seit: 2. Mär 2004
5.508 Beiträge
 
Delphi 5 Professional
 
#3

Re: FileStream.WriteBuffer für binäre Daten in einem String

  Alt 29. Mai 2009, 14:54
Delphi-Quellcode:
// vorher
if FileExists(aDestDir + ExtractFileName(FFileList[loc_i])) then
  loc_fs := TFileStream.Create(aDestDir + ExtractFileName(FFileList[loc_i]), fmOpenWrite)
else
  loc_fs := TFileStream.Create(aDestDir + ExtractFileName(FFileList[loc_i]), fmCreate);

// nachher
dateiname := aDestDir + ExtractFileName(FFileList[loc_i]);
if FileExists(dateiname) then
  loc_fs := TFileStream.Create(dateiname, fmOpenWrite)
else
  loc_fs := TFileStream.Create(dateiname, fmCreate);
PS: fangen eigentlich alle deine lokalen Variablen mit "loc_" an?
Andreas
  Mit Zitat antworten Zitat
Antwort Antwort


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 07:10 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