Autor Beitrag
Quitzlinga
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 60
Erhaltene Danke: 2

Win XP
Delphi 2007 Prof. Codegear Win32
BeitragVerfasst: Fr 05.10.07 00:06 
Hallo zusammen

Ich wollte mein Chatprogramm um eine Übertragungsroutine zum Filetransfer erweitern. Die Übtragung funktoniert jedoch nicht so, wie ich das erwartet habe und zwar sind die Daten nach der Übertragung verstümmelt. Zur ersten Veranschaulichung erst mal etwas Code.

Serverübertragungsroutine :

ausblenden volle Höhe 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:
procedure TRSPPBase.SendFile(Empfaenger : TIDContext; TargetDir: String;FName : string);
var
  image : TFileStream;
  Stream : TStringStream;
  data : string;
  len  : longInt;
  i,anz,rest : integer;

begin
  // /FILE_START Dir DasBild.jpg
  CastTextMessage(Empfaenger,'/FILE_START ' +'0 ' + TargetDir + ' ' +
                  ExtractFilename(FName));
  image := TFileStream.Create(FName,fmOpenRead);
  image.Position := 0;
  Stream := TStringStream.Create('');
  Stream.CopyFrom(image,image.Size);
  image.free;
  len := Stream.size;
  stream.Position := 0;
  anz := len div Package_Size;
  rest := len mod Package_Size;
  for i := 1 to anz do begin
    //SetLength(data,Package_Size);
    data := stream.ReadString(Package_Size);
    StrTmp := '*' + MaskTermChar(data,Mask_Char) + '*'// MaskChar ist #10
    CastTextMessage(Empfaenger,'/FILE_ADD ' + IntToStr(Length(StrTmp)) + ' ' +
                               StrTmp);
  end;
  if rest = 0
    then begin
      CastTextMessage(Empfaenger,'/FILE_END 0');
    end
    else begin
      SetLength(data,rest);
      data := stream.ReadString(rest);
      StrTmp := MaskTermChar(data,Mask_Char);
      CastTextMessage(Empfaenger,'/FILE_ADD ' + IntToStr(rest) + ' *' + StrTmp + '*');
      CastTextMessage(Empfaenger,'/FILE_END 0');
    end;
  stream.Free;
end;


Client Empfangsroutine

ausblenden volle Höhe 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:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
procedure TRSPPBase.ThreadReceive(Sender: TObject;IP : String; Msg: String);
begin
  // und auf stapel packen;
  Batch.Add(msg);
  // Wenn Busy dann raus
  if not ClientBusy then begin
    // Client arbeitet
    ClientBusy := true;
    // solange abbarbeiten, bis Batchbefehle leer
    while Batch.Count > 0 do begin
      // Nachricht am Amfang herausloesen, zwischenspeichern
      // und aus der Liste loeschen
      MsgGlobal := Batch.Strings[0];
      Batch.Delete(0);
      // String parsen
      ParseMsg(MsgGlobal);
      // Befehlskontrolle
      if      MyStrMsg.Com = '/TEXT' then begin
                                       ChatMemo.Lines.Add(MyStrMsg.Substring[2]);
                                     end
      else if MyStrMsg.Com = '/FILE_START' then begin
                                             Datei_Client :=
                                             TFileStream.Create(MyStrMsg.Substring[2
                                             + MyStrMsg.Substring[3],fmCreate);
                                             Datei_Client.Position := 0;
                                             String_Client := TStringStream.Create('');
                                             String_Client.Position := 0;
                                           end

      else if MyStrMsg.Com = '/FILE_ADD' then begin
                                           // Erstes und letztes Zeichen loeschen
                                           // weil "*data* Aufbau wegen 
                                           // Leertrennzeichen
                                           Delete(MyStrMsg.Substring[2],
                                                  Length(MyStrMsg.Substring[2]),1);
                                           Delete(MyStrMsg.Substring[2],1,1);
                                           // und String demaskieren
                                           StrTmp :=
                                           UnmaskTermChar(MyStrMsg.Substring[2],
                                           Mask_Char); // Mask_Char ist #10
                                           String_Client.WriteString(StrTmp);
                                         end

      else if MyStrMsg.Com = '/FILE_END' then begin
                                           String_Client.Position := 0;
                                           Datei_Client.CopyFrom(
                                           String_Client,String_Client.Size);
                                           String_Client.Free;
                                           Datei_Client.Free;
                                         end

      else begin
        ChatMemo.Lines.Add('System (Unbekannter Befehl): ' + MsgGlobal);
      end;
    end;
    // Abarbeitung beendet;
    ClientBusy := false;
  end;
end;


Die Parsingroutine

ausblenden volle Höhe 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:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
procedure TRSPPBase.ParseMsg(Msg : string);
var
   MsgTmp :string;
   i,j : integer;
begin
   // auf Kommando pruefen
   if pos('/',msg) <> 0
   then begin
     // Nachricht ist ein Kommandostring
     // nun in die einzelnen Bestandteile trennen
     MsgTmp := Msg;
     // Rest des Stringarrays loeschen
       for i := 1 to 10 do begin
       MyStrMsg.substring[i] := '';
       end;
     // nun Kommando rauskopieren
     j := pos(' ',MsgTmp);
     if j <> 0 then begin
       MyStrMsg.com := Uppercase(trim(copy(MsgTmp,1,j)));
       delete(MsgTmp,1,j);
       MsgTmp := trim(MsgTmp);
     end
     else begin
       MyStrMsg.com := Uppercase(trim(MsgTmp));
       MsgTmp := '';
     end;
     // Wenn Textnachricht, dann alles im ersten
     // sonst restliche Operatoren verteilen
     if MyStrMsg.com = '/TEXT'
     then begin
       MyStrMsg.Substring[2] := Trim(MsgTmp);
     end

     else if MyStrMsg.com = '/FILE_ADD' then begin
       // Nun Laenge rauskopieren
       // Erster Operand immer Laenge
       j := pos(' ',MsgTmp);
       if j <> 0 then begin
         MyStrMsg.substring[1] := Uppercase(trim(copy(MsgTmp,1,j)));
         delete(MsgTmp,1,j);
       end
       else begin
         MyStrMsg.substring[1] := Uppercase(trim(MsgTmp));
         MsgTmp := '';
       end;
       // und RestString kopieren
       MyStrMsg.substring[2] := copy(MsgTmp,1,StrToInt(MyStrMsg.substring[1]));
       MsgTmp := '';
     end

     else begin
       // Nun Laenge rauskopieren
       // Erster Operand immer Laenge
       i := 2;    // Laenge immer im ersten Element
       j := pos(' ',MsgTmp);
       if j <> 0 then begin
         MyStrMsg.substring[1] := Uppercase(trim(copy(MsgTmp,1,j)));
         delete(MsgTmp,1,j);
         MsgTmp := trim(MsgTmp);
       end
       else begin
         MyStrMsg.substring[1] := Uppercase(trim(MsgTmp));
         MsgTmp := '';
       end;

       while MsgTmp <> '' do begin
         // Trennzeichen feststellen
         j := pos(' ',MsgTmp);
         if j <> 0 then begin
           // string enthaelt noch trennzeichen
           MyStrMsg.substring[i] := copy(MsgTmp,1,j-1);
           delete(MsgTmp,1,j);
           MsgTmp := Trim(MsgTmp);
           i := i + 1;
         end
         else begin
           // string enthaelt keine Trennzeichen mehr
           MyStrMsg.substring[i] := MsgTmp;
           MsgTmp := '';
         end;
       end;
     end;
   end
   else begin
     // Nachricht ist ein ganz normaler String
     MyStrMsg.Com := '/TEXT';
     MystrMsg.substring[1] := '';
     MyStrMsg.Substring[2] := msg;
     for i := 3 to 10 do begin
       MyStrMsg.substring[i] := '';
     end;
   end;
end;


Technisch arbeite ich mit der Indy10 Komponenten (Blocking), auf deren Client zum Empfang ein Thread aufgesetzt wurde. Solange ich mit normalen Texten arbeite, funktioniert es auch so, wie erwartet. Bei Dateien aber wird verstümmelt übermittelt. Ich habe beide Turorials von Narses einmal durchgearbeitet und denke, das ich sie schon einigermassen verstanden habe. Aber letztendlich muss ich wohl entweder beim Übermittel oder beim Empfangen einen Denkfehler gemacht haben. Ich weiss auch nicht so genau, ob die Maskierung unbedingt notwendig ist bei einer zeilenweisen Übermittlung aber die Fehler sind weniger geworden, als ich das 0-Byte maskiert habe. Ich vermute mal, das bei einer zeilenweisen Übermittlung das 0-Byte der Trenncode ist, ähnlich wie im Tutotial der Zeilenvorschub als Trenner genutzt wurde.

Ich bin mit meinem Latein am Ende. Wo habe ich meinen Denkfehler ?

MfG

Quitzlinga


Zuletzt bearbeitet von Quitzlinga am Fr 05.10.07 12:16, insgesamt 1-mal bearbeitet
Narses
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Administrator
Beiträge: 10185
Erhaltene Danke: 1261

W11x64
TP3 .. D7pro .. D10.2CE
BeitragVerfasst: Fr 05.10.07 00:24 
Moin!

user profile iconQuitzlinga hat folgendes geschrieben:
Ich habe beide Turorials von Narses einmal durchgearbeitet und denke, das ich sie schon einigermassen verstanden habe.

Naja, ich fürchte, leider noch nicht soo richtig gut. :?


user profile iconQuitzlinga hat folgendes geschrieben:
// Übermittelte Strings haben den Aufbau /Command len Com1 Com2 Com3
// Trennzeichen ist Space

Ja was denn nun: Space, Slash oder doch lieber Stern? :?!?: ;)

Dieses Protokoll-"Design" hast du aber nicht aus meinen Tuts. :zwinker: Wenn du mit einem Trennzeichen arbeiten willst, dann musst du das auch entsprechend durchziehen. Sagen wir halt mal ein Space, ganz willkürlich. Dann würde dein Syntaxrahmen so aussehen:
ausblenden Quelltext
1:
Command len Com1 Com2 com3 					

wobei der erste Frame immer das Kommando darstellt (und sich mir ehrlich gesagt die Bedeutung von den restlichen Parametern nicht wirklich erschließt :gruebel:)

user profile iconQuitzlinga hat folgendes geschrieben:
Ich weiss auch nicht so genau, ob die Maskierung unbedingt notwendig ist

Unter der oben genannten Voraussetzung ist die Maskierung notwendig, weil du mit einem Terminatorzeichen arbeitest. Aber dann musst du auch das Terminatorzeichen -> das Leerzeichen - ersetzen, und nicht irgendwas anderes. :idea: ;)

Schau mal, ob du damit schon weiter kommst.

cu
Narses

_________________
There are 10 types of people - those who understand binary and those who don´t.
Quitzlinga Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 60
Erhaltene Danke: 2

Win XP
Delphi 2007 Prof. Codegear Win32
BeitragVerfasst: Fr 05.10.07 01:34 
Die Maskierung des Leerzeichens hat nichts gebracht, aber das hatte ich erwartet. Ich habe wahrsceinlich vergessen noch zu erwähnen, das ich mittels writeln und readln arbeite, also zeilenweise die Befehle übermittle und nicht wie im Tutorial Blockweise lese und die Strings dann nach dem #13 Zeichen trenne.

Ein String hat bei mir immer folgenden Aufbau /Kommando Param1 Param2 ..., wobei der / anzeigt, das ein Kommandowort folgt und die Parameter dahinter durch Leerzeichen getrennt sind. Der Parser trennt das dann alles halt wieder auf. Das funktioniert auch ganz gut. Nur eben, wenn der Parameter ein "Dateistring" ist, tauchen Fehler auf.

MfG

Quitzlinga
Narses
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Administrator
Beiträge: 10185
Erhaltene Danke: 1261

W11x64
TP3 .. D7pro .. D10.2CE
BeitragVerfasst: Fr 05.10.07 09:45 
Moin!

user profile iconQuitzlinga hat folgendes geschrieben:
Die Maskierung des Leerzeichens hat nichts gebracht, aber das hatte ich erwartet. Ich habe wahrsceinlich vergessen noch zu erwähnen, das ich mittels writeln und readln arbeite, also zeilenweise die Befehle übermittle und nicht wie im Tutorial Blockweise lese und die Strings dann nach dem #13 Zeichen trenne.

Das ist aber ein ziemlich essentieller Bestandteil eines Terminatorzeichen-Protokolls: genau 1 Terminator-Zeichen, dass außerhalb der Datenzeichenmenge verwendet wird! :mahn: Du verwendest gleich mehrere: #10,#13,#32,'/','*' 8)

Deshalb ist es auch keine besonders schlaue Idee, das Leerzeichen als Terminator zu wählen: dann darfst du nämlich jede Textnachricht maskieren (sind ja vermutlich Leerzeichen drin)! :idea:

user profile iconQuitzlinga hat folgendes geschrieben:
und die Parameter dahinter durch Leerzeichen getrennt sind. Der Parser trennt das dann alles halt wieder auf. Das funktioniert auch ganz gut. Nur eben, wenn der Parameter ein "Dateistring" ist, tauchen Fehler auf.

Klar, weil du in den Binärdaten deine "Terminatorzeichenmenge" nicht maskiert hast... ;)

user profile iconQuitzlinga hat folgendes geschrieben:
Ein String hat bei mir immer folgenden Aufbau /Kommando Param1 Param2 ..., wobei der / anzeigt, das ein Kommandowort folgt

Der Slash ist redundant, das ist dir schon klar, oder? Hast du dir eigentlich mal Gedanken über deine Protokoll-Syntax gemacht? Wenn, dann noch nicht ausreichend viele. :think:

Ich fasse nochmal zusammen: du hast die Erkenntnisse aus meinen Terminatorzeichen-Tutorials leider noch nicht ausreichend verstanden oder zumindest nicht entsprechend umgesetzt. Die Tuts sind ja nun nicht die Bibel, deshalb solltest du vielleicht nach einem anderen Tutorial Ausschau halten, an dem du dich orientieren kannst. Ansonsten: bitte nochmal :les: :mahn: :D

cu
Narses

_________________
There are 10 types of people - those who understand binary and those who don´t.
Quitzlinga Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 60
Erhaltene Danke: 2

Win XP
Delphi 2007 Prof. Codegear Win32
BeitragVerfasst: Fr 05.10.07 12:26 
Hurra, die Routine funktioniert. Man musste nur wissen, das INDY bei Readln und Writeln automatisch ein Terminatortrennzeichen setzt (nämlich #13#10). Für denjenigen, der das nicht weiss, ist das natürlich eine böse Falle. Hab nur das #10 im übertragenden String maskiert und dann klappt es wunderbar.

@Narses : Wäre gut, wenn du diese Information über das Terminatortrennzeichen noch deinem Tutorial hinzufügen könntest. Nicht alle arbeiten ja mit dem blockweisen Einlesen. Die meisten benutzen wahrscheinlich readln und writeln wie ich und da kann dann so ein Fehler schnell zu fast unlösbaren Problemen führen (hab 3 Tage dran gesessen, bis ich das herausfand)

Ich hoffe, Du hast nichts dagegen, das ich deine Maskierungs- und Demaskierungsroutine erst mal weiter verwende, bis ich sie verstanden habe und eine eigene (vielleicht verbsserte) geschrieben habe.

MfG

Quitzlinga