ins Netzwerk senden mit Threads

Für allgemeine Fragen zur Programmierung mit PureBasic.
stevie1401
Beiträge: 700
Registriert: 19.10.2014 15:51
Kontaktdaten:

ins Netzwerk senden mit Threads

Beitrag von stevie1401 »

Mit folgender Procedure sende ich an die Clients:

Code: Alles auswählen


Procedure Sende(aClientId,aStr.s)
  Protected counter,i,sentBytes,gesendet,timer,zeit,l,ok
  Protected.s nname
  ok=1
  l=Len(astr)
  l=StringByteLength(astr, #PB_Ascii)  ;soll besser sein als len()
  timer=Date()
  If aClientId>0 
    Repeat
      If aClientId>0 
        gesendet=SendNetworkString(aClientId, aStr, #PB_Ascii)
        If gesendet=-1
          Delay(20)
        Else
          sentBytes+gesendet
        EndIf
        zeit=Date()-timer
      EndIf
    Until sentBytes=L Or zeit>60 Or aclientid<1
    If zeit>60
      ok=0  
    EndIf
    
    If ok=1
      Debug "Iconstringlänge: "+Str(sentBytes)+" gesendet (Original: "+Str(l)
    EndIf
    
    ProcedureReturn ok
  EndIf
  
EndProcedure


Beispiel:

Code: Alles auswählen

for i=1 to 100
  sende (SpielerId(i),aLongLongString)
next i
Nun kann es sein, dass einer der Clients eine langsame Verbindung hat. Das bremst beim Senden den ganzen Server aus.
Ich möchte dass der Server jeden einzelnen Client in einem einzelnen Thread sendet, in der Hoffnung, dass dann nicht alles ausgebremst wird.
Leider kenne ich mich mit Threads überhaupt nicht aus und weiss auch nicht, ob man das überhaupt machen kann.
Hat jemand einen Tip für mich?
Ich programmiere nur noch mit Linux.
Linux Mint 21.x
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8807
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 64 GB DDR4-3200
Ubuntu 24.04.2 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken

Re: ins Netzwerk senden mit Threads

Beitrag von NicTheQuick »

Eigentlich macht man das nur so, also mit Threads. Dein Beispielcode ist quasi das Gegenteil von dem wie man es richtig macht. Und das hast du ja auch erkannt. :allright:
Das Problem ist nur, dass Purebasic mal wieder nicht so gut darauf ausgelegt ist, alleine schon wegen dem Eventsystem der Networklib. Es gibt hier im Forum eine Library in Quellcode-Form, die das alles schon kann. Am besten nutzt du also die. Durch die Forensuche müsste man sie eigentlich finden.
Benutzeravatar
Sicro
Beiträge: 963
Registriert: 11.08.2005 19:08
Kontaktdaten:

Re: ins Netzwerk senden mit Threads

Beitrag von Sicro »

Das ist kein Problem, solange pro Client nur ein Thread zum Senden verwendet wird. Andernfalls kommen die Daten nicht in der Reihenfolge an, wie sie gesendet wurden.

Wenn der zu sendende String für alle Clients gleich ist, kannst du es so machen:

Code: Alles auswählen

Global aStr.s

Procedure Sende(aClientId)
  Protected counter,i,sentBytes,gesendet,timer,zeit,l,ok
  Protected.s nname
  ok=1
  l=StringByteLength(astr, #PB_Ascii)
  timer=ElapsedMilliseconds()
  If aClientId>0
    Repeat
      gesendet=SendNetworkString(aClientId, aStr, #PB_Ascii)
      If gesendet=-1
        Delay(20)
      Else
        sentBytes+gesendet
      EndIf
      zeit=ElapsedMilliseconds()-timer
    Until sentBytes=L Or zeit>60
    If zeit>60
      ok=0 
    EndIf
   
    If ok=1
      Debug "Iconstringlänge: "+Str(sentBytes)+" gesendet (Original: "+Str(l)
    EndIf
   
    ProcedureReturn ok
  EndIf
 
EndProcedure

aStr = "Mein String"

For i=0 To 99
  CreateThread(@sende(), SpielerId(i))
Next i
Hast du unterschiedliche Strings für jeden Client, wäre es so lösbar:

Code: Alles auswählen

Structure SpielerStruc
  ClientID.i
  String.s
EndStructure

Dim Spieler.SpielerStruc(99)

Procedure Sende(*Spieler.SpielerStruc)
  Protected counter,i,sentBytes,gesendet,timer,zeit,l,ok
  Protected.s nname
  ok=1
  l=StringByteLength(*Spieler\String, #PB_Ascii)
  timer=ElapsedMilliseconds()
  If *Spieler\ClientID>0
    Repeat
      gesendet=SendNetworkString(*Spieler\ClientID, *Spieler\String, #PB_Ascii)
      If gesendet=-1
        Delay(20)
      Else
        sentBytes+gesendet
      EndIf
      zeit=ElapsedMilliseconds()-timer
    Until sentBytes=L Or zeit>60
    If zeit>60
      ok=0
    EndIf
   
    If ok=1
      Debug "Iconstringlänge: "+Str(sentBytes)+" gesendet (Original: "+Str(l)
    EndIf
   
    ProcedureReturn ok
  EndIf
 
EndProcedure

Spieler(0)\ClientID = ; SpielerID
Spieler(0)\String   = "String für einen Client"

Spieler(1)\ClientID = ; SpielerID
Spieler(1)\String   = "String für anderen Client"

For i=0 To 1
  CreateThread(@sende(), @Spieler(i))
Next i
stevie1401 hat geschrieben:

Code: Alles auswählen

l=StringByteLength(astr, #PB_Ascii)  ;soll besser sein als len()
StringByteLength() gibt eben die Bytes zurück, welche für den String benötigt werden und Len() nur die Zeichenanzahl. Bei #PB_Ascii geben StringByteLength() und Len() die gleiche Zahl zurück, weil im Ascii-Format alle Zeichen nur ein Byte brauchen. Andere Formate brauchen hingegen bei vielen Zeichen mehrere Bytes.
stevie1401 hat geschrieben:

Code: Alles auswählen

timer=Date()
[...]
zeit=Date()-timer
Date() ist hier ungeeignet, weil es zu Problemen führt, wenn der Benutzer oder das System das Datum (Uhrzeit) zurückstellt und der Wert von "zeit" dadurch negativ wird.

Wie ich in deinem anderem Thread schon erwähnt habe, habe ich ein Modul geschrieben, das das Senden und Empfangen von Strings, Binärdaten und Dateien vereinfacht. Das Senden per Threads ist dort ebenfalls möglich.
http://www.purebasic.fr/german/viewtopi ... =8&t=28989
Bild
Warum OpenSource eine Lizenz haben sollte :: PB-CodeArchiv-Rebirth :: Pleasant-Dark (Syntax-Farbschema) :: RegEx-Engine (kompiliert RegExes zu NFA/DFA)
Manjaro Xfce x64 (Hauptsystem) :: Windows 10 Home (VirtualBox) :: Neueste PureBasic-Version
stevie1401
Beiträge: 700
Registriert: 19.10.2014 15:51
Kontaktdaten:

Re: ins Netzwerk senden mit Threads

Beitrag von stevie1401 »

Erst einmal vielen vielen Dank!!


Würde es auch so gehen?

Code: Alles auswählen


Global aStr.s

Procedure Sende(aClientId)
  Protected counter,i,sentBytes,gesendet,timer,zeit,l,ok
  Protected.s nname
  ok=1
  l=StringByteLength(astr, #PB_Ascii)
  timer=ElapsedMilliseconds()
  If aClientId>0
    Repeat
      gesendet=SendNetworkString(aClientId, aStr, #PB_Ascii)
      If gesendet=-1
        Delay(20)
      Else
        sentBytes+gesendet
      EndIf
      zeit=ElapsedMilliseconds()-timer
    Until sentBytes=L Or zeit>60
    If zeit>60
      ok=0 
    EndIf
   
    If ok=1
      Debug "Iconstringlänge: "+Str(sentBytes)+" gesendet (Original: "+Str(l)
    EndIf
   
    ProcedureReturn ok
  EndIf
 
EndProcedure

aStr = "Mein String"

For i=0 To 99
  CreateThread(@sende(), SpielerId(i))
Next i



aStr = "Nur für Spieler 17"
x=CreateThread(@sende(), SpielerId(17)) ;Nur für Spieler 17
WaitThread(x)
aStr="Dies geht an Spieler 10-20"
For i=10 To 20
  x=CreateThread(@sende(), SpielerId(i))
WaitThread(x)
Next i

aStr = "Noch mal ein anderer String an Spieler 17"
x=CreateThread(@sende(), SpielerId(17))
WaitThread(x)
Alles direkt hintereinander gesendet.
Würde das funktionieren?


EDIT:
Ich habe es mit obigen Code probiert, habe also immer CreateThread(@sende(), SpielerId(Nr)) verwendet.
Die Daten werden ordnungsgemäß verschickt, allerdings sehe ich keinerlei Geschwindigkeitsvorteil.
Sobald ich meinen Zeitlupen-Rechner ranklemme, als Client (per Funk, Handy verbunden), dauert es eeeewig, bis der Server an alle gesendet hat.
Der Server verhält sich exakt genau so, als ob ich keine eigene Threads erstelle.

Es funktioniert nur, wenn ich WaitThread() benutze.
Dadurch arbeitet der Server allerding genau so, also würde er ohne diese Threads arbeiten und das ganze macht so keinen Sinn.
Gibt es irgendwo einen Beispielcode für einen kleinen Spieleserver, den ein Laie auch verstehen kann?
Aus den hiesigen Beispielen werde ich leider nicht schlau.
Ich programmiere nur noch mit Linux.
Linux Mint 21.x
Benutzeravatar
mhs
Beiträge: 224
Registriert: 11.01.2009 16:30
Wohnort: Graben
Kontaktdaten:

Re: ins Netzwerk senden mit Threads

Beitrag von mhs »

Wenn du mir WaitThread auf die Abarbeitung des Threads wartest, dann kannst du dadurch auch keinen Geschwindigkeitsvorteil haben, da der Thread ja auch so lange braucht, als würdest du direkt das SendNetworkString aufrufen.

Du musst die Logik so verändern, dass der Thread parallel und asynchron zum Rest arbeitet.
Michael Hack

Michael Hack Software :: Softwareentwicklung | Webentwicklung | IT-Dienstleistungen
www.michaelhacksoftware.de :: www.mh-s.de :: www.michael-hack.de
stevie1401
Beiträge: 700
Registriert: 19.10.2014 15:51
Kontaktdaten:

Re: ins Netzwerk senden mit Threads

Beitrag von stevie1401 »

Im ganzen Forum gibt es Beispiele, "wie man es nicht macht".
Selbst die Hilfe zeigt ja Beispiele, die ungeeignet für einen Spieleserver sind.
Deshalb noch einmal meine Frage:
Gibt es irgendwo ein funktionierendes Beispiel, welches auch Laien einigermaßen verstehen können?
Ich programmiere in Basic, weil ich KEIN professioneller Programmierer bin, sondern weil ich mir mein jetziges Wissen mit "Versuch macht Kluch" angeignet habe.

Um zu lernen und zu verstehen brauche ich Beispielcodes.
Ich programmiere nur noch mit Linux.
Linux Mint 21.x
Benutzeravatar
Sicro
Beiträge: 963
Registriert: 11.08.2005 19:08
Kontaktdaten:

Re: ins Netzwerk senden mit Threads

Beitrag von Sicro »

Threads führen Codes parallel aus, während der Haupt-Code ebenfalls ungestört weiterläuft.
Mit WaitThread() machst du die eigentliche Funktion von Threads wieder zunichte, weil du wartest jedes Mal auf den vorherigen Thread.

Code: Alles auswählen

aStr = "Nur für Spieler 17"
x=CreateThread(@sende(), SpielerId(17)) ;Nur für Spieler 17
[...]
aStr = "Noch mal ein anderer String an Spieler 17"
x=CreateThread(@sende(), SpielerId(17))
Das funktioniert bei dir nur wegen dem WaitThread() korrekt.
Ohne WaitThread() würdest du dem ersten Thread den zu sendenden String sofort wieder wegnehmen, weil du diesen ja gleich wieder für den zweiten Thread änderst.
Es ist besser, du verwendest für jeden Client eine separate String-Variable, wie ich es im zweiten Beispiel gemacht habe.

So könntest du es ohne WaitThread() machen:

Code: Alles auswählen

Structure SpielerStruc
  ClientID.i
  String.s
EndStructure

Dim Spieler.SpielerStruc(99)

Procedure Sende(*Spieler.SpielerStruc)
  Protected counter,i,sentBytes,gesendet,timer,zeit,l,ok
  Protected.s nname
  ok=1
  l=StringByteLength(*Spieler\String, #PB_Ascii)
  timer=ElapsedMilliseconds()
  If *Spieler\ClientID>0
    Repeat
      gesendet=SendNetworkString(*Spieler\ClientID, *Spieler\String, #PB_Ascii)
      If gesendet=-1
        Delay(20)
      Else
        sentBytes+gesendet
      EndIf
      zeit=ElapsedMilliseconds()-timer
    Until sentBytes=L Or zeit>60
    If zeit>60
      ok=0
    EndIf
   
    If ok=1
      Debug "Iconstringlänge: "+Str(sentBytes)+" gesendet (Original: "+Str(l)
    EndIf
   
    ProcedureReturn ok
  EndIf
 
EndProcedure

Spieler(0)\ClientID = ; SpielerID
Spieler(0)\String   = "String für einen Client"
CreateThread(@sende(), @Spieler(0))

Spieler(1)\ClientID = ; SpielerID
Spieler(1)\String   = "String für anderen Client"
CreateThread(@sende(), @Spieler(1))

OpenWindow(0, 0, 0, 300, 100, "Mein Programm", #PB_Window_SystemMenu|#PB_Window_ScreenCentered)
Repeat
  Event = WaitWindowEvent()
Until Event = #PB_Event_CloseWindow
Durch das leere Fenster läuft das Programm weiter und kann später sauber beendet werden, ohne es per Taskmanager abschießen zu müssen.

stevie1401 hat geschrieben:Spieleserver
Es wäre für uns von Vorteil, wenn wir von dir wüssten, was dieser alles können muss.

stevie1401 hat geschrieben:Gibt es irgendwo ein funktionierendes Beispiel, welches auch Laien einigermaßen verstehen können?
Netzwerk-Sachen sind eben nicht einfach.
Sendest du mehrere Strings von einem Client, kommen die Strings beim Server nicht getrennt an, wie du sie gesendet hast, sondern an einem Stück gekettet. Um das Trennen der Strings musst du dich dann selber kümmern, indem du für jeden String noch die String-Länge mitsendest, damit du beim Server weißt, wo der erste String aufhört und der nächste String beginnt.
Wenn du mir sagst, was du bei der Verwendung meines Moduls "NetworkManager" nicht verstehst, kann ich ihn vielleicht noch etwas für dich vereinfachen.
Bild
Warum OpenSource eine Lizenz haben sollte :: PB-CodeArchiv-Rebirth :: Pleasant-Dark (Syntax-Farbschema) :: RegEx-Engine (kompiliert RegExes zu NFA/DFA)
Manjaro Xfce x64 (Hauptsystem) :: Windows 10 Home (VirtualBox) :: Neueste PureBasic-Version
stevie1401
Beiträge: 700
Registriert: 19.10.2014 15:51
Kontaktdaten:

Re: ins Netzwerk senden mit Threads

Beitrag von stevie1401 »

Vielen Dank für deine Hilfe!

Ich habe nun folgendes geschrieben:

Code: Alles auswählen


Procedure sende(aClientId,aStr.s)
  Protected   i,merki,ok
  
  For i=1 To spieleranz        ;Index des Clients herausfinden
    If aClientId=SpielerId(i)
      merki=i
    EndIf
  Next i


  i=0
  Repeat
    ok=1
    If IsThread(spielerthread(merki))  ;Läuft für diesen Client noch ein Thread, dann warten
      ok=0
      i+1
      Delay(100)
    EndIf
  Until i=20 Or ok=1
  
  If ok=0  ;Thread des Spielers ist noch nicht beendet, es kann KEIN neuer erstellt werden
    ProcedureReturn 0
  EndIf
  
  
  If merki>0   ;Thread des Spielers ist BEENDET, es kann ein neuer Thread erstellt werden
    client(merki)\ClientID = aClientId   ;; SpielerID
    client(merki)\String   = aStr        ;"String für anderen Client"
    spielerthread(merki)=CreateThread(@ThreadSende(), @client(merki))
  EndIf
EndProcedure



Procedure ThreadSende(*Client.SpielerStruc)
  Protected counter,i,sentBytes,gesendet,timer,zeit,l,ok
  Protected.s nname
  ok=1
  l=StringByteLength(*Client\String, #PB_Ascii)
  timer=ElapsedMilliseconds()
  If *client\ClientID>0
    Repeat
      gesendet=SendNetworkString(*Client\ClientID, *Client\String, #PB_Ascii)
      If gesendet=-1
        Delay(20)
      Else
        sentBytes+gesendet
      EndIf
      zeit=ElapsedMilliseconds()-timer
    Until sentBytes=L Or zeit>60
    If zeit>60
      ok=0
    EndIf
   
    If ok=1
      Debug "Iconstringlänge: "+Str(sentBytes)+" gesendet (Original: "+Str(l)
    EndIf
   
    ProcedureReturn ok
  EndIf
 
EndProcedure




Nun kann ich beliebige Strings an beliebige Clients senden:

For i=1 to 100
sende(spielerId(i),NachrichtAnDieClients)
next i

Das scheint zu klappen.
Allerdings bekommen langsame Clients nicht alle Strings gesendet.
Aber ich denke, es geht schon mal in die richtige Richtung.
Noch schöner wäre natürlich, wenn ich den langsamen Clients die Strings "nachreichen" könnte, Ich guck einfach mal was da noch machbar ist.

Es geht übrigens um einen Doppelkopf-Server. http://www.doko-lounge.de
Spielbar ist es jetzt schon, nur es nervt halt, wenn Leute per Handyverbindung reingehen und dann alles quälend langsam ist.
Ich programmiere nur noch mit Linux.
Linux Mint 21.x
Benutzeravatar
mk-soft
Beiträge: 3845
Registriert: 24.11.2004 13:12
Wohnort: Germany

Re: ins Netzwerk senden mit Threads

Beitrag von mk-soft »

Ich denke über UDP ist der bessere weg.

Client sendet eine Anfordrung
Server sendet die Daten per UDP
Client senden per UDP eine Bestätigung.
Server prüft welcher Client bestätigt hat und sendet bei nicht Antwort eine zweiten versuch.
Alles ist möglich, fragt sich nur wie...
Projekte ThreadToGUI / EventDesigner V3 / OOP-BaseClass-Modul
Downloads auf MyWebspace / OneDrive
auser
Beiträge: 58
Registriert: 17.05.2011 10:56

Re: ins Netzwerk senden mit Threads

Beitrag von auser »

@stevie1401 - Bei IsThread() musst du aufpassen daß du nicht auf einen anderen Thread guckst der später gestartet wurde aber wieder die selbe ID bekommen hat. Ich würde das nicht über IsThread machen sondern am Ende eine Aufräumprozedur triggern (z.B. mit Semaphore)

Bei "timer" hast du Integer genommen. Als 64 Bit Binary kann das ein Problem werden weil ElapsedMilliseconds() je nach Laufzeit vom Server negative Zahlen zurück gibt.

UDP macht dann Sinn wenn du zwischen wichtigen und unwichtigen Daten unterscheiden kannst (FPS, Platformer, ...). Bei unwichtigen Daten die man zum Abgleich als Annäherungswerte an die Clients spammt (z.B. x,y,z vor 50 msecs) ist die Fehlerkorrektur nämlich lästig weil sie neuere Daten (die genauer wären) hinten an reiht. Wenn es aber nur Daten gibt die ohnehin immer ankommen müssen macht es nicht viel Sinn und ich würde bei TCP bleiben.
Antworten