Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Netzwerke (https://www.delphipraxis.net/14-netzwerke/)
-   -   TCP Filelistening (https://www.delphipraxis.net/178559-tcp-filelistening.html)

Briand 14. Jan 2014 23:36

TCP Filelistening
 
Hi DPler

Ich versuche gerade alle Dateinamen in einem Ordner mit Foto Dateien auszulesen und über TCP an einen server.socket zu übermitteln und in einem Listview anzuzeigen.
Dies funktioniert eigentlich ganz gut.
Nun habe ich aber einen Ordner wo es über 1000 Fotos drinn hat und da sendet es nur bis zu einer bestimmten Stelle. z.b

Foto0001
Foto0002
Foto0003
..
..
Foto0999

obwohl eingentlich bis Foto1239 sein sollte.
Die Datei listening Rutine listet alle Dateien auf, dies hab ich überprüfft nur nach dem socket.sendtext(Filelist); sind diese nicht mehr vollständig.

Meine Frage hat socket.sendtext nur eine bestimmte anzahl an Zeichen?

Delphi-Quellcode:

Function ListFiles(sDir: String): String;
Var
  sFileName: ansiString;
  sFileList: ansiString;
  sDirList: ansiString;
  sSizeList: ansiString;
  sRec: TWin32FindData;
  findHandle: THandle;
Begin

  If AnsiLastChar(sDir) <> '\' Then
  Begin
    sDir := sDir + '\';
  End;

  Try
    findHandle := FindFirstFile(PChar(sDir + '*.*'), sRec);

    If findHandle <> INVALID_HANDLE_VALUE Then
      Repeat
        sFileName := sRec.cFileName;

        If (sRec.dwFileAttributes And FILE_ATTRIBUTE_DIRECTORY) <> 0 Then Begin
        End
        Else
        Begin
          sFileList := sFileList + sFileName + '++';
        End;
      Until FindNextFile(findHandle, sRec) = False;
  Finally
  End;

  Result := sFileList;

  End;

procedure TForm1.SpeedButton1Click(Sender: TObject);
begin
client.Socket.SendText('Files|' +ListFiles('C:\Fotos\Ordner2'));
end;
Danke im voraus

Aphton 15. Jan 2014 00:01

AW: TCP Filelistening
 
Sendtext sendet nicht zwingendermaßen alles.. Es liefert dir die Anzahl der Bytes die geschickt wurden, zurück. Bei größeren Daten werden die Pakete fragmentiert - ganz normales Verhalten bei TCP.

Briand 15. Jan 2014 00:28

AW: TCP Filelistening
 
Und wie kann ich das Problem lösen?
Kenne mich da nicht so wirklich aus mit TCP.

Aphton 15. Jan 2014 00:57

AW: TCP Filelistening
 
Wenn du [ABCDEF] schicken willst, aber nur [ABCD]geschickt wird, was muss dann getan werden? [EF] muss nochmal geschickt werden!
Außerdem soll der Empfänger wissen, wann alles angekommen ist - also am Schluss noch Marker setzen usw.
Damit solltest du das Problem nun selber lösen können

mjustin 15. Jan 2014 07:55

AW: TCP Filelistening
 
Zitat:

Zitat von Briand (Beitrag 1243688)
Und wie kann ich das Problem lösen?
Kenne mich da nicht so wirklich aus mit TCP.

Verwende eine TCP-Bibliothek, die dieses Problem für dich löst - zum Beispiel Internet Direct (Indy) oder Synapse. Der übergibt der Client einen Text und dessen Länge, oder einen Terminator, und der Gegenpart kann dann bequem die gesamten gesendeten Daten lesen.

Die Daten müssen dann nur eindeutig getrennt sein, z.B. ist ein CR/LF als Trennzeichenfolge ungünstig wenn die zu transportierenden Daten bereits diese Trennzeichenfolge enthalten können. In diesem Fall kann man auf ein Trennzeichen ausweichen das nicht in den Nutzdaten enthalten ist (#0 zum Beispiel), oder vor den Daten deren Länge (Anzahl der folgenden Bytes) übermitteln. Indy bietet da viele nützliche Varianten der Write... und Read... Methoden.

DeddyH 15. Jan 2014 09:32

AW: TCP Filelistening
 
Etwas anderes: der try-finally-Block und die Auswertung der Dateiattribute sehen merkwürdig aus. Sollte das nicht eher so lauten?
Delphi-Quellcode:
If findHandle <> INVALID_HANDLE_VALUE Then
  try
      Repeat
        If (sRec.dwFileAttributes And FILE_ATTRIBUTE_DIRECTORY) = 0 Then Begin
          sFileName := sRec.cFileName;
          sFileList := sFileList + sFileName + '++';
        End;
      Until not FindNextFile(findHandle, sRec);
  Finally
    Windows.FindClose(findHandle);
  End;

himitsu 15. Jan 2014 09:41

AW: TCP Filelistening
 
Ich weiß auch nicht, wie es schon im Forum erwähnt wurde?
KEINER VERGLEICHE MIT TRUE/FALSE :!:

Zitat:

Delphi-Quellcode:
      Repeat
        sFileName := sRec.cFileName;

        If (sRec.dwFileAttributes And FILE_ATTRIBUTE_DIRECTORY) <> 0 Then Begin
        End
        Else
        Begin
          sFileList := sFileList + sFileName + '++';
        End;
      Until FindNextFile(findHandle, sRec) = False;

Nja, merkwürdig ist eigentlich nur das sinnlose ELSE und die unnötigen BEGIN-END.

Delphi-Quellcode:
      Repeat
        sFileName := sRec.cFileName;
        If sRec.dwFileAttributes And FILE_ATTRIBUTE_DIRECTORY = 0 Then
          sFileList := sFileList + sFileName + '++';
      Until not FindNextFile(findHandle, sRec);

      Repeat
        If sRec.dwFileAttributes And FILE_ATTRIBUTE_DIRECTORY = 0 Then Begin
          sFileName := sRec.cFileName;
          sFileList := sFileList + sFileName + '++';
        End;
      Until not FindNextFile(findHandle, sRec);

      Repeat
        If sRec.dwFileAttributes And FILE_ATTRIBUTE_DIRECTORY = 0 Then
          sFileList := sFileList + sRec.cFileName + '++';
      Until not FindNextFile(findHandle, sRec);
Und wenn jetzt noch dieses "++" in einem Dateinamen vorkommt, dann war's das.

DeddyH 15. Jan 2014 09:45

AW: TCP Filelistening
 
Den 2. Code entspricht ja meinem, nur dass ich das FindClose noch untergebracht habe.

Klaus01 15. Jan 2014 10:22

AW: TCP Filelistening
 
.. wenn eine Variable sFileList heißt,
warum verwendest Du dann keine?
Eine StringList würde sich hier anbieten.

Dann benötigst Du noch ein Übertragungs-Protokoll.

z.B.

fileCount:8Byte
fileNumber: 8Byte
fileName:1024 Byte
fileSize: 8Byte
fileData: fileSize

fileNumber: 8Byte
fileName:1024 Byte
fileSize: 8Byte
fileData: fileSize

Grüße
Klaus

Briand 24. Jan 2014 11:40

AW: TCP Filelistening
 
Also ich hab das nun so gelöst weiss zwar nicht ob es der richtige Weg ist aber es funktioniert ;-)

Delphi-Quellcode:
unit Unit1;

interface

uses
  Windows, Winapi.Messages, SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.Buttons, Web.Win.Sockets,
  System.Win.ScktComp, Vcl.ComCtrls, Vcl.StdCtrls, Vcl.ExtCtrls;
type
  TForm1 = class(TForm)
    SpeedButton1: TSpeedButton;
    client: TClientSocket;
    server: TServerSocket;
    SpeedButton2: TSpeedButton;
    lv1: TListView;
    Edit1: TEdit;
    Procedure SpeedButton1Click(Sender: TObject);
    Procedure clientError(Sender: TObject; Socket: TCustomWinSocket; ErrorEvent: TErrorEvent; var ErrorCode: Integer);
    Procedure serverClientRead(Sender: TObject; Socket: TCustomWinSocket);
    Procedure clientRead(Sender: TObject; Socket: TCustomWinSocket);
    Punction ListFiles(sDir: String): String;
    Punction GetFileSize(sFile: PChar): Int64;
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }

  end;

var
  Form1: TForm1;
  LI2: TListItem;
  ListedFiles: Ansistring;
  TotalFiles: integer;
  i : integer;
implementation

Uses Unit3;
  {$R *.dfm}

Function ListFiles(sDir: String): String;
Var
  sFileName: String;
  sFileList: String;
  sSizeList: String;
  sRec: TWin32FindData;
  findHandle: THandle;
  Begin
  totalfiles := 0;
 If AnsiLastChar(sDir) <> '\' Then
 Begin
   sDir := sDir + '\';
 End;
  Try
    findHandle := FindFirstFile(PChar(sDir + '*.*'), sRec);
    If findHandle <> INVALID_HANDLE_VALUE Then
      Repeat
        sFileName := sRec.cFileName;
        If (sRec.dwFileAttributes And FILE_ATTRIBUTE_DIRECTORY) <> 0 Then Begin
        End
        Else
        Begin
        sFileList := sFileList + sFileName +'¦'+ IntToStr(GetFileSize(PChar(sDir + sFileName))) + '|';
        totalfiles := totalfiles + 1;
        End;
      Until FindNextFile(findHandle, sRec) = False;
  Finally
  End;
  Result := sFileList;
End;

Function GetFileSize(sFile: PChar): Int64;
Var
  fFile: THandle;
  wFD: TWIN32FINDDATA;
Begin
  Result := 0;
  If Not FileExists(sFile) Then
  Begin
  Exit;
  End;
  fFile := FindFirstFile(PChar(sFile), wFD);
  If fFile = INVALID_HANDLE_VALUE Then
  Begin
  Exit;
  End;
  Result := (wFD.nFileSizeHigh * (Int64(MAXDWORD) + 1)) + wFD.nFileSizeLow;
  Windows.FindClose(fFile);
End;

procedure TForm1.SpeedButton1Click(Sender: TObject);
begin
form1.server.Socket.Connections[0].SendText('FileStart|');
form1.lv1.Clear;
end;

procedure TForm1.clientError(Sender: TObject; Socket: TCustomWinSocket; ErrorEvent: TErrorEvent; var ErrorCode: Integer);
begin
errorcode := 0;
end;

procedure TForm1.clientRead(Sender: TObject; Socket: TCustomWinSocket);
var cData, Ccmd: string;

begin
 cData := socket.ReceiveText;
 Ccmd := copy(cData,0,pos('|',cData)-1);

 if cCmd = ('FileStart') then
 begin
 ListedFiles := ListFiles('C:\MeineFotos\ordner1');
 form1.Caption := inttostr(totalfiles);
if totalfiles = 0 then
begin
  exit
end;
 form1.client.Socket.SendText('FileADD|'+ copy(ListedFiles,0,pos('|',ListedFiles)-1));
 Delete(ListedFiles,1,pos('|',ListedFiles));
 totalfiles := totalfiles -1;
 end;

 if cCmd = ('FileNEXT') then
 begin
 if totalfiles = 0 then
 begin
 exit
 end;

 if Totalfiles = 1 then
 begin
 form1.client.Socket.SendText('FileEND|'+ copy(ListedFiles,0,pos('|',ListedFiles)-1));
 end
 else begin
 form1.client.Socket.SendText('FileADD|'+ copy(ListedFiles,0,pos('|',ListedFiles)-1));
 Delete(ListedFiles,1,pos('|',ListedFiles));
 totalfiles := totalfiles -1;
 end;
 end;

end;

procedure TForm1.serverClientRead(Sender: TObject; Socket: TCustomWinSocket);
var
  sData, sCmd: string;

begin
 sData := Socket.ReceiveText;
 sCmd := copy(SData,0,pos('|',sData)-1);

 if sCmd = ('FileADD') then
 begin
 Delete(sdata,1,pos('|',sdata));
 LI2 := form1.lv1.Items.Add;
 LI2.Caption := copy(sdata,0,pos('¦',sdata)-1);
 Delete(sdata,1,pos('¦',sdata));
 LI2.SubItems.Add(sdata);
 form1.server.Socket.Connections[0].SendText('FileNEXT|');
 end;

 if sCmd = ('FileEND') then
 begin
 Delete(sdata,1,pos('|',sdata));
 LI2 := form1.lv1.Items.Add;
 LI2.Caption := copy(sdata,0,pos('¦',sdata)-1);
 Delete(sdata,1,pos('¦',sdata));
 LI2.SubItems.Add(sdata);

 end;

end.

Aphton 24. Jan 2014 16:36

AW: TCP Filelistening
 
Immernoch fehlerhaft!
Das ursprngliche Problem, wo ich eine Lösung dafür in meinem zweiten Beitrag beschrieben habe, ist ungelöst..

Edit1: Kann sein, dass ReceiveText() und SendText() das per LineBreak Marker doch regelt.. In dem Fall sollte es passen; muss halt nur einer bestätigen bzw. nachkucken.

Edit2: Ich hab schnell nachgekuckt:
Delphi-Quellcode:
function TCustomWinSocket.ReceiveText: AnsiString;
begin
  SetLength(Result, ReceiveBuf(Pointer(nil)^, -1));
  SetLength(Result, ReceiveBuf(Pointer(Result)^, Length(Result)));
end;

function TCustomWinSocket.SendText(const s: AnsiString): Integer;
begin
  Result := SendBuf(Pointer(S)^, Length(S) * SizeOf(AnsiChar));
end;
Folglich kannste mein Edit1 vergessen --> dein Problem ist weiterhin nicht gelöst! Der Fehler wird nur selten auftreten (bei hoher RTT und niedriger Window/Buffersize)

Briand 27. Jan 2014 00:07

AW: TCP Filelistening
 
Also Aphton ich verstehe da leider nur Bahnhof.....Bin halt ein Neuling in dem Bereich.

Wie müsste mann dann das machen damit es richtig funktionier?

Briand 10. Feb 2014 22:35

AW: TCP Filelistening
 
hmmmm bin nach fast 2 Wochen leider immer noch nicht weiter....
:(

Aphton 10. Feb 2014 23:27

AW: TCP Filelistening
 
Wie bereits erwähnt, wenn du 10 Bytes/Zeichen schicken willst, wird nicht garantiert, dass auch alle 10 geschickt werden...
Die Sendefunktion liefert dir nen Integer zurück, der dir die tatsächliche Anzahl an geschickten Bytes sagt.

Wenn du es lokal testest, wirst du auf das Problem nicht stoßen, da eben der Ping einfach zu gering ist.

Beispiel, wie so eine Kommunikation aussehen könnte, wenn man zwei Nachrichten "abCDef" und "Hallo" schicken würde:
Code:
Sende: "abCDef"
Sende liefert zurück 2 - dh. "ab" wurde geschickt, daher -->

Sende: "CDef"
Sende liefert zurück 0 - konnte nichts schicken; warum auch immer -->

Sende: "CDef"
Sende liefert zurück 4 -> passt

Sende: "Hallo"
Sender liefert zurück 5 -> passt

####

Empfange 1000 Zeichen:
Empfange liefert 0 zurück

Empfange 1000 Zeichen:
Empfange liefert 3 zurück -> "abC"

Empfange 1000 Zeichen:
Empfange liefert 1 zurück -> "D"

Empfange 1000 Zeichen:
Empfange liefert 5 zurück -> "efHal"

Empfange 1000 Zeichen:
Empfange liefert 2 zurück -> "lo"
Bei diesem Verhalten kannst du nun folgende Rückschlüsse ziehen:
- Du bist dafür zuständig, dass du deine Daten wirklich vollständig überträgst
- Beim Empfangen musst du auch Pakete unterscheiden können

Das Problem kann man - wenn man es ordentlich machen will - ganz elegant über Streams lösen:
Pro Socket ein Sende & Empfangstream instanzieren.

Alles was geschickt werden soll, wird in ein Paket gekapselt:
Ein Paket besteht aus [Größe][Daten]

Zum Schicken schreibt man die Pakete hinten im Sendestream rein.
Der Sendestream wird periodisch abgearbeitet und wie beim Beispiel oben wird der Komplette Inhalt des Streams versucht zu versenden und versendete Bytes werden verworfen!

Beim Empfangen puffert man auch alles in den Empfangsstream hinten rein. Der Empfangsstream muss auch perodisch abgerabeitet werden und es muss nach folgendes geprüft werden:
- habe ich GrößeInBytes(Paket.[Größe]) empfangen?
- falls ja, habe ich GrößeInBytes(Paket.[Größe]) + Paket.[Größe] empfangen?
- falls ja, dann erhält der Empfangsstream mindestens ein vollständiges Paket, das rausgenommen werden kann

Beispiel (hier gehen wir mal davon aus, dass die [Größe] 1 Byte groß ist):
Code:
SendePuffer.send("abCDef")
-> SendePuffer erhält folgenden Datenstrom [6]["abCDef"]

SendePuffer.send("Hallo")
-> SendePuffer erhält folgenden Datenstrom [5]["Hallo"]

Sendepuffer sieht so aus:
[6]["abCDef"][5]["Hallo"]

Periodisches abarbeiten des Sendepuffers:
Sende.. liefert zurück: 2 -->
["bCDef"][5]["Hallo"]

Sende... liefert zurück: 8 -->
["llo"]

Semde... liefert zurück: 3 -->
Leer

####

Empfange.. liefert zurück: 3 -->
Empfangspuffer:
[6]["ab"]

Paket entnehmbar?:
- ist die Größe des Empfangspuffers (3) > GrößeInBytes(Paket.[Größe]) (1) --> ja
- ist GrößeInBytes(Paket.[Größe]) (1) + Paket.[Größe] (=[6] = 6) <= Größe des Empfangspuffer?
also ist 1+6 <= 3 --> nein --> folglich kann man kein Paket rausfischen

Empfange.. liefert zurück: 10 -->
Empfangspuffer:
[6]["abCDef"][5]["Hallo"]

Paket entnehmbar?:
- 13 > 1 --> ja
- 1 + 6 <= 13 --> ja
- dh. 1 + 6 Bytes können aus dem Empfangspuffer entnommen werden
Paket: [6]["abCDef"]
Empfangspuffer:
[5]["Hallo"]

Paket entnehmbar?:
- 6 > 1 --> ja
- 1 + 5 <= 6 --> ja
- dh. 1 + 5 Bytes können aus dem Empfangspuffer entnommen werden
Paket: [5]["Hallo"]
Empfangspuffer:
Leer
[5]["Hallo"]
Alles was du nun zu tun hast, ist genau das hier umzusetzen.
Mehr nicht.

Achja, kann durchaus sein, dass es irgendwelche fertige Bibliotheken gibt, die das hier bereits für dich tun.. Da müsstest du dich erkundigen; aber an sich ist das eig. in 20 Minuten programmiert. Sollte ne Fingerübung sein.

Die Beschreibung könnte an manchen Stellen unverständlich sein; hab nicht drüber gelesen.. Sollte aber trotzdem helfen!

Briand 10. Feb 2014 23:34

AW: TCP Filelistening
 
Danke Dir werd mich morgen mal damit beschäfftigen und mich ev. nochmals melden.

L.g Briand

Briand 1. Mär 2014 15:54

AW: TCP Filelistening
 
So hab das Problem gelöst.

Es war so das das Socket Receive Event mehrmals aufgerufen wurde und ich immer nur den Schluss des Strings bekam.
Beim SendText schicke ich die grösse des Strings mit damit ich beim empfangen weiss wieviel das kommen sollte.

Habe das jetzt so gelöst.

Delphi-Quellcode:
procedure TForm1.ServerSocketClientRead(Sender: TObject; Socket: TCustomWinSocket);
var Befehl : String;
begin

Datas := Datas + socket.ReceiveText;

if Copy(datas, 1, 1) = '*' then
begin
Delete(datas,1,pos('*',datas));
buffer := strtoint(copy(datas,0,pos('*',datas)-1));
Delete(datas,1,pos('*',datas));
end;

if length(datas) >= buffer then
begin
Befehl := copy(datas,0,pos('|',datas)-1);

if Befehl = ('DRIVES') then
begin
Delete(Datas,1,pos('|',Datas));
TeileDrives(datas);
buffer := 0; datas := '';
end;

if Befehl = ('FOLDERS') then
begin
Delete(Datas,1,pos('|',Datas));
TeileFolders(datas);
buffer := 0; datas := '';
end;

if Befehl = ('FILES') then
begin
Delete(Datas,1,pos('|',Datas));
TeileFiles(datas);
buffer := 0; datas := '';
end;

himitsu 1. Mär 2014 16:37

AW: TCP Filelistening
 
Kleine Datenhäppchen werden gern mal zusammengefasst
und große Blöcke werden natürlich aufgeteilt, denn viele Übertragungsprotokolle haben eine "maximale" Größe, wieviele Daten in ein Datenpacket reinpassen.

Außerdem können/müssen natürlich nicht alle Datenpackete gleichzeitig am Ziel ankommen.

Und nun rate mal, was das für deine empfangenen Datenhäppchen bedeutet.
In ReciveText muß also noch nicht alles drin sein.
Irgendwie sieht man auch nicht wo/ob
Delphi-Quellcode:
buffer
ausgewertet wird.

Und das Löschen von
Delphi-Quellcode:
datas
kann den nächsten Befehl (teilweise) löschen.

[edit] Hab das
Delphi-Quellcode:
length(datas) >= buffer
übersehn, aber auch dir scheint wohl aufgefallen zu sein, daß
Delphi-Quellcode:
length(datas) = buffer
wohl nicht immer funktioniert. :roll:

Zitat:

Delphi-Quellcode:
Delete(Datas,1,pos('|',Datas));
...
buffer := 0; datas := '';

Das sieht nach nutzlos doppeltem Code aus.

PS: Die 0 in dem Copy sieht nicht grade richtig aus. :stupid:

Aphton 1. Mär 2014 17:22

AW: TCP Filelistening
 
Jo, das sieht schonmal logisch korrekter aus :thumb:

Aber ein kleiner Fehler ist immernoch drinnen :P
Zitat:

Delphi-Quellcode:
if Copy(datas, 1, 1) = '*' then
begin
Delete(datas,1,pos('*',datas));
buffer := strtoint(copy(datas,0,pos('*',datas)-1));
Delete(datas,1,pos('*',datas));
end;

Du gehst davon aus, dass, wenn ein '*' am Anfang von "datas" (dein Buffer) steht, dann ist der Rest (die Längenangabe mit Trenner '*') auch drinnen, also z.B. so~
'*20*' (für Länge = 20)

WAS ist aber, wenn z.B. ReceiveText nur 2 Zeichen zurückliefert; also macht ein
Delphi-Quellcode:
Datas := Datas + ReceiveText
folgendes z.B.:
Code:
Datas = ''
ReceiveText = '*2'
Datas = '*2'
Die If Beindung passt hier, da das erste Zeichen ein '*' ist - ach übrigens, Strings können per Klammer ab 1 indiziert werden, verwende dafür einzelne Zeichen kein Copy bitte :P also so in etwa:
Delphi-Quellcode:
if Datas[1] = '*' then
  //...
[Edit]
Die If Bedingung so umändern:
Delphi-Quellcode:
// Verbesserungsvorschlag
if (Datas[1] = '*') and (Pos('*', Copy(Datas, 2, Length(Datas))) <> 0) then
An erster Stelle muss ein '*' stehen und irgendwo danach (= ohne erstes Zeichen -- das ist das, was Copy zurückliefert) muss ein '*' vorkommen
[/Edit]

Abschließend möcht ich nochmals erwähen - das Senden muss nach dem selben Prinzip erfolgen.. Lege dort auch ein Datas an, schreibe ne neue Procedure zum Schicken von Daten, welches am Ende von Datas deinen String mit Längenangabe dazu "tut"..
Datas muss dann, wie zuvor beschrieben, (wenn nötig, häppchenweise) periodisch übertragen werden, sofern nicht leer!


Alle Zeitangaben in WEZ +1. Es ist jetzt 17:05 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