Entwickler-Ecke
Internet / Netzwerk - Dateiübermittlung per Strings fehlerhaft
Quitzlinga - Fr 05.10.07 00:06
Titel: Dateiübermittlung per Strings fehlerhaft
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 :
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 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 data := stream.ReadString(Package_Size); StrTmp := '*' + MaskTermChar(data,Mask_Char) + '*'; 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
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 Batch.Add(msg); if not ClientBusy then begin ClientBusy := true; while Batch.Count > 0 do begin MsgGlobal := Batch.Strings[0]; Batch.Delete(0); ParseMsg(MsgGlobal); 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 Delete(MyStrMsg.Substring[2], Length(MyStrMsg.Substring[2]),1); Delete(MyStrMsg.Substring[2],1,1); StrTmp := UnmaskTermChar(MyStrMsg.Substring[2], Mask_Char); 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; ClientBusy := false; end; end; |
Die Parsingroutine
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 if pos('/',msg) <> 0 then begin MsgTmp := Msg; for i := 1 to 10 do begin MyStrMsg.substring[i] := ''; end; 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; if MyStrMsg.com = '/TEXT' then begin MyStrMsg.Substring[2] := Trim(MsgTmp); end
else if MyStrMsg.com = '/FILE_ADD' then begin 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; MyStrMsg.substring[2] := copy(MsgTmp,1,StrToInt(MyStrMsg.substring[1])); MsgTmp := ''; end
else begin i := 2; 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 j := pos(' ',MsgTmp); if j <> 0 then begin MyStrMsg.substring[i] := copy(MsgTmp,1,j-1); delete(MsgTmp,1,j); MsgTmp := Trim(MsgTmp); i := i + 1; end else begin MyStrMsg.substring[i] := MsgTmp; MsgTmp := ''; end; end; end; end else begin 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
Narses - Fr 05.10.07 00:24
Titel: Re: Dateiübermittlung per Strings fehlerhaft
Moin!
Quitzlinga 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. :?
Quitzlinga 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:
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:)
Quitzlinga 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
Quitzlinga - 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 - Fr 05.10.07 09:45
Moin!
Quitzlinga 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:
Quitzlinga 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... ;)
Quitzlinga 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
Quitzlinga - 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
Entwickler-Ecke.de based on phpBB
Copyright 2002 - 2011 by Tino Teuber, Copyright 2011 - 2026 by Christian Stelzmann Alle Rechte vorbehalten.
Alle Beiträge stammen von dritten Personen und dürfen geltendes Recht nicht verletzen.
Entwickler-Ecke und die zugehörigen Webseiten distanzieren sich ausdrücklich von Fremdinhalten jeglicher Art!