Seite 6 von 9

Re: WebSocket Server

Verfasst: 28.02.2021 22:31
von Dadido3
Wie vorhin angekündigt ist hier die Version ohne Threads: WebSocket_Server/threadless

Dafür musst du quasi nur zwei Zeilen in deinem Code ändern.
Deine Hauptschleife:

Code: Alles auswählen

Repeat
  ; Other stuff
  WebSocket_Server::Worker(*Server)
  ; Other stuff
ForEver
Das Erstellen des Servers:

Code: Alles auswählen

*Server = WebSocket_Server::Create(80, @DeineEventFunktion())

Re: WebSocket Server

Verfasst: 02.03.2021 10:23
von stevie1401
Ja Mensch, da bin ich ja mal gespannt :)
Ab heute läuft der threadlose Code.
Ich werde berichten.
Vielen lieben Dank!!!

Re: WebSocket Server

Verfasst: 27.03.2021 08:37
von stevie1401
Zwischenbericht nach 4 Wochen:
Der Server läuft teilweise mit sehr vielen Clients und es gab bis jetzt keinerlei Probleme :)

Vielen, vielen Dank, Dadido3! :)

Re: WebSocket Server

Verfasst: 27.07.2021 08:17
von stevie1401
Ich habe inzwischen die Client() - Liste herausgenommen und durch einfaches Dimensionieren von Variablen ersetzt.

Code: Alles auswählen

Dim Client_Name.s(x)
Dim Cliend_Id(x)
Das Programm beendete sich manchmal, wenn es in der IDE lief ohne dass es die IDE bemerkte.
Ich habe auch nicht herausfinden können, woran es lag.
Seitdem die Liste raus ist, ist es bis jetzt nicht mehr passiert.
Kann es am Pointer *client liegen?

Mir ist zudem aufgefallen, dass das Serverprogramm ab und zu ein #Event_Disconnect empfängt, obwohl der Browser des Clients nicht geschlossen wurde und die Verbindung auch nicht anders unterbrochen wurde.
Woran kann das liegen?

Re: WebSocket Server

Verfasst: 31.07.2021 20:42
von Dadido3
stevie1401 hat geschrieben: 27.07.2021 08:17 Ich habe auch nicht herausfinden können, woran es lag.
Seitdem die Liste raus ist, ist es bis jetzt nicht mehr passiert.
Das deutet darauf hin, dass irgendwo in "fremde" Speicherbereiche geschrieben wurde, und somit unbeabsichtigt irgendwelche Werte überschrieben wurden (Memory corruption). Das kann passieren, wenn du z.B. auf ein Listenelement zugreifst, welches zuvor per DeleteElement gelöscht wurde. Oder wenn du per ChangeCurrentElement die aktuelle Listenposition auf ein nicht (mehr) gültiges Listenelement setzt. Auch kann es zu Problemen kommen, wenn mehrere Threads auf die selbe Liste zugreifen. (Was bei dir aber nicht der fall sein kann, da du keine Threads verwendest)

Der Beispielcode zeigt wie man die Liste mit verbundenen Clients verwaltet. Ich kann leider nicht sagen, warum es bei dir mit der verketteten Liste abstürzt. Wenn es aber bei dir mit einem einfachen Array funktioniert, dann lass es so. Du musst aber das Array vergrößern, wenn der Platz zu knapp wird.
stevie1401 hat geschrieben: 27.07.2021 08:17 Kann es am Pointer *client liegen?
Die *Client Zeiger sind immer solange gültig, bis du ein #Event_Disconnect empfängst (Exakt nach dem Verlassen der event handler Funktion wird der Zeiger ungültig). Danach darfst du den *Client Zeiger nicht mehr verwenden. Es spielt keine Rolle wie du deine Clients verwaltest (Als verlinkte Liste, als Array oder als map), solange du keinen ungültig gewordenen *Client verwendest.
stevie1401 hat geschrieben: 27.07.2021 08:17 Mir ist zudem aufgefallen, dass das Serverprogramm ab und zu ein #Event_Disconnect empfängt, obwohl der Browser des Clients nicht geschlossen wurde und die Verbindung auch nicht anders unterbrochen wurde.
Woran kann das liegen?
Entweder bricht der Browser die Verbindung aus irgendeinem Grund ab, oder mein Server erzwingt das Schließen der Verbindung weil ein Client bestimmte Grenzen überschritten hat. Es gibt im Moment leider keine Möglichkeit den Grund abzufragen.

Im Code der Library sind ein paar stellen, die preisgeben warum ein Client getrennt wurde:

Code: Alles auswählen

Client_Disconnect_Mutexless(*Object, *Client, #CloseStatusCode_ProtocolError) ; Server hat eine Unstimmigkeit in den vom Client gesendeten Daten gefunden.
Client_Disconnect_Mutexless(*Object, *Client, #CloseStatusCode_SizeLimit) ; Ein client hat die gültige frame size überschritten.
Auch wäre diese Stelle noch interessant für dich:

Code: Alles auswählen

statusCode = ((statusCode & $FF00) >> 8) | ((statusCode & $FF) << 8)
reason = PeekS(Event_Frame\Payload + 2, Event_Frame\Payload_Size - 2, #PB_UTF8 | #PB_ByteLength)
Das ist der statusCode und reason (als lesbarer text), wenn ein Client einen Verbindungsabbruch einleitet. Einfach mal per debug (oder sonstwie) ausgeben.

Mögliche statusCodes:

Code: Alles auswählen

Enumeration
    #CloseStatusCode_Normal = 1000      ; indicates a normal closure, meaning that the purpose for which the connection was established has been fulfilled.
    #CloseStatusCode_GoingAway          ; indicates that an endpoint is "going away", such as a server going down or a browser having navigated away from a page.
    #CloseStatusCode_ProtocolError      ; indicates that an endpoint is terminating the connection due to a protocol error.
    #CloseStatusCode_UnhandledDataType  ; indicates that an endpoint is terminating the connection because it has received a type of data it cannot accept (e.g., an endpoint that understands only text data MAY send this if it receives a binary message).
    #CloseStatusCode_1004               ; Reserved.  The specific meaning might be defined in the future.
    #CloseStatusCode_NoStatusCode       ; is a reserved value and MUST NOT be set as a status code in a Close control frame by an endpoint.  It is designated for use in applications expecting a status code to indicate that no status code was actually present.
    #CloseStatusCode_AbnormalClose      ; is a reserved value and MUST NOT be set as a status code in a Close control frame by an endpoint.  It is designated for use in applications expecting a status code to indicate that the connection was closed abnormally, e.g., without sending or receiving a Close control frame.
    #CloseStatusCode_1007               ; indicates that an endpoint is terminating the connection because it has received data within a message that was not consistent with the type of the message (e.g., non-UTF-8 [RFC3629] data within a text message).
    #CloseStatusCode_PolicyViolation    ; indicates that an endpoint is terminating the connection because it has received a message that violates its policy.  This is a generic status code that can be returned when there is no other more suitable status code (e.g., 1003 or 1009) or if there is a need to hide specific details about the policy.
    #CloseStatusCode_SizeLimit          ; indicates that an endpoint is terminating the connection because it has received a message that is too big for it to process.
EndEnumeration

Re: WebSocket Server

Verfasst: 01.08.2021 16:20
von stevie1401
Alles klar!
Vielen Dank für deine Hilfe und Unterstützung!
Ohne dich würde mein Projekt nicht laufen! :)

Stevie

Re: WebSocket Server

Verfasst: 16.02.2025 20:49
von stevie1401
Mir stürzt der Server ab und zu hier ab:

Code: Alles auswählen

    If *Payload
        CopyMemory(*Payload, *Pointer, Payload_Size)
        ;*Pointer + Payload_Size
        
        ;Fehler hier:  Die Liste hat kein aktuelles Element.
        *Client\TX_Frame()\RxTx_Size + Payload_Size
      EndIf
      
Kann ich den Server irgendwie überreden nicht abzustürzen? Evtl mit einer Prüfung ob es das Element gibt?
Wenn ja, wie und wo?


Hier noch einmal die ganze Procedure:

Code: Alles auswählen

Procedure Frame_Send_Mutexless(*Object.Object, *Client.Client, FIN.a, RSV.a, Opcode.a, *Payload, Payload_Size.q)
    Protected *Pointer.Ascii
    Protected *Eight_Bytes.Eight_Bytes
    
    
   
    If *client.client<1000   ;Fakespieler - fakelounge oder in echt ein ClientID-Fehler
      ProcedureReturn #False
    EndIf
    
    
    If Not *Object
      ProcedureReturn #False
    EndIf
    
    If Not *Client
      ProcedureReturn #False
    EndIf
    
    If Not *Client\ID Or *Client\Event_Disconnect_Manually
      ProcedureReturn #False
    EndIf
    
    If Payload_Size < 0
      ProcedureReturn #False
    EndIf
    
    If Not *Payload
      Payload_Size = 0
    EndIf
    
    ; #### Special case: Connection close request (or answer).
    If Opcode = #Opcode_Connection_Close
      *Client\Event_Disconnect_Manually = #True : ClientQueueEnqueue(*Object, *Client)
      
      ; #### Remove all TX_Frame elements (Except the one that is being sent right now).
      While LastElement(*Client\TX_Frame()) And ListIndex(*Client\TX_Frame()) > 0
        If *Client\TX_Frame()\Data
          FreeMemory(*Client\TX_Frame()\Data) : *Client\TX_Frame()\Data = #Null
        EndIf
        DeleteElement(*Client\TX_Frame())
      Wend
    EndIf
    
    LastElement(*Client\TX_Frame())
    If AddElement(*Client\TX_Frame())
      
      *Client\TX_Frame()\Data = AllocateMemory(10 + Payload_Size)
      If Not *Client\TX_Frame()\Data
        *Client\Event_Disconnect_Manually = #True : ClientQueueEnqueue(*Object, *Client)
        ProcedureReturn #False
      EndIf
      
      ; #### FIN, RSV and Opcode
      *Pointer = *Client\TX_Frame()\Data
      *Pointer\a = (FIN & 1) << 7 | (RSV & %111) << 4 | (Opcode & %1111) : *Pointer + 1
      *Client\TX_Frame()\RxTx_Size + 1
      
      ; #### Payload_Size and extended stuff
      Select Payload_Size
        Case 0 To 125
          *Pointer\a = Payload_Size       : *Pointer + 1
          *Client\TX_Frame()\RxTx_Size + 1
        Case 126 To 65535
          *Eight_Bytes = @Payload_Size
          *Pointer\a = 126                  : *Pointer + 1
          *Pointer\a = *Eight_Bytes\Byte[1] : *Pointer + 1
          *Pointer\a = *Eight_Bytes\Byte[0] : *Pointer + 1
          *Client\TX_Frame()\RxTx_Size + 3
        Default
          *Eight_Bytes = @Payload_Size
          *Pointer\a = 127                  : *Pointer + 1
          *Pointer\a = *Eight_Bytes\Byte[7] : *Pointer + 1
          *Pointer\a = *Eight_Bytes\Byte[6] : *Pointer + 1
          *Pointer\a = *Eight_Bytes\Byte[5] : *Pointer + 1
          *Pointer\a = *Eight_Bytes\Byte[4] : *Pointer + 1
          *Pointer\a = *Eight_Bytes\Byte[3] : *Pointer + 1
          *Pointer\a = *Eight_Bytes\Byte[2] : *Pointer + 1
          *Pointer\a = *Eight_Bytes\Byte[1] : *Pointer + 1
          *Pointer\a = *Eight_Bytes\Byte[0] : *Pointer + 1
          *Client\TX_Frame()\RxTx_Size + 9
      EndSelect
      
      If *Payload
        CopyMemory(*Payload, *Pointer, Payload_Size)
        ;*Pointer + Payload_Size
         ;Fehler hier:  Die Liste hat kein aktuelles Element.
        *Client\TX_Frame()\RxTx_Size + Payload_Size
      EndIf
      
      ProcedureReturn #True
    EndIf
    
    ProcedureReturn #False
  EndProcedure
  

Re: WebSocket Server

Verfasst: 22.02.2025 13:09
von Dadido3
Hallo,
stevie1401 hat geschrieben: 16.02.2025 20:49

Code: Alles auswählen

    If *Payload
        CopyMemory(*Payload, *Pointer, Payload_Size)
        ;*Pointer + Payload_Size
        
        ;Fehler hier:  Die Liste hat kein aktuelles Element.
        *Client\TX_Frame()\RxTx_Size + Payload_Size
      EndIf
ich gehe davon aus, dass du immer noch die "threadless" Variante (Threadless branch auf GitHub) der Library verwendest. Hier sollte es eigentlich nicht dazu kommen, dass die Liste *Client\TX_Frame() in Frame_Send_Mutexless(...) kein Element hat. Es wird nämlich ein Listenelement am Anfang dieser Funktion erstellt, und dann damit gearbeitet. Es ist quasi ausgeschlossen, dass das Listenelement nicht existiert.

Es könnte aber sein, dass du die "Main-Loop-Funktion" Worker(*Object) von einem anderen Thread aus aufrufst, womit der Worker dann gleichzeitigen zugriff auf die oben genannte Liste bekommen und eventuell Elemente von dieser löschen könnte.

Dies wäre entgegen dem Sinn der "threadless" Variante; diese ist nicht threadsafe. Es muss also von Dir sichergestellt werden, dass kein gleichzeitiger Aufruf stattfindet. Am einfachsten wäre es natürlich einfach keine Threads zu verwenden. Alternativ müsstest du wieder zur normalen Variante wechseln.

Daneben sehe ich noch, dass du den Code modifiziert hast. Z.B.
stevie1401 hat geschrieben: 16.02.2025 20:49

Code: Alles auswählen

Procedure Frame_Send_Mutexless(*Object.Object, *Client.Client, FIN.a, RSV.a, Opcode.a, *Payload, Payload_Size.q)
    Protected *Pointer.Ascii
    Protected *Eight_Bytes.Eight_Bytes
    
    
   
    If *client.client<1000   ;Fakespieler - fakelounge oder in echt ein ClientID-Fehler
      ProcedureReturn #False
    EndIf
    
    If Not *Object
      ProcedureReturn #False
    EndIf
    
    If Not *Client
      ProcedureReturn #False
    EndIf
Hier dereferenzierst du *Client, bevor geprüft wurde ob *Client überhaupt existiert.

Ich vermute, dass du noch andere Änderungen in der Library vorgenommen hast. Leider kann ich dafür keinen Support bieten. Es wäre also gut, wenn du zum Reproduzieren der Fehler die aktuelle Version von Github verwendest.
stevie1401 hat geschrieben: 16.02.2025 20:49 Kann ich den Server irgendwie überreden nicht abzustürzen? Evtl mit einer Prüfung ob es das Element gibt?
Wenn ja, wie und wo?
Du könntest überprüfen, ob das Listenelement existiert. Das würde die Wahrscheinlichkeit ein wenig reduzieren, nicht aber auf 0 bringen. Das Problem liegt anderswo. Du hast irgendwo einen gleichzeitigen zugriff auf diese Liste, weil du vermutlich Libraryfunktionen von verschienen Threads aus aufrufst. Ich sehe jetzt nur eine der folgende Möglichkeiten (Falls das Problem auch mit der unveränderten Library von GitHub auftritt):
  • Nutzung der offiziellen "threaded" Version der Library (master branch auf GitHub). Diese ist threadsafe, hat aber eine klein wenig andere API.
  • Nutzung eines Mutex um den gleichzeitigen Zugriff auf die Libraryfunktionen einzuschränken.
  • Sicherstellen, dass alle Aufrufe auf die Library nur von einem Thread aus stattfinden.
  • Keine Threads verwenden, dementsprechend alles aus der Hauptschleife abwickeln.

Re: WebSocket Server

Verfasst: 06.03.2025 17:26
von stevie1401
Oh, ich habe jetzt erst gesehen, dass du mir geschrieben hast.
Ok, erst einmal danke, ich schaue mir das alle noch einmal genauer an!
Vielen lieben Dank!

Re: WebSocket Server

Verfasst: 02.04.2025 13:05
von stevie1401
Ich konnte endlich einen vermutlichen "Bug" entdecken:


Ich habe mit Spiderbasic einen Code geschrieben, der viele Clients anmeldet und die dann etwas senden.
Wenn ich während die Clients sich anmelden das Browserfenster von der Spiderbasic-App schliesse, dann verabschiedet sich der Webserver ins Nirvana.
Im unteren Beispiel soll das Spider-Client-Programm 400 Clients anmelden. Im Debugfenster des Browsers sehe ich welcher Client schon angemeldet ist.
Wenn ich jetzt mittendrin, zum z.B. bei Client 100 das Browserfenster einfach schließe, verabschiedet sich der Server.

Ich habe dies mit allen Servern getestet, dem Threadlosen und den beiden aktuellen mit Threads.

Hier der Spiderbasic-Code:

Code: Alles auswählen



EnableExplicit
Structure BotStruc
  Name.s
  Websocket.i
  verbunden.i
EndStructure


Declare BotAnmelden(index)
Declare AlleBotsAnmelden()
Declare BotConnect(index)
Declare.s Tag_String(Tag.s, Inhalt.s)
Declare sende(id,s.s)
Declare WebsocketEvents()
Declare check_nachricht(WebSocket,StringFromServer.s)
Declare TimerEvents()
Declare.s jetzt()
Declare Main()


Global botanz=400
Global alleBotsAngemeldet

Global Dim Bot.BotStruc(botanz)





Enumeration
  
  #labertimer
  #anUndAbmeldenTimer
  
  
EndEnumeration






BindEvent(#PB_Event_WebSocket,    @WebsocketEvents())
BindEvent(#PB_Event_Timer,        @TimerEvents())

AlleBotsAnmelden()


Procedure AlleBotsAnmelden()
  
  Protected   i
  

  
  For i=1 To botanz
  
    bot(i)\Name="Bot"+Str(i)
    bot(i)\Websocket=i
    OpenWebSocket(i,"ws://127.0.0.1:8090")
  Next i
  
  AddTimer(#labertimer,4000)
  AddTimer(#anUndAbmeldenTimer,8000)
  
  
EndProcedure
Procedure BotAnmelden(index)
  Protected.s Inhalt

  Sende(bot(index)\Websocket,"Test")
  
EndProcedure

Procedure.s Tag_String(Tag.s, Inhalt.s)
  ProcedureReturn "<<" + Tag + ">>" + Inhalt + "<</" + Tag + ">>"
EndProcedure


Procedure sende(WebSocket,aStr.s)
  
  If IsWebSocket(WebSocket)
    SendWebSocketString(websocket,astr)
  EndIf
  
EndProcedure


Procedure WebsocketEvents()
  
  Protected i,j
  
  Select EventType()
    Case #PB_EventType_Connected
      Debug jetzt()+" Verbunden Socket "+Str( EventWebSocket())    
      
      For i=1 To botanz
        If bot(i)\Websocket=EventWebSocket()
          bot(i)\verbunden=1
          BotAnmelden(i)
          Break
        EndIf  
      Next i
      
      If alleBotsAngemeldet=0
        j=0
        For i=1 To botanz
          If bot(i)\verbunden=1
            j+1
          EndIf  
        Next i
        If j=botanz
          alleBotsAngemeldet=1
        EndIf
      EndIf
      
      
      
      
      
    Case #PB_EventType_Closed
      Debug jetzt()+" #PB_EventType_Closed "+Str( EventWebSocket())    
    Case #PB_EventType_String
      check_Nachricht(EventWebSocket(),EventString())
    Case #PB_EventType_Data
      
    Case #PB_EventType_Error
      Debug jetzt()+" Error on WebSocket #" + EventWebSocket()
  EndSelect
  
EndProcedure


Procedure check_nachricht(WebSocket,StringFromServer.s)

EndProcedure

Procedure TimerEvents()
  
  Protected     i
  Protected.s   s
  
  
  If EventTimer()=#labertimer And alleBotsAngemeldet=1
    For i=1 To botanz
      s=Tag_String("ping",jetzt()+" Grüße von "+bot(i)\Name)    
      sende(bot(i)\Websocket,s)
    Next i
    
  EndIf
  
  
  If EventTimer()=#anUndAbmeldenTimer And alleBotsAngemeldet=1
    If IsWebSocket(3)
      CloseWebSocket(3)
    Else
       OpenWebSocket(3,"ws://127.0.0.1:8090")
    EndIf
    
  EndIf



EndProcedure


Procedure.s jetzt()
  ProcedureReturn FormatDate("%hh:%ii:%ss", Date())
EndProcedure

Ich habe auch festgestellt, dass die

Procedure Frame_Text_Send(*Object.Object, *Client.Client, Text.s)

NICHT prüft, ob es den Client an den es senden soll überhaupt gibt.

Ob es dafür eine Lösung gibt?