Netzwerkclient zeigt komisches Verhalten

Für allgemeine Fragen zur Programmierung mit PureBasic.
Vi-P
Beiträge: 30
Registriert: 18.01.2013 18:56

Netzwerkclient zeigt komisches Verhalten

Beitrag von Vi-P »

Hallo liebe Netzwerkspezialisten.

Habe einen einfachen TCP Client programmiert (frei nach den üblichen snippets), der Daten von einem ESP8266 abholen soll.
Ca. alle 10 sec. werden Pakete an einen verbundenen Client geschickt. 18 Blöcke mit ein paar Bytes und dann ist wieder für 10 Sek. Ruhe.
1. Putty holt die Daten auf dem gleichen Rechner zuverlässig und regelmäßig ab. Zwischen den einzelnen Blöcken kann es mal ein paar ms Verzögerungen geben, aber gerade eben an der Wahrnehmungsgrenze. (Also kann es doch eigentlich nicht am ESP oder der Infrastruktur liegen)
2. Durch Abändern der Position und Wert von delay () sowie der Anzahl der auf einmal gelesenen Bytes habe ich zumindest einen Teilerfolg erreicht. Der Datentransfer bleibt nicht nach 0-5 Zeilen endgültig stehen. Bisweilen stockt aber der Datenfluss für 5-30 Sec. oder mehr. Plötzlich wird alles nachgeholt. Wie wenn Windows die Daten nicht hergeben möchte. Aber das kann es doch eigentlich nicht sein.
Habe ich mal wieder was grundlegend falsch gemacht oder bin ich einem Bug auf der Spur?
System: Windows 10, kein Virenscanner, Firewall off

Code: Alles auswählen

Global *bufr = AllocateMemory (10000)
Global ipcon

Procedure.s Getline ()
  erg.s = ""
  Repeat
    evnt = NetworkClientEvent (ipcon)
    If #PB_NetworkEvent_Data
      rec = ReceiveNetworkData (ipcon, *bufr, 1)
      erg + PeekS (*bufr, rec, #PB_Ascii)
      Debug erg
      Continue
    EndIf
    Delay (1)
  Until FindString (erg, ";X")
  ProcedureReturn Mid (erg, 1, FindString (erg, ";X"))
EndProcedure

Debug InitNetwork ()
Repeat
  ipcon = OpenNetworkConnection ("192.168.200.188", 80, #PB_Network_TCP)
Until ipcon
Debug "Connection"

If OpenConsole ()
  PrintN ("Serverconsole:") 
EndIf

Repeat
  PrintN (Getline ())
ForEver
Benutzeravatar
STARGÅTE
Kommando SG1
Beiträge: 6999
Registriert: 01.11.2005 13:34
Wohnort: Glienicke
Kontaktdaten:

Re: Netzwerkclient zeigt komisches Verhalten

Beitrag von STARGÅTE »

Also zunächst mal fällt mir auf, dass hier:

Code: Alles auswählen

evnt = NetworkClientEvent (ipcon)
If #PB_NetworkEvent_Data
die If-Abfrage immer Wahr ist, weil du garnicht nach evnt fragst. Es sollte sicher evnt = #PB_NetworkEvent_Data oder?
Danach stellt sich mir die Frage was da für Daten ankommen?
Wirklich außschließlich ASCII Zeichen? Ohne NUL-Zeichen?
PB 6.01 ― Win 10, 21H2 ― Ryzen 9 3900X, 32 GB ― NVIDIA GeForce RTX 3080 ― Vivaldi 6.0 ― www.unionbytes.de
Aktuelles Projekt: Lizard - Skriptsprache für symbolische Berechnungen und mehr
Vi-P
Beiträge: 30
Registriert: 18.01.2013 18:56

Re: Netzwerkclient zeigt komisches Verhalten

Beitrag von Vi-P »

Herzlichen dank Stargate für den Tip, sowas kommt davon, wenn man Code fürs Forum "aufbereitet".

2 Fragen bleiben zurück:
1. Wenn´s nix zum abholen gibt, krieg ich halt 0 Zeichen, sollte eigentlich auch nicht stören, außer dass die CPU belastet wird (??) Wenn derartiges Verhalten ein Problem verursacht, wäre es allerdings sinnvoll einen Laufzeitfehler auszugeben. (Vielleicht kann ja jemand noch seine Meinung hierzu kund tun)
2. Der ursprüngliche Code, der nicht wie gewünscht funktionierte, war mit einer (entarteten) Switch Anweisung (aus einem anderen Code entnommen, abgeändert und verkürzt), die ich so nicht ins Forum stellen wollte und mich entschied eine if Anweisung draus zu machen:
komischerweise funktioniert z.Z. dieser ursprüngliche Code auch, was er vorher nicht tat. Werde nochmals versuchen der Sache nachzugehen und im Fall interessanter Ergebnisse diese posten.

Code: Alles auswählen

Procedure.s Getline ()
  
  erg.s = ""
  Repeat
    evnt = NetworkClientEvent (ipcon)
    Delay (1)
    Select evnt
      Case #PB_NetworkEvent_Data
         rec = ReceiveNetworkData (ipcon, *bufr, 1)
         erg + PeekS (*bufr, rec, #PB_Ascii)
     EndSelect
  Until FindString (erg, ";X")
  ProcedureReturn Mid (erg, 1, FindString (erg, ";X"))
EndProcedure
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8679
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 32 GB DDR4-3200
Ubuntu 22.04.3 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken
Kontaktdaten:

Re: Netzwerkclient zeigt komisches Verhalten

Beitrag von NicTheQuick »

Wenn du ein #PB_NetworkEvent_Data kriegst, solltest du solange Daten lesen bis ReceiveNetworkData 0 zurückgibt. Du gehst davon aus, dass nach jedem Byte, das du liest, ein neues Event produziert wird. Dem ist aber nicht so. Netzwerkpakete können auf dem Weg zwischen zwei Computern beliebig zerlegt und wieder zusammengefügt werden. Das heißt selbst wenn der Sender jeden Buchstaben einzeln schickt, können die am Ende alle in einem Paket ankommen. Oder umgekehrt.
Bild
Benutzeravatar
STARGÅTE
Kommando SG1
Beiträge: 6999
Registriert: 01.11.2005 13:34
Wohnort: Glienicke
Kontaktdaten:

Re: Netzwerkclient zeigt komisches Verhalten

Beitrag von STARGÅTE »

Dem muss ich widersprechen NicTheQuick.
Wenn man die Daten nicht bis zum Ende aus dem Empfangspuffer ausließt, dann wird Network*Event auch wieder ein #PB_NetworkEvent_Data zurückgeben. Andersrum besteht nämlich das Problem, wenn kein einziger Byte mehr im Empfangspuffer ist, dann hängt der Code solange bei ReceiveNetworkData fest, bis wieder was zu lesen kommt (was ja nicht erwünscht ist).
Hier die entsprechenden Beispiele:

Alles wird empfangen und die Schleife hängt nicht:

Code: Alles auswählen

Enumeration
	#Server
EndEnumeration

#Port = 6000

InitNetwork()
CreateNetworkServer(#Server, #Port)
Define ConnectionID.i = OpenNetworkConnection("localhost", #Port)
Define ClientID.i

Repeat
	Select NetworkServerEvent(#Server)
		Case #PB_NetworkEvent_Connect
			ClientID = EventClient()
			Break
	EndSelect
ForEver

SendNetworkString(ClientID, "Hallo", #PB_Ascii)

Define Char.a
Repeat
	Debug "."
	Select NetworkClientEvent(ConnectionID)
		Case #PB_NetworkEvent_Data
			Debug ReceiveNetworkData(ConnectionID, @Char, 1)
			Debug Chr(Char)
	EndSelect
	Delay(100)
ForEver
Alles wird empfangen, aber der Code hängt sich bei ReceiveNetworkData() auf:

Code: Alles auswählen

Enumeration
	#Server
EndEnumeration

#Port = 6000

InitNetwork()
CreateNetworkServer(#Server, #Port)
Define ConnectionID.i = OpenNetworkConnection("localhost", #Port)
Define ClientID.i

Repeat
	Select NetworkServerEvent(#Server)
		Case #PB_NetworkEvent_Connect
			ClientID = EventClient()
			Break
	EndSelect
ForEver

SendNetworkString(ClientID, "Hallo", #PB_Ascii)

Define Char.a
Repeat
	Debug "."
	Select NetworkClientEvent(ConnectionID)
		Case #PB_NetworkEvent_Data
			While ReceiveNetworkData(ConnectionID, @Char, 1) > 0
				Debug Chr(Char)
			Wend
	EndSelect
	Delay(100)
ForEver
Das heißt, eine 0 wird (zumindest auf der Clientseite) von ReceiveNetworkData() nicht zurückgegeben!
Auf der Server-Seite ist das verhalten ein bisschen anders, aber in diesem Fall hier, wird ja eine Verbindung aufgebaut.
PB 6.01 ― Win 10, 21H2 ― Ryzen 9 3900X, 32 GB ― NVIDIA GeForce RTX 3080 ― Vivaldi 6.0 ― www.unionbytes.de
Aktuelles Projekt: Lizard - Skriptsprache für symbolische Berechnungen und mehr
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8679
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 32 GB DDR4-3200
Ubuntu 22.04.3 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken
Kontaktdaten:

Re: Netzwerkclient zeigt komisches Verhalten

Beitrag von NicTheQuick »

STARGÅTE hat geschrieben:Dem muss ich widersprechen NicTheQuick.
Wenn man die Daten nicht bis zum Ende aus dem Empfangspuffer ausließt, dann wird Network*Event auch wieder ein #PB_NetworkEvent_Data zurückgeben. Andersrum besteht nämlich das Problem, wenn kein einziger Byte mehr im Empfangspuffer ist, dann hängt der Code solange bei ReceiveNetworkData fest, bis wieder was zu lesen kommt (was ja nicht erwünscht ist).
Hoppla. Danke für's Verbessern. :oops:
Bild
Vi-P
Beiträge: 30
Registriert: 18.01.2013 18:56

Re: Netzwerkclient zeigt komisches Verhalten

Beitrag von Vi-P »

Erst mal vielen Dank, an alle, die sich hier beteiligen.

Wenn "ReceiveNetworkData" keine Daten zum Abruf vorfindet, warten muss und somit die Programmausführung blockiert, dann müsste doch eigentlich mit dem Empfang des nächsten Blockes die Funktion mit Daten (Hier ein Byte) zurückkehren? Tut sie meist, aber halt irgendwann (kann auch ziemlich dauern).

Der Hinweis im Handbuch: "should be called only after having received a #PB_NetworkEvent_Data event" ist auch etwas wachsweich. Und dann der Satz "If 'Result' is equal to DataBufferLength then more data is available to be read." stimmt der wirklich? Schließlich der Satz "If an error occurred on the connection (link broken, connection close by the server etc...) 'Result' will be -1." könnte einen auch fehlleiten, etwas zu programmieren was danach Probleme macht. Das soll jetzt nicht mein fehlerhaftes if Statement rechtfertigen. Schön wäre m.E. entweder, dass die Funktion in der nächsten Version etwas "robuster" gemacht wird oder dass im Handbuch stärker vor dem Problem fehlender Daten gewarnt wird. In jedem Fall kann man jetzt nützliche Tipps hierzu im Forum finden, das ist ja auch schon was.

Nach wie vor ist noch ungeklärt, warum meine Urversion nicht funktionierte. Sollte ich was Relevantes rausfinden, trage ich das noch nach.
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8679
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 32 GB DDR4-3200
Ubuntu 22.04.3 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken
Kontaktdaten:

Re: Netzwerkclient zeigt komisches Verhalten

Beitrag von NicTheQuick »

If 'Result' is equal to DataBufferLength then more data is available to be read.
Diesen Satz finde ich auch etwas fragwürdig. Was ist, wenn Result = DataBufferLength und der Netzwerkpuffer tatsächlich leer ist? Dann versucht man noch einen weiteren Aufruf und das Programm hängt.
Bild
Benutzeravatar
mk-soft
Beiträge: 3701
Registriert: 24.11.2004 13:12
Wohnort: Germany

Re: Netzwerkclient zeigt komisches Verhalten

Beitrag von mk-soft »

PB Hilfe: ReceiveNetworkData()
Gibt die Anzahl an empfangenen Bytes zurück. Ist das 'Ergebnis' gleich der 'DatenPufferLänge', dann sind weitere Daten zum Einlesen verfügbar. Wenn ein Fehler bei der Verbindung auftrat (Verbindung unterbrochen, Verbindung durch den Server beendet, etc.), wird 'Ergebnis' gleich -1 sein.
Dieser Satz ist etwas unglücklich gewählt, oder sogar falsch. Wenn der *Datenbuffer genau so groß ist wie die empfangende Daten, ist dieser dann leer.
Somit muss man auf jedem fall auf das Event #PB_NetworkEvent_Data warten. Das #PB_NetworkEvent_Data steht auf jedem fall an, wenn sich noch Daten zum abholen bereit stehen.
Alles ist möglich, fragt sich nur wie...
Projekte ThreadToGUI / EventDesigner V3 / OOP-BaseClass-Modul
Downloads auf MyWebspace / OneDrive
Benutzeravatar
mk-soft
Beiträge: 3701
Registriert: 24.11.2004 13:12
Wohnort: Germany

Re: Netzwerkclient zeigt komisches Verhalten

Beitrag von mk-soft »

Da hier das erste mal mit Netzwerk gearbeitet wird, ein paar grundlegende infos und regeln.

ReceiveNetworkData holt alles was sich im Empfangsbuffer in der Zwischenzeit an Daten aufgelaufen ist.
SendNetworkData übergibt die die Daten an den Sendebuffer. Der Rückgabewert gibt nur an, das die Daten an den Sendebuffer übergeben wurde und nicht das die Daten gesendet wurde.

Die Daten werden als Paket über das Netzwerk übertragen. Daten werden je nach PDU größe in mehreren Pakete versendet.
Bei TCP wird nur gewährleistet das die Daten bis 64kB konsistent in der richtigen Reihenfolge übertragen werden.
Nicht das die Daten erfolgreich Komplet gesendet oder empfangen wurde.
Somit kann es sein das bei Aufruf von ReceiveNetworkData bereits ein Teil der Daten aus dem Empfangsbuffer gelesen werden kann, obwohl noch nicht alle Pakete empfangen wurde.

Das die Daten vollständig gesendet oder empfangen wurde, ist die Aufgabe des Programmierers.

Um nicht das Programm zu blockieren, ist es immer zu empfehlen, die Kommunikation in einem Thread auszulagern.

Hier mal ein Grundsätzlicher Aufbau für einen Client der Textdaten empfängt.
Das Trennzeichen haben ich hier mal das Return (#CR$ - Zeichen 13) genommen.

Ist aber noch nicht alles drin. Zum Beispiel wenn der Server Ausfällt oder das die Netzwerkverbindung gestört ist, kann es sein das der Client diese nicht merk.
Somit sollte noch ein Timeout programmiert, wenn vom Server keine Daten mehr kommen und eine Reconnect versucht werden.

Den code habe ich aber nicht nicht Komplet getestet.

Code: Alles auswählen

;-TOP

CompilerIf Not #PB_Compiler_Thread
  CompilerError "Use Compiler Option ThreadSafe!"
CompilerEndIf

If Not InitNetwork()
  MessageRequester("Error", "Faild init Network!")
  End
EndIf

Structure udtClientData
  ; Thread
  ThreadID.i
  Exit.i
  ; Connection
  Hostname.s
  Port.i
  ConnectionID.i
  ; Data
  *Buffer
  Empfang.s
EndStructure


Procedure thNetworkClient(*data.udtClientData)
  Protected receive_len, pos, text.s
  
  With *data
    Debug "Init Thread Resources"
    \Buffer = AllocateMemory(8192)
    
    Debug "Init Network Connection"
    
    ; Einsprungpunkt Verbindung wieder herstellen
    Reconnect:
    
    Repeat
      If \Exit
        Break
      EndIf
      \ConnectionID = OpenNetworkConnection(\Hostname, \Port, #PB_Network_TCP)
      If Not \ConnectionID
        ; 5 Sekunden warten bis zum nächsten versuch
        Delay(5000)
      EndIf
    Until \ConnectionID
    
    If \ConnectionID
      Debug "Network Connection ok"
    EndIf
    
    Repeat
      If \Exit
        Break
      EndIf
      
      Select NetworkClientEvent(\ConnectionID)
        Case #PB_NetworkEvent_Data
          ; Daten zum abholen im Buffer
          receive_len = ReceiveNetworkData(\ConnectionID, \Buffer, 8192)
          ; Daten als String auslesen und zum String hizufügen
          \Empfang + PeekS(\Buffer, receive_len, #PB_Ascii)
          ; String auf Endezeichen prüfen. Kann auch mehrere enthalten
          Repeat
            pos = FindString(\Empfang, #CR$) ; Endezeichen Chr(13)
            If pos
              ; Daten übernehmen bis Endezeichen
              text = Left(\Empfang, pos - 1)
              ; Empfangs String korrigieren
              \Empfang = Mid(\Empfang, pos + 1) ; 
              ;TODO -> Hier den Part abarbeiten
              Debug text
              ; <-
            EndIf
          Until pos = 0
          
        Case #PB_NetworkEvent_Disconnect
          ; Server hat die Verbindung getrennt
          CloseNetworkConnection(\ConnectionID)
          \ConnectionID = 0
          Break
          
        Case #PB_NetworkEvent_None
          ; Nicht zu tun. Kleine Pause (CPU schonen)
          Delay(10)
          
      EndSelect
    ForEver
    
    If Not \Exit
      Goto Reconnect
    EndIf
    
    ; Verbindung abbauen
    If \ConnectionID
      CloseNetworkConnection(\ConnectionID)
      \ConnectionID = 0
    EndIf
    
    Debug "Release Thread Resources"
    FreeMemory(\Buffer)
    \ThreadID = 0
    \Exit = 0
  EndWith
  
EndProcedure

;- Main

Global ClientData.udtClientData
ClientData\Hostname = "192.168.200.188"
ClientData\Port = 80

If OpenWindow(0, 50, 40, 300, 150, "Test Network Client", #PB_Window_SystemMenu)
  
  ClientData\ThreadID = CreateThread(@thNetworkClient(), @ClientData)
  
  Repeat
    Select WaitWindowEvent()
      Case #PB_Event_CloseWindow
        Break
        
    EndSelect
    
  ForEver
  
  ; Thread beenden
  ClientData\Exit = #True
  ; Warten auf Thread
  If WaitThread(ClientData\ThreadID, 10000) = 0
    ; Thread beenden fehlgeschlagen
    KillThread(ClientData\ThreadID)
  EndIf
  
EndIf
Alles ist möglich, fragt sich nur wie...
Projekte ThreadToGUI / EventDesigner V3 / OOP-BaseClass-Modul
Downloads auf MyWebspace / OneDrive
Antworten