AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Programmierung allgemein Netzwerke Delphi Spielwiese - SocketTest
Thema durchsuchen
Ansicht
Themen-Optionen

Spielwiese - SocketTest

Ein Thema von stahli · begonnen am 7. Okt 2016 · letzter Beitrag vom 24. Mär 2017
Antwort Antwort
Benutzerbild von stahli
stahli
Online

Registriert seit: 26. Nov 2003
Ort: Halle/Saale
4.358 Beiträge
 
Delphi 11 Alexandria
 
#1

AW: Spielwiese - SocketTest

  Alt 8. Dez 2016, 08:02
Ich habe den Übeltäter.

Längere Texte werden ggf. bei der Übertragung gesplittet.

Mein Handler hatte hier noch einen Bug und hat die unvollständigen Teile schon verarbeitet. Das Problem trat auf meinen 2 Rechnern nicht auf, aber bei allen anderen getesteten. Das lag wohl daran, wie die einzelnen Threads jeweils zeitlich zum arbeiten kommen.


Also falls das jemand nutzen will dann hier ein Bugfix:
Delphi-Quellcode:
function TsoConnectionStringList.GetNextSL: TStringList;
var
  C: Integer;
begin
  fCS.Enter;
  try
    if (fIndex < fItems.Count) then
    begin
      C := StrToInt(fItems[fIndex]);
      fItems[fIndex] := '';
      Inc(fIndex);
      Result := TStringList.Create;
      while (C > 0) do
      begin
        Result.Add(fItems[fIndex]);
        fItems[fIndex] := '';
        Inc(fIndex);
        Inc(fTransferCounter);
        Dec(C);
      end;
      if (fIndex >= fItems.Count) then
      begin
        fItems.Clear;
        fIndex := 0;
      end
      else
      begin
        if (fIndex > 1000) then
        begin
          while (fItems.Count > 0) and (fItems[0] = '') do
          begin
            fItems.Delete(0);
            Dec(fIndex);
          end;
        end;
      end;
    end
    else
      Result := nil;
  finally
    fCS.Leave;
  end;
end;
Stahli
http://www.StahliSoft.de
---
"Jetzt muss ich seh´n, dass ich kein Denkfehler mach...!?" Dittsche (2004)
  Mit Zitat antworten Zitat
Benutzerbild von stahli
stahli
Online

Registriert seit: 26. Nov 2003
Ort: Halle/Saale
4.358 Beiträge
 
Delphi 11 Alexandria
 
#2

AW: Spielwiese - SocketTest

  Alt 22. Dez 2016, 12:21
Die letzte Änderung reicht doch nicht aus.

Also mein Konzept ist gut einsetzbar für kurze Stringlisten-Messages.
Wem das ausreicht, der kann das gut einsetzen.

Längere Stringlisten werden aber von den Sockets in 2 oder mehr Teilen übertragen und auf Grund der Zeilenumbrüche nicht immer korrekt zusammengesetzt.
Wer also auch längere Stringlisten übertragen will braucht eine alternative Lösung.

Ich bin daher dabei, die Stringlisten in Streams zu speichern und die Streams zu übertragen. Das hat auch den Vorteil, dass man die Streams bei Bedarf leichter komprimieren bzw. verschlüsseln kann.

Orientiert habe ich mich an folgendem Beitrag: http://www.delphipraxis.net/687499-post27.html

Jetzt habe ich das Problem, dass Read(...) manchmal feuert, Socket.ReceiveLength aber 0 ist und entsprechend keine Daten geladen werden können. Read(...) feuert aber später nicht noch einmal, so dass Daten verloren gehen.

Wenn ein Stream vollständig angekommen ist wird er wieder in eine StringList geschrieben und in eine Queue gehängt zur weiteren Verarbeitung durch Threads.
Kann sein, dass ich hier etwas verbockt habe, aber in der Nacht konnte ich da nichts mehr finden.
Eigentlich sollten die Verarbeitungsthreads ja die Eriegnisbehandlung im Mainthread nicht stören. Solange ich Strings übertragen hatte war das auch nicht der Fall.
(Blöderweise spinnt mein USB-Stick schon wieder, so dass ich hier gerade keinen Code dabei habe und ihn gerade nicht posten kann.)

Hat jemand so pauschal eine Idee, wie das Problem Socket.ReceiveLength=0 zu lösen ist?
Stahli
http://www.StahliSoft.de
---
"Jetzt muss ich seh´n, dass ich kein Denkfehler mach...!?" Dittsche (2004)
  Mit Zitat antworten Zitat
Benutzerbild von Zacherl
Zacherl

Registriert seit: 3. Sep 2004
4.629 Beiträge
 
Delphi 10.2 Tokyo Starter
 
#3

AW: Spielwiese - SocketTest

  Alt 22. Dez 2016, 16:04
Hat jemand so pauschal eine Idee, wie das Problem Socket.ReceiveLength=0 zu lösen ist?
Der von dir verlinkte Code hat mehrere Schwächen:
  • Theoretisch ist es möglich, dass die Länge schon nicht in einem Stück ankommt
  • Wenn du zwei Streams hintereinander schickst, kann das letzte Paket von Stream 1 bereits die Länge und Daten von Stream 2 enthalten
Einfachste Vorgehensweise:
  1. Erstmal ALLE eingehenden Daten an einen Buffer (präziser: einer FIFO Datenstruktur) hinten anhängen
  2. Dann in einer Schleife alle vollständigen Pakete abarbeiten (Pseudo-Code):
    Delphi-Quellcode:
    while ((PacketSize = 0) and (BufferSize >= SizeOf(PacketSize)) or
      ((PacketSize > 0) and (BufferSize >= PacketSize)) do
    begin
      if (PacketSize = 0) then
      begin
        // Hier PacketSize vom Anfang des Buffers auslesen, dann alle Daten
        // im Buffer um SizeOf(PacketSize) nach vorne verschieben und schließlich
        // BufferSize um SizeOf(PacketSize) dekrementieren
      end;
      // Vollständiges Paket vorhanden
      if (BufferSize >= PacketSize) then
      begin
        // Hier Paket-Daten vom Anfang des Buffers auslesen, dann alle Daten
        // im Buffer um PacketSize nach vorne verschieben und schließlich
        // BufferSize um PacketSize dekrementieren
      end;
    end;
Durch diese Vorgehensweise solltest du alle Möglichen Fälle abdecken. Aus Performancegründen würde ich noch versuchen die Verschiebe- und besonders die Realloc Operationen des Buffers zu minimieren. Beispielsweise könntest du noch ein zustäzliches Feld BufferPos pflegen, mit dem du dir diese Aktionen sparst (müssten dann nur einmalig nach der Schleife ausgeführt werden, aber nicht bei jedem Durchlauf). Falls deine Pakete nicht in anderen Threads abgearbeitet werden, kannst du dir auch das Auslesen der Paketdaten sparen und deinem Paket-Handler einfach einen Zeiger auf @Buffer[BufferPos] zurückgeben, etc. Auch würde ich den Buffer nie komplett auf Null shrinken, sondern immer mindestens X-Byte alloziiert lassen, damit du bei kleineren Paketen im Optimalfall sogar komplett auf Realloc verzichten kannst.

Edit:
Achso und wie sieht deine Sende-Routine aus? Bei non-blocking Sockets kann MSDN-Library durchsuchenSend auch erfolgreich sein, aber trotzdem keine bzw. nicht alle Daten senden (wenn der Empfangsbuffer zu voll ist). Deshalb musst du hier in einer Schleife selbst dafür sorgen, dass alles verschickt wird. Bei den blocking Sockets kann das nicht passieren, da MSDN-Library durchsuchenSend dann einfach blockiert, bis im Empfangsbuffer wieder Platz ist und alle Daten gesendet werden konnten.
Projekte:
- GitHub (Profil, zyantific)
- zYan Disassembler Engine ( Zydis Online, Zydis GitHub)

Geändert von Zacherl (22. Dez 2016 um 16:08 Uhr)
  Mit Zitat antworten Zitat
Namenloser

Registriert seit: 7. Jun 2006
Ort: Karlsruhe
3.724 Beiträge
 
FreePascal / Lazarus
 
#4

AW: Spielwiese - SocketTest

  Alt 23. Dez 2016, 11:47
Sind es blocking oder non-blocking Sockets?

Theoretisch ist es möglich, dass die Länge schon nicht in einem Stück ankommt
Das sollte eigentlich bei blocking Sockets nicht passieren. Wenn ich 4 Bytes lese, denn bekomme ich auch 4 Bytes, und nicht 3, 2 oder 1. Allerdings ist das im verlinkten Code tatsächlich etwas komisch umgesetzt, weil da ja erst noch abgefragt wird, wieviele Bytes lesbar sind und dann nur so viel auch gelesen wird. Das läuft dem Konzept von blockierenden Sockets eigentlich zuwider.

Blockierender Ansatz wäre eigentlich so:

Server (wie im Original):
Delphi-Quellcode:
var
  lLen: Integer;
  lStream: TStream;
begin
  lStream := TFileStream.Create('c:\testbild.bmp', fmOpenRead);

  lLen := lStream.Size; // grösse Stream ermitteln
  Socket.SendBuf(lLen, SizeOf(lLen)); // Grösse senden

  Socket.SendStream(lStream); // dann das Bild hinten dran...
end;
Client:
Delphi-Quellcode:
var
  lLen: Integer;
  lStream: TStream;
  Buffer: Pointer;
begin
  Socket.RecvBuf(lLen, SizeOf(lLen));

  lStream := TFileStream.Create('c:\testbild.bmp', fmOpenWrite);

  // Im einfachsten Fall. Besser wäre es natürlich, einen Buffer fester Größe
  // zu erzeugen und mit einer Schleife zu arbeiten, damit der RAM nicht gesprengt wird.
  GetMem(Buffer, lLen);
  Socket.ReceiveBuf(Buffer^, lLen);
  lStream.Write(Buffer^, lLen);

  FreeMem(Buffer);
  lStream.free;
end;
Der nicht-blockierende Ansatz wäre so wie Zacherl beschrieben hat.

Wieso genau der Fehler bei dir kommt, weiß ich nicht, aber ich hatte ja schon mal geschrieben, dass blockierend und eventgetrieben sich nicht gut verträgt.
  Mit Zitat antworten Zitat
Benutzerbild von stahli
stahli
Online

Registriert seit: 26. Nov 2003
Ort: Halle/Saale
4.358 Beiträge
 
Delphi 11 Alexandria
 
#5

AW: Spielwiese - SocketTest

  Alt 23. Dez 2016, 12:18
Meine Anwendung ist nicht blockierend.

Ich habe gestern erst mal meinen bisherigen Ansatz in einer repeat-Schleife gepackt und den Ablauf etwas geloggt.

Im Eingang in die Methode hat ReceiveLength z.B. 8 ausgegeben.
Drei Zeilen weiter dann ggf. 100.

Also verändert sich der Wert auch schon mal dynamisch.

Until habe ich dann bei "ReceiveLength = 0" veranlasst.

Im Ergebnis holt sich die Behandlung zunächst einen Integerwert für die Streamgröße sobald 4 Bytes verfügbar sind und den Stream selbst sobald dieser komplett verfügbar ist.

So sollte das m.E. ja eigentlich funktionieren - hat es auch.

Aber sobald ich die Logs raus genommen habe lief es wieder nicht mehr.
Da muss wohl irgendwo anders noch ein (Thread-)Problem sein.

In der Nacht um 3 habe ich dann erst mal aufgegeben. :-/

Hier mal der Quelltext (mit dem Logging), der funktionierte.

PS: Ich schaue mir auch Zacherls Hinweis nochmal an, aber ich dachte, meine aktuelle Lösung würde quasi das Gleiche machen...

Delphi-Quellcode:
...Send
                  MS := TMemoryStream.Create;
                  lSL.SaveToStream(MS);
                  MS.Seek(0, soBeginning);
                  MSSize := MS.Size;
                  fServerSocket.Socket.Connections[I].SendBuf(MSSize, SizeOf(MSSize));
                  if fServerSocket.Socket.Connections[I].SendStream(MS) then
                    LogSL('==>', '', lSL)
                  else
                    LogSL('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx==>', '', lSL);


//==============================================

...Read
var
  iLen: Integer;
  Bfr: Pointer;
  SL: TStringList;
  lP: Int64;
  rl: Integer;
  iL: Integer;
begin
  if fff then
    Codesite.Send('ffffffffffffffffffffffffffff');

  fff := True;

  repeat
    rl := Socket.ReceiveLength;
    Codesite.Send('Read: ' + IntToStr(rl));

    Codesite.Send('a: ' + IntToStr(Socket.ReceiveLength));
    if not Assigned(fMS) then
    begin
      Codesite.Send('not Assigned(fMS)');
      if Socket.ReceiveLength >= SizeOf(fMSSize) then
      begin
        Codesite.Send('Socket.ReceiveLength >= SizeOf(fMSSize)');
        iL := Socket.ReceiveBuf(fMSSize, SizeOf(fMSSize));
        Codesite.Send('reading=' + IntToStr(iL));
        fMS := TMemoryStream.Create;
      end
      else
        Codesite.Send('NOT Socket.ReceiveLength >= SizeOf(fMSSize)');
    end
    else
      Codesite.Send('NOT not Assigned(fMS)');

    Codesite.Send('b: ' + IntToStr(Socket.ReceiveLength));
    if Assigned(fMS) then
    begin
      Codesite.Send('Assigned(fMS)');
      iLen := Socket.ReceiveLength;
      Codesite.Send('*** ' + IntToStr(iLen) + '/' + IntToStr(fMSSize));
      iLen := Min(fMSSize, iLen);
      GetMem(Bfr, iLen);
      try
        iL := Socket.ReceiveBuf(Bfr^, iLen);
        Codesite.Send('reading=' + IntToStr(iL));
        Dec(fMSSize, fMS.Write(Bfr^, iLen));
        if Socket.ReceiveLength > 0 then
        begin
          Codesite.Send('!!!REST');
        end;
        // lP := fMS.Position;
        // fMS.Position := 0;
        // SL := TStringList.Create;
        // SL.LoadFromStream(fMS);
        // SL.Insert(0, IntToStr(SL.Count));
        // LogSL('<--', '', SL);
        // FreeAndNil(SL);
        // fMS.Position := lP;
      finally
        FreeMem(Bfr);
      end;

      Codesite.Send('c: ' + IntToStr(Socket.ReceiveLength));
      if fMSSize = 0 then
      begin
        Codesite.Send('fMSSize = 0');
        fMS.Position := 0;
        SL := TStringList.Create;
        SL.LoadFromStream(fMS);
        SL.Insert(0, IntToStr(SL.Count));
        FreeAndNil(fMS);
        fMessageHandlerServer.RegisterInSL(SL, Socket);
        FreeAndNil(SL);
      end
      else
        Codesite.Send('NOT fMSSize = 0');
    end
    else
      Codesite.Send('NOT Assigned(fMS)');
    Codesite.Send('----: ' + IntToStr(Socket.ReceiveLength));
  until (Socket.ReceiveLength = 0);
  Codesite.Send('----------------------: ' + IntToStr(Socket.ReceiveLength));
  fff := false;
end;
Angehängte Dateien
Dateityp: txt log.txt (81,4 KB, 5x aufgerufen)
Stahli
http://www.StahliSoft.de
---
"Jetzt muss ich seh´n, dass ich kein Denkfehler mach...!?" Dittsche (2004)
  Mit Zitat antworten Zitat
Namenloser

Registriert seit: 7. Jun 2006
Ort: Karlsruhe
3.724 Beiträge
 
FreePascal / Lazarus
 
#6

AW: Spielwiese - SocketTest

  Alt 23. Dez 2016, 14:38
Meine Anwendung ist nicht blockierend.
Ah, okay, das ist dann wohl neu.

Also ich muss vorweg sagen, ich habe mit TClientSocket/TServerSocket nie gearbeitet, wohl aber mit der dahinterliegenden BSD-Socket bzw. WinSock-API. Dort ist es so, dass die Funktion recv() einen Wert zurückliefert, der angibt, wieviele Bytes tatsächlich gelesen wurde (oder -1 falls keine Bytes gelesen werden konnten oder im Falle eines Fehlers). Ein Rückgabewert von 0 bedeutet, dass die Verbindung geschlossen wurde. Das könnte hier der Fall sein. Das kannst du aber nur herausfinden, indem du tatsächlich ein recv ausführst, ReceiveLength ist irreführend.

Wenn du deinen Code nicht überall ändern willst, kannst du einfach 1 Byte lesen und als Flag MSG_PEEK übergeben. So wird das Byte nicht "konsumiert" und der restliche Programmablauf bleibt unbeeinflusst.
Delphi-Quellcode:
var
  Dummy: Byte;
begin
  if Socket.ReceiveBuf(Dummy, 1, MSG_PEEK) = 0 then
    // Verbindung geschlossen;
  Mit Zitat antworten Zitat
Benutzerbild von stahli
stahli
Online

Registriert seit: 26. Nov 2003
Ort: Halle/Saale
4.358 Beiträge
 
Delphi 11 Alexandria
 
#7

AW: Spielwiese - SocketTest

  Alt 23. Dez 2016, 15:07
Meine Anwendung ist nicht blockierend.
Ah, okay, das ist dann wohl neu.
Hier im Thread und Testprojekt ging es immer um non blocking.

Mein echtes Projekt hatte Indy genutzt, also blocking.
Das stelle ich derzeit entsprechend um.


@stahli:
Deine Send-Routine enthält auf jeden Fall das von mir beschriebene Problem, dass MSDN-Library durchsuchensend deine Daten bei non-blocking Sockets leider NICHT garantiert komplett verschickt:

...

Achso: Den TCP Stack direkt buffern zu lassen - wie du es momentan machst -, ist meiner Meinung nach keine gute Idee. Die Buffergröße hängt von verschiedenen Parametern ab und kann sich von System zu System stark unterscheiden. Wenn du dann versuchst eine Nachricht zu schicken, die nicht komplett in den Buffer passt, wird deine Verbindung dauerhaft blockiert sein, da du in diesem Falle ja niemals Daten ausliest (also auch keinen neuen Platz im Receive-Buffer schaffst).
Zumindest geben SendStream immer True zurück. Also sollten sie erfolgreich verlaufen.
Die Datenmengen sind derzeit auch sehr klein, so dass diese kein Problem darstellen sollte.

Ich gehe derzeit eher von einem Threading-Problem aus.

Aber ich schaue mir das die nächsten Tage nochmal genauer an.


Danke Euch!
Stahli
http://www.StahliSoft.de
---
"Jetzt muss ich seh´n, dass ich kein Denkfehler mach...!?" Dittsche (2004)
  Mit Zitat antworten Zitat
Benutzerbild von Zacherl
Zacherl

Registriert seit: 3. Sep 2004
4.629 Beiträge
 
Delphi 10.2 Tokyo Starter
 
#8

AW: Spielwiese - SocketTest

  Alt 23. Dez 2016, 14:45
Dort ist es so, dass die Funktion recv() einen Wert zurückliefert, der angibt, wieviele Bytes tatsächlich gelesen wurde (oder -1 falls keine Bytes gelesen werden konnten oder im Falle eines Fehlers). Ein Rückgabewert von 0 bedeutet, dass die Verbindung geschlossen wurde. Das könnte hier der Fall sein. Das kannst du aber nur herausfinden, indem du tatsächlich ein recv ausführst, ReceiveLength ist irreführend.
Das ist bei den (dummen ) non-blocking Sockets leider auch wieder mal komplizierter:
Zitat von MSDN recv():
If no incoming data is available at the socket, the recv call blocks and waits for data to arrive according to the blocking rules defined for WSARecv with the MSG_PARTIAL flag not set unless the socket is nonblocking. In this case, a value of SOCKET_ERROR is returned with the error code set to WSAEWOULDBLOCK. The select, WSAAsyncSelect, or WSAEventSelect functions can be used to determine when more data arrives.
@stahli:
Deine Send-Routine enthält auf jeden Fall das von mir beschriebene Problem, dass MSDN-Library durchsuchensend deine Daten bei non-blocking Sockets leider NICHT garantiert komplett verschickt:
Zitat von MSDN send():
If no buffer space is available within the transport system to hold the data to be transmitted, send will block unless the socket has been placed in nonblocking mode. On nonblocking stream oriented sockets, the number of bytes written can be between 1 and the requested length, depending on buffer availability on both the client and server computers. The select, WSAAsyncSelect or WSAEventSelect functions can be used to determine when it is possible to send more data.
Achso: Den TCP Stack direkt buffern zu lassen - wie du es momentan machst -, ist meiner Meinung nach keine gute Idee. Die Buffergröße hängt von verschiedenen Parametern ab und kann sich von System zu System stark unterscheiden. Wenn du dann versuchst eine Nachricht zu schicken, die nicht komplett in den Buffer passt, wird deine Verbindung dauerhaft blockiert sein, da du in diesem Falle ja niemals Daten ausliest (also auch keinen neuen Platz im Receive-Buffer schaffst).
Projekte:
- GitHub (Profil, zyantific)
- zYan Disassembler Engine ( Zydis Online, Zydis GitHub)

Geändert von Zacherl (23. Dez 2016 um 14:51 Uhr)
  Mit Zitat antworten Zitat
Namenloser

Registriert seit: 7. Jun 2006
Ort: Karlsruhe
3.724 Beiträge
 
FreePascal / Lazarus
 
#9

AW: Spielwiese - SocketTest

  Alt 23. Dez 2016, 15:02
Das ist bei den (dummen ) non-blocking Sockets leider auch wieder mal komplizierter:
Zitat von MSDN recv():
If no incoming data is available at the socket, the recv call blocks and waits for data to arrive according to the blocking rules defined for WSARecv with the MSG_PARTIAL flag not set unless the socket is nonblocking. In this case, a value of SOCKET_ERROR is returned with the error code set to WSAEWOULDBLOCK. The select, WSAAsyncSelect, or WSAEventSelect functions can be used to determine when more data arrives.
Es ist schon so, wie ich es geschrieben habe. Die Dokumentation liest sich nur leider manchmal etwas verwirrend.

Ich versuche es noch mal in Tabellenform:
Code:
                               | Verbindung besteht | Verbindung geschlossen
Lesbare Bytes vorhanden        | > 0                | --
Keine lesbaren Bytes vorhanden | -1                 | 0
Fehler                         | -1                 | -1

(Wie zur Hölle verwendet man den [TABLE]-BBCode? )
  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 22:03 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