Entwickler-Ecke

Internet / Netzwerk - [TServerSocket]Keine Strings annehmen wenn Stream empf. wird


Born-to-Frag - Di 28.02.06 00:14
Titel: [TServerSocket]Keine Strings annehmen wenn Stream empf. wird
Hallo!

Also wenn ich vom ClientSocket zum ServerSocket einen Stream schicke, und mir dann noch jemand nen String schickt, gibts Probleme, da die Datei dann nicht mehr stimmt. Beim Client will ich jedoch nichts machen wie eine Boolean-Variable dass erst Strings gesendet werden wenn der Server ein "Transfer complete" zurück gibt, denn dann hab ich wieder Probleme mit mehreren Clients etc.

Ich denke ich werde es so machen, dass ich vor dem auswerten der Nachricht den Socket überprüfe um zu sehen wer sendet (Der, der den Stream sendet oder jemand anderes).
Das ist ja nicht das Problem eigendlich :)

Jetzt brauche ich nur noch eine Lösung um zu erkennen wenn ein normaler String ankommt.
Kann mir jemand nen Ansatz geben?

Danke für alle Antworten!! ;)


greetz


BenBE - Di 28.02.06 00:21

Sowas nennt man Protokoll *g*

Einfach bei Stream-Blöcken und Strings jeweils ne bestimmte Kennung vorausschicken ... Dann sollte das kein Thema sein ...


Born-to-Frag - Di 28.02.06 00:57

Nun ja, genau das mach ich ja, doch wenn jemand anderes etwas schickt dann geht es ja nicht, denn wenn ich dann in dem Modus bin, in dem ich die Datei empfange wird der String mit rein genommen


Narses - Di 28.02.06 01:13

Moin!

user profile iconBorn-to-Frag hat folgendes geschrieben:
Nun ja, genau das mach ich ja, doch wenn jemand anderes etwas schickt dann geht es ja nicht, denn wenn ich dann in dem Modus bin, in dem ich die Datei empfange wird der String mit rein genommen

Das Problem bei Streams und Protokollen auf Terminatorzeichenbasis ist, dass man sie nicht (so ohne weiteres) mischen kann...

Du könntest etwa sowas machen:
- Befehl "Stream-Start","x Bytes lang"
- dann auf der Clientseite den Stream in den Socket stecken
- auf der Serverseite die nächsten x Bytes nach dem Kommano (und dem Terminatorzeichen) aus dem Socket in einen Stream fließen lassen
- danach automatisch wieder in den "Befehlsmodus" wechseln

Der "Witz" ist also, die Länge des Streams zu kennen, zu versenden und dann die Bytes mitzuzählen. :wink:

cu
Narses


Born-to-Frag - Di 28.02.06 01:22

user profile iconNarses hat folgendes geschrieben:
Moin!

user profile iconBorn-to-Frag hat folgendes geschrieben:
Nun ja, genau das mach ich ja, doch wenn jemand anderes etwas schickt dann geht es ja nicht, denn wenn ich dann in dem Modus bin, in dem ich die Datei empfange wird der String mit rein genommen

Das Problem bei Streams und Protokollen auf Terminatorzeichenbasis ist, dass man sie nicht (so ohne weiteres) mischen kann...

Du könntest etwa sowas machen:
- Befehl "Stream-Start","x Bytes lang"
- dann auf der Clientseite den Stream in den Socket stecken
- auf der Serverseite die nächsten x Bytes nach dem Kommano (und dem Terminatorzeichen) aus dem Socket in einen Stream fließen lassen
- danach automatisch wieder in den "Befehlsmodus" wechseln

Der "Witz" ist also, die Länge des Streams zu kennen, zu versenden und dann die Bytes mitzuzählen. :wink:

cu
Narses


Das mache ich auch schon :D

Ich kenne immer die Größe der Streams und schalte danach wieder den Modus um. Aber: Wenn ich einen Stream sende, kann dann nie dieser String "zwischen" die Übertragung kommen und irgendwo in den Stream gelangen?

Wenn nicht, dann müsste ich nur noch wissen, wenn der letzte Teil ankommt, wie ich das ende abtrenne wenn das Ende des Stream + Ein Befehl ankommt..


Narses - Di 28.02.06 01:30

Moin!

user profile iconBorn-to-Frag hat folgendes geschrieben:
Wenn ich einen Stream sende, kann dann nie dieser String "zwischen" die Übertragung kommen und irgendwo in den Stream gelangen?

Naja, da gibt´s bei den Socket-Kompos im asynchronen Modus zugegeben etwas Probleme... :| Wenn du einen dicken Stream versendest, dann kommt die Methode .SendStream() sofort zurück, die WSA sendet aber im "Hintergrund" fleißig weiter. Wenn du jetzt einen .SendText() dazwischen machst, dann könnte dir das Paket dazwischen kommen, weiß ich aber nicht genau. Lösung: entweder blocking-socket-calls machen oder mit OnWrite() arbeiten, dass Ereignis tritt ein, wenn der Socket fertig ist mit Daten senden (also nach dem Stream).

Fazit: Das ganze wird mit großen Streams "spannend", kleinere (so bis 8kb) sollten keine Probleme machen, die passen nämlich in den WSA-Buffer. :wink:

Das ist kein leichtes Problem (leider, ich weiß), das löst man am besten dadurch, dass man den großen Stream in kleine Blöcke zerhackt.

user profile iconBorn-to-Frag hat folgendes geschrieben:
Wenn nicht, dann müsste ich nur noch wissen, wenn der letzte Teil ankommt, wie ich das ende abtrenne wenn das Ende des Stream + Ein Befehl ankommt..

Stream-Ende erkennen: Bytes zählen, ganz simpel! :D Wenn du sagst, du weißt wie lang der Stream ist, sollte das ja wohl kein Problem sein.

cu
Narses

PS: Wenn ich mir den Titel des Topics so ansehe, habe ich das Gefühl, ich sollte noch zur Sicherheit sagen: Es gibt keinen Unterschied zwischen den Stream-Daten und Strings am Socket, das sind einfach nur Daten. Dein Programm muss wissen, um was für Daten es sich handelt, die da gerade aus dem Socket sprudeln; da liegt eben das Problem -> Protokoll! :wink:


Born-to-Frag - Di 28.02.06 01:48

Die Streams sind unterschiedlich groß, aber über 8KB, und da sie immer in 8KB blöcken ankommen, weiß ich nicht wie ich das machen soll, denn ich hänge dann immer diesen Block an den Stream dran und beim letzten Block ist dann eben
schwer.
Bisher mach ich immer Socket.ReceiveBuf(Pointer(Int64(rM.Memory) + rM.Position)^, RecLeng)

Mh.. also Soll ich machen [rM is mein Stream]

Delphi-Quelltext
1:
2:
3:
  if (rM.Position + RecLeng) > FileSize then
    Socket.ReceiveBuf(Pointer(Int64(rM.Memory) + rM.Position)^, FileSize - rM.Position)
  else Socket.ReceiveBuf(Pointer(Int64(rM.Memory) + rM.Position)^, RecLeng);
?

Probier ich Morgen mal aus :)


//EDIT: Wie kann ich dann das Ende der Blocks in ein String umwandeln?
//EDIT2: Achso.. das ganze funktioniert ja nur wenn nichts "dazwischen" kommt :cry:
Was macht genau OnWrite?


Narses - Di 28.02.06 01:55

Moin!

Ja, so oder ähnlich; wenn du nicht alle Daten aus dem Socket liest, stellt dir die WSA sogar netterweise auch wieder ein FD_READ in die Messagequeue, kommt also wieder´n OnRead() hinterher. :wink:

Besser wäre natürlich die Stream-Stücke ins Protokoll zu integrieren und nicht immer einen kompletten Stream am Stück zu senden; ist auch für QoS besser zu handeln.

cu
Narses

PS:
user profile iconBorn-to-Frag hat folgendes geschrieben:
Wie kann ich dann das Ende der Blocks in ein String umwandeln?

Wenn du nicht alles gelesen hast, kriegst du wieder ein OnRead(), dann machste einfach .ReceiveText. :wink:

user profile iconBorn-to-Frag hat folgendes geschrieben:
Achso.. das ganze funktioniert ja nur wenn nichts "dazwischen" kommt :cry:
Was macht genau OnWrite?

S.o. oder DOH, tritt ein, wenn der Socket fertig ist mit Senden.

Hmmm, mir fällt da gerade noch ein anderes "Horror-Szenario" ein: wenn du asynchron einen fetten Stream sendest, dann kommt das .SendStream() ja sofort zurück, es werden aber noch im Hintergrund Daten gesendet (z.B. aus dem Memory-Stream). Was passiert wohl, wenn du jetzt (während dem Senden!) den Mem-Stream freigibst... :? :| :shock: ... :puke: also ganz soo einfach ist das alles nicht! :(


BenBE - Di 28.02.06 14:16

Ich hab vor etwas längerer Zeit einmal versucht, ein Übertragungs-Protokoll auf IntelHex-Basis zu entwickeln.

Der Witz an dem Protokoll war: Das Teil konnte beliebig viele (in den Grenzen von Speicher und DWORDs) Streams gleichzeitig verarbeiten, ohne das es konflikte gab (selbst mit automatischer Fehlerkorrektur).

Das Teil lief so (Achtung, sehr viel Theorie zum Intel-Hex-Format):

:NNAAAAAAAAPPPPCC????..????SS

Wobei NN die Länge des Datenbereichs, AAAAAAAA der DWORD-Offset im Stream, PPPP die Stream-\Page-ID und CC eine Befehls-ID war. Ferner waren die Fragezeichen die HEX-codierten Daten und SS eine Prüfsumme.

SS ist so zu setzen, dass alle Bytes der Zeile addiert (+SS) ein vielfaches von 256 ergeben.

Damit ließ sich auf jeden Fall erstmal sicherstellen, dass, wenn eine Zeile ankam, diese immer eindeutig dekodierbar war, da ich wusste, dass
- immer 2n+1 Bytes pro Zeile (Zeilenumbrüche ausgenommen) vorhanden waren
- eine Zeile immer mit : anfängt und danach nur HEX-Zeichen kommen dürfen
- eine Prüfsumme vorhanden ist

Wenn also ein Datenblock unterbrochen wurde und ich

:AB00AAABA155645012:1200312012...

erhalten hab, konnt ich diesen Block verwerfen. Ich brauchte am Ende nur mitzuzählen, welche Bytes\Blöcke eines Streams ich schon hatte und ggf. fehlende oder defekte Blöcke erneut anzufordern. Lief auch recht schnell, mal abgesehen davon, dass die Socket-Verbindung nicht die stabilste war.

Ich hab mein Protokoll damals dann auf folgende Befehle aufgebaut:

(Übernommen von IntelHex)
00 Es kommen Daten
01 End Of Stream

Zusätzlich:
02 Page open (bei mir Stream Open)
03 Page close (Stream Ende*)
04 Resend Request*

Wobei bei einem 03 erst dann geschlossen wurde, wenn dies die Gegenstelle auch mit einem 03 bestätigte. Fehlten Daten, wurden diese Blöcke per 04 erneut angefordert.

Meine Geschwindigkeiten lagen damals um die 20 KB\s (128 Bytes pro Block Nutzdaten) ... Haupteinschränkung war aber, dass meine verwendet Kompo bereits Datenverlust hatte, wenn ich die Maus bewegte ...

Hoffe, das ist für dich ein Ansatz, um dein Protokoll zu erweitern.


Born-to-Frag - Mi 01.03.06 17:11

Hallo!

user profile iconNarses hat folgendes geschrieben:
Besser wäre natürlich die Stream-Stücke ins Protokoll zu integrieren und nicht immer einen kompletten Stream am Stück zu senden; ist auch für QoS besser zu handeln.


Klingt gut! Wie kann ich Streams denn Teilen?

Wie kann ich denn an den von dem Stream gesendetem Block noch mein Protokoll dranhängen?

Das von dir BenBe klingt ziemlich kompliziert ^^ ..
Wie kodiere ich denn einen Stream in Hex?

greetz


BenBE - Mi 01.03.06 18:02

@B2F: Einfach Byte für Byte des Streams hernehmen und mit IntToHex ???, 2); kodieren.

Du musst nicht zwangsläufig IntelHex, wie ich es damals getan hab, hernehmen, sondern kannst eigentlich JEDES Protokoll nutzen, was eine Block-Integrität herstellen kann und dabei für deine Zwecke nicht mehr als nötig Overhead erzeugt.

Das Protokoll, was ich damals implementiert hab, war eigentlich von der Implementierung recht simpel (~300 Zeilen für die eigentliche Stream-Verwaltung). Der Rest (~400 Zeilen) kümmerte sich um die Initialisierung und Socket-Verwaltung.

@Streams auseinandernehmen: Einfach nicht mit einem Mal den gesamten Stream senden, sondern immer kleinere Teile (sagen wir 128 Bytes). Protokoll dran hängen heißt auch nur: Vor diesem Teilblock, ähnlich meiner IntelHex-Variante, noch zusätzliche Daten anfügen, die erklären, was eigentlich grad ankommt - was dieser Block zu bedeuten hat.


Born-to-Frag - Mi 01.03.06 19:55

Also danke schon mal! Hört sich alles realisierbar an :)
Nur noch mal um sicherzugehen: Ich sende doch dann nur noch String, oder? :D

Also ich melde mich dann noch mal falls nichts klappt :mrgreen:


//EDIT: Wie geh ich denn Byte für Byte durch :oops: ?
//EDIT2: So? IntToHex(Str.Seek(i, sofrombeginning), 2); //i: For-Schleife


BenBE - Mi 01.03.06 20:12

Nope...


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
var
    Buffer: Array of Byte;
    BufStr: String;
    X: Integer;
begin
    SetLength(Buffer, 256);
    SetLength(Buffer, Stream.Read(Buffer[0], Length(Buffer)));
    BufStr := '';
    For X := 0 TO High(Buffer) Do
        BufStr := BufStr + IntToHex(Buffer[X], 2);
end;


Born-to-Frag - Mi 01.03.06 22:07

user profile iconBenBE hat folgendes geschrieben:
@Streams auseinandernehmen: Einfach nicht mit einem Mal den gesamten Stream senden, sondern immer kleinere Teile (sagen wir 128 Bytes). Protokoll dran hängen heißt auch nur: Vor diesem Teilblock, ähnlich meiner IntelHex-Variante, noch zusätzliche Daten anfügen, die erklären, was eigentlich grad ankommt - was dieser Block zu bedeuten hat.


Bekomm das doch noch nich so ganz hin :(

Also mein Problem liegt wohl bei der umwandlung in einen String...
Dann noch eine Frage wegen dem Senden der Teile: Wenn ich das in einer Schleife mache dann kann es ja wieder pasieren das 2Teile auf einmal gesendet werden und irgendwas angeschnitten wird. Oder soll ich nach jedem Empfang ein "ok" oder etwas in dr Art senden?

BenBe hast du vielleicht noch deine Unit oder irgendein Beispiel das du posten willst an dem ich mir das klar machen kann? :)


greetz


Narses - Do 02.03.06 01:25

Moin!

user profile iconBorn-to-Frag hat folgendes geschrieben:
Dann noch eine Frage wegen dem Senden der Teile: Wenn ich das in einer Schleife mache dann kann es ja wieder pasieren das 2Teile auf einmal gesendet werden und irgendwas angeschnitten wird.

Wenn du unterhalb der WSA-Buffergröße bleibst (AFAIK 8kb), dann sollte es keine "Überlagerungseffekte" geben, da die einzelnen Socket-Calls nicht unterbrochen werden, bei einem Stream aber nicht der ganze Stream auf einmal gesendet wird, wenn er nicht in den WSA-Buffer passt, sondern in Stücken. Deshalb Vorschlag: nimm 4kb-Segmente. :wink:

cu
Narses


Born-to-Frag - Fr 03.03.06 20:10

Hallo!

Funktioniert soweit, nur bin ich mir nicht sicher, weil ich glaube das es doch passieren kann das 2Teile zusammen ankomen :gruebel: Aber das werd ich dann schon Testen.

Jetzt hab ich aber noch 2 Fragen:
Und zwar hat der Stream ja eine bestimmte Größe, z.B. 170.000. Wenn ich jetzt z.B. den Fortschritt zeigen will, wie geht das? Denn die String-Länge ist ja am Ende der Übertragung nicht 170.000, sondern größer, oder?

Und dann hab ich Probleme bei der Konvertierung der Hex-Strings zum Stream. Klappt nicht so ganz, habe es mit HexToBin versucht und dann MeinStream.Write(), aber das geht nicht :(


greetz


Narses - Fr 03.03.06 22:27

Moin!

user profile iconBorn-to-Frag hat folgendes geschrieben:
weil ich glaube das es doch passieren kann das 2Teile zusammen ankomen :gruebel:

Wenn du das mit einem "vernünftigen" Protokoll machst, ist das kein Problem, das muss der Protokollparser auflösen können, sonst taugt der nix. :wink:

user profile iconBorn-to-Frag hat folgendes geschrieben:
Und zwar hat der Stream ja eine bestimmte Größe, z.B. 170.000. Wenn ich jetzt z.B. den Fortschritt zeigen will, wie geht das? Denn die String-Länge ist ja am Ende der Übertragung nicht 170.000, sondern größer, oder?

Wenn du die binärdaten in HexStrings umwandelst, dann ist der Faktor 2! Das ist mal nicht ganz unheftig, also für 1 MB Nutzdaten überträgst du 2 MB + Protokolloverhead... da wäre ein Binärprotokoll vermutlich besser (ich plaudere mal aus dem Nähkästchen: ich Arbeite gerade an einem Bin-Proto-Tut... :wink:)

user profile iconBorn-to-Frag hat folgendes geschrieben:
Und dann hab ich Probleme bei der Konvertierung der Hex-Strings zum Stream. Klappt nicht so ganz, habe es mit HexToBin versucht und dann MeinStream.Write(), aber das geht nicht :(

Hatte am Anfang auch Probleme mit HexToBin, hat irgendwie komisch rumgezickt, die Prozedur. Kann mich aber nicht mehr genau erinnern... naja, hilft jetzt wohl nicht gerade viel, diese Aussage. :| :wink:

cu
Narses


Born-to-Frag - Sa 04.03.06 02:38

Ich habe immer noch große Probleme beim codieren/decodieren meines Streams.
Ich habe jetzt alles überprüft und irgendwo liegt dort der fehler. Die Codieren Zeichen die der Client sendet, und die, die der Server empfängt, enthalten keine fehler.

Also hier mal die codierung in HEX:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
  Str.Position := 0;
  SetLength(Buffer, 256);
  repeat
    i := Str.Read(Buffer[0], Length(Buffer));
    BufStr := '';
    for j := 0 to i - 1 do
      BufStr := BufStr + IntToHex(Buffer[j], 2);
    if i <> 0 then
      Socket.SendText('$STREAM$' + BufStr + #13);
  until i = 0;
  Socket.SendText('$STREAMEND$' + #13);



decodieren:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
  rM.Clear;
  rM.Position := 0;
  for i := 0 to Length(StreamStr) - 1 do
  begin
    StreamBuf := StrToInt('$' + Copy(StreamStr, i, 2)) and $FF;
    rM.Write(StreamBuf, 1);
  end// danach eben noch rM.SaveToFile();



Vielleicht findet ja jemand den Fehler für mich :)


greetz


EDIT: Mal ein Beispiel, was ich auf meiner boot.ini für einen Text bekommen habe:

Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
[[¶b&oöo÷tB lÆoöadFeWr%]Ð
Ð
§tFi–mÖeVo÷uWtC=Ó330 
Ð
¦dFeVffauVlÇtC=Öm×uVlÇtFi’(ƒ0)–dFi—s6k²(ƒ0)—r&dFi—s6k²(ƒ0)—par'tFi—tFi–oönâ(ƒ1)•\ÅWtI”NäDDOõWuS0
Ð
¥[¶o÷peWr&atFi–nægr s7y—s7tFeVm×s5]Ð
Ð
¦m×uVlÇtFi’(ƒ0)–dFi—s6k²(ƒ0)—r&dFi—s6k²(ƒ0)—par'tFi—tFi–oönâ(ƒ1)•\ÅWtI”NäDDOõWuS3=Ò"$MÖi–c7r&o÷s6oöfgtB Wvi–nædFo÷wws2 X…P Pr&oöffeWs7s6i–oönæalÂ"" /önæoöeWx†eVc7uWtFeS=Öo÷ptFi–nâ /öffas7tFdFeWtFeVc7t@
Ð


EDIT2: Ok geschafft, BenBe hat mir geholfen =)

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
  rM.Clear;
  rM.Position := 0;
  for i := 1 to Length(StreamStr) div 2 do
  begin
    StreamBuf := StrToInt('$' + Copy(StreamStr, i*2 -12)) and $FF;
    rM.Write(StreamBuf, 1);
  end;