Websocket Client

Anfängerfragen zum Programmieren mit PureBasic.
stevie1401
Beiträge: 700
Registriert: 19.10.2014 15:51
Kontaktdaten:

Re: Websocket Client

Beitrag von stevie1401 »

Das ist sehr nett von dir, aber das ist der SERVER, ich brauche einen Client-Code
War bei dem Projekt nicht auch auch Beispiel Client dabei?
Ja, ein HTLM bzw. Javascript Code, der mir nichts nützt, da ich einen Purebasic-Code brauche.
Ich programmiere nur noch mit Linux.
Linux Mint 21.x
Benutzeravatar
dige
Beiträge: 1235
Registriert: 08.09.2004 08:53

Re: Websocket Client

Beitrag von dige »

"Papa, ich laufe schneller - dann ist es nicht so weit."
stevie1401
Beiträge: 700
Registriert: 19.10.2014 15:51
Kontaktdaten:

Re: Websocket Client

Beitrag von stevie1401 »

Auch das kenne ich schon. Das funktioniert nicht.
Der Client stürzt ständig mit einem Speicherfehler ab.
Ich programmiere nur noch mit Linux.
Linux Mint 21.x
stevie1401
Beiträge: 700
Registriert: 19.10.2014 15:51
Kontaktdaten:

Re: Websocket Client

Beitrag von stevie1401 »

Neuer Versuch, ich habe mich zwar im englischen Forum an ein Thema rangeklemmt, aber dort antwortet nniemand.

Ich habe einen testwebsocketserver aufgesetzt.

Accessible at ws://doko-cafe.de:8090

Ich benutze den Code von Dadio3:

Code: Alles auswählen

Structure Chat_Message
  Type.s
  Author.s
  Message.s
  Timestamp.q
EndStructure

Structure Chat_Username_Change
  Type.s
  Username.s
EndStructure

Structure Chat_Userlist
  Type.s
  
  List Username.s()
EndStructure

Structure Client
  *WebSocket_Client
  
  Username.s
EndStructure

Global NewList Client.Client()

XIncludeFile "Includes/WebSocket_Server.pbi"

Procedure WebSocket_Event(*Server, *Client, Event, *Event_Frame.WebSocket_Server::Event_Frame)
  Protected Chat_Message.Chat_Message
  Protected Chat_Username_Change.Chat_Username_Change
  Protected Chat_Userlist.Chat_Userlist
  Protected JSON_ID.i
  Protected JSON2_ID.i
  Protected JSON_String.s
  
  Select Event
    Case WebSocket_Server::#Event_Connect
      PrintN(" #### Client connected: " + *Client)
      AddElement(Client())
      Client()\WebSocket_Client = *Client
      
      JSON2_ID = CreateJSON(#PB_Any)
      If JSON2_ID
        
        Chat_Userlist\Type = "Userlist"
        ForEach Client()
          AddElement(Chat_Userlist\UserName())
          Chat_Userlist\UserName() = Client()\Username
        Next
        
        InsertJSONStructure(JSONValue(JSON2_ID), Chat_Userlist, Chat_Userlist)
        
        WebSocket_Server::Frame_Text_Send(*Server, *Client, ComposeJSON(JSON2_ID))
        
        FreeJSON(JSON2_ID)
      EndIf
      
    Case WebSocket_Server::#Event_Disconnect
      PrintN(" #### Client disconnected: " + *Client)
      ForEach Client()
        If Client()\WebSocket_Client = *Client
          DeleteElement(Client())
          Break
        EndIf
      Next
      
      JSON2_ID = CreateJSON(#PB_Any)
      If JSON2_ID
        
        Chat_Userlist\Type = "Userlist"
        ForEach Client()
          AddElement(Chat_Userlist\UserName())
          Chat_Userlist\UserName() = Client()\Username
        Next
        
        InsertJSONStructure(JSONValue(JSON2_ID), Chat_Userlist, Chat_Userlist)
        
        JSON_String = ComposeJSON(JSON2_ID)
        ForEach Client()
          WebSocket_Server::Frame_Text_Send(*Server, Client()\WebSocket_Client, JSON_String)
        Next
        
        FreeJSON(JSON2_ID)
      EndIf
      
    Case WebSocket_Server::#Event_Frame
      Select *Event_Frame\Opcode
        Case WebSocket_Server::#Opcode_Ping
          PrintN(" #### Ping from *Client " + *Client)
        Case WebSocket_Server::#Opcode_Text
          JSON_ID = ParseJSON(#PB_Any, PeekS(*Event_Frame\Payload, *Event_Frame\Payload_Size, #PB_UTF8|#PB_ByteLength))
          If JSON_ID
            
            Select GetJSONString(GetJSONMember(JSONValue(JSON_ID), "Type"))
              Case "Message"
                ExtractJSONStructure(JSONValue(JSON_ID), Chat_Message, Chat_Message)
                PrintN(Chat_Message\Author + ": " + Chat_Message\Message)
                
                Debug PeekS(*Event_Frame\Payload, *Event_Frame\Payload_Size, #PB_UTF8|#PB_ByteLength)
                
                JSON2_ID = CreateJSON(#PB_Any)
                If JSON2_ID
                  
                  ForEach Client()
                    If Client()\WebSocket_Client = *Client
                      Chat_Message\Author = Client()\Username
                      ;Chat_Message\Timestamp = Date()
                      Break
                    EndIf
                  Next
                  
                  InsertJSONStructure(JSONValue(JSON2_ID), Chat_Message, Chat_Message)
                  
                  JSON_String = ComposeJSON(JSON2_ID)
                  ;Debug JSON_String
                  ForEach Client()
                    WebSocket_Server::Frame_Text_Send(*Server, Client()\WebSocket_Client, JSON_String)
                  Next
                  
                  FreeJSON(JSON2_ID)
                EndIf
                
              Case "Username_Change"
                ExtractJSONStructure(JSONValue(JSON_ID), Chat_Username_Change, Chat_Username_Change)
                ForEach Client()
                  If Client()\WebSocket_Client = *Client
                    Client()\Username = Chat_Username_Change\Username
                    Break
                  EndIf
                Next
                
                JSON2_ID = CreateJSON(#PB_Any)
                If JSON2_ID
                  
                  Chat_Userlist\Type = "Userlist"
                  ForEach Client()
                    AddElement(Chat_Userlist\UserName())
                    Chat_Userlist\UserName() = Client()\Username
                  Next
                  
                  InsertJSONStructure(JSONValue(JSON2_ID), Chat_Userlist, Chat_Userlist)
                  
                  JSON_String = ComposeJSON(JSON2_ID)
                  ForEach Client()
                    WebSocket_Server::Frame_Text_Send(*Server, Client()\WebSocket_Client, JSON_String)
                  Next
                  
                  FreeJSON(JSON2_ID)
                EndIf
                
            EndSelect
            
            FreeJSON(JSON_ID)
          EndIf
      EndSelect
      
  EndSelect
EndProcedure

OpenConsole()

*Server = WebSocket_Server::Create(8090)

Repeat
  While WebSocket_Server::Event_Callback(*Server, @WebSocket_Event())
  Wend
  
  Delay(10)
ForEver
; IDE Options = PureBasic 6.21 Beta 5 (Linux - x64)
; ExecutableFormat = Console
; CursorPosition = 160
; EnableThread
; EnableXP
; Executable = testserver_polling
; CompileSourceDirectory

Dann testete ich diesen Clientcode:

Code: Alles auswählen

; Websocketclient by Netzvamp
; Version: 2016/01/08

PurifierGranularity(1, 1, 1, 1)

DeclareModule WebsocketClient
  Declare OpenWebsocketConnection(URL.s)
  Declare SendTextFrame(connection, message.s)
  Declare ReceiveFrame(connection, *MsgBuffer)
  Declare SetSSLProxy(ProxyServer.s = "", ProxyPort.l = 8182)
  
  Enumeration
    #frame_text
    #frame_binary
    #frame_closing
    #frame_ping
    #frame_unknown
  EndEnumeration
  
EndDeclareModule

Module WebsocketClient
  
  ;TODO: Add function to send binary frame
  ;TODO: We don't support fragmetation right now
  ;TODO: We should send an closing frame, but server will also just close
  ;TODO: Support to send receive bigger frames
  
  Declare Handshake(Connection, Servername.s, Path.s)
  Declare ApplyMasking(Array Mask.a(1), *Buffer)
  
  Global Proxy_Server.s, Proxy_Port.l
  
  Macro dbg(txt)
    CompilerIf #PB_Compiler_Debugger
      Debug "WebsocketClient: " + FormatDate("%yyyy-%mm-%dd %hh:%ii:%ss",Date()) + " > " + txt
    CompilerEndIf
  EndMacro
  
  Procedure SetSSLProxy(ProxyServer.s = "", ProxyPort.l = 8182)
    Proxy_Server.s = ProxyServer.s
    Proxy_Port.l = ProxyPort.l
  EndProcedure
  
  Procedure OpenWebsocketConnection(URL.s)
    Protokol.s = GetURLPart(URL.s, #PB_URL_Protocol)
    Servername.s = GetURLPart(URL.s, #PB_URL_Site)
    Port.l = Val(GetURLPart(URL.s, #PB_URL_Port))
    If Port.l = 0 : Port.l = 80 : EndIf
    Path.s = GetURLPart(URL.s, #PB_URL_Path)
    If Path.s = "" : Path.s = "/" : EndIf
    

    If Protokol.s = "wss" ; If we connect with encryption (https)
      If Proxy_Port
        Connection = OpenNetworkConnection(Proxy_Server.s, Proxy_Port.l, #PB_Network_TCP, 1000)
      Else
        dbg("We need an SSL-Proxy like stunnel for encryption. Configure the proxy with SetSSLProxy().")
      EndIf
    ElseIf Protokol.s = "ws"
      Connection = OpenNetworkConnection(Servername.s, Port.l, #PB_Network_TCP, 1000)
    EndIf
    
    If Connection
      If Handshake(Connection, Servername.s, Path.s)
        dbg("Connection and Handshake ok")
        ProcedureReturn Connection
      Else
        dbg("Handshake-Error")
        ProcedureReturn #False
      EndIf
    Else
      dbg("Couldn't connect")
      ProcedureReturn #False
    EndIf
  EndProcedure
  
  Procedure Handshake(Connection, Servername.s, Path.s)
    Request.s = "GET /" + Path.s + " HTTP/1.1"+ #CRLF$ +
                "Host: " + Servername.s + #CRLF$ +
                "Upgrade: websocket" + #CRLF$ +
                "Connection: Upgrade" + #CRLF$ +
                "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" + #CRLF$ +
                "Sec-WebSocket-Version: 13" + #CRLF$ + 
                "User-Agent: CustomWebsocketClient"+ #CRLF$ + #CRLF$
                
    SendNetworkString(Connection, Request.s, #PB_UTF8)
    *Buffer = AllocateMemory(65536)
    
    ; We wait for answer
    Repeat
      Size = ReceiveNetworkData(connection, *Buffer, 65536)
      Answer.s = Answer.s + PeekS(*Buffer, Size, #PB_UTF8)
      If FindString(Answer, #CRLF$ + #CRLF$)
        Break
      EndIf
    Until Size <> 65536
    
    Answer.s = UCase(Answer.s)
    
    ; Check answer
    If FindString(Answer.s, "HTTP/1.1 101") And FindString(Answer.s, "CONNECTION: UPGRADE") And FindString(Answer.s, "UPGRADE: WEBSOCKET")
      ProcedureReturn #True
    Else
      ProcedureReturn #False
    EndIf
  EndProcedure
  
  Procedure ApplyMasking(Array Mask.a(1), *Buffer)
    For i = 0 To MemorySize(*Buffer) - 1
      PokeA(*Buffer + i, PeekA(*Buffer + i) ! Mask(i % 4))
    Next
  EndProcedure
  
  Procedure SendTextFrame(connection, message.s)
    
    ; Put String in Buffer
    MsgLength.l = StringByteLength(message.s, #PB_UTF8)
    *MsgBuffer = AllocateMemory(MsgLength)
    PokeS(*MsgBuffer, message.s, MsgLength, #PB_UTF8|#PB_String_NoZero)
    
    dbg("Messagelength to send: " + Str(MsgLength))
    
    ; The Framebuffer, we fill with senddata
    If MsgLength <= 125
      Fieldlength = 6
    ElseIf MsgLength >= 126 And MsgLength <= 65535
      Fieldlength = 8
    Else
      Fieldlength = 14
    EndIf
    
    dbg("Fieldlength to send: " + Str(Fieldlength))
    
    
    *FrameBuffer = AllocateMemory(Fieldlength + MsgLength)
    
    ; We generate 4 random masking bytes
    Dim Mask.a(3)
    Mask(0) = Random(255,0)
    Mask(1) = Random(255,0) 
    Mask(2) = Random(255,0) 
    Mask(3) = Random(255,0) 
    
    pos = 0 ; The byteposotion in the framebuffer
    
    ; First Byte: FIN(1=finished with this Frame),RSV(0),RSV(0),RSV(0),OPCODE(4 byte)=0001(text) 
    PokeB(*FrameBuffer, %10000001) : pos + 1 ; = 129
    
    ; Second Byte: Masking(1),length(to 125bytes, else we have to extend)
    If MsgLength <= 125                                             ; Length fits in first byte
      PokeA(*Framebuffer + pos, MsgLength + 128)    : pos + 1       ; + 128 for Masking
    ElseIf MsgLength >= 126 And MsgLength <= 65535                  ; We have to extend length to third byte
      PokeA(*Framebuffer + pos, 126 + 128)          : pos + 1       ; 126 for 2 extra length bytes and + 128 for Masking
      PokeA(*FrameBuffer + pos, (MsgLength >> 8))   : pos + 1       ; First Byte
      PokeA(*FrameBuffer + pos, MsgLength)          : pos + 1       ; Second Byte
    Else                                                            ; It's bigger than 65535, we also use 8 extra bytes
      PokeA(*Framebuffer + pos, 127 + 128)          : pos + 1       ; 127 for 8 extra length bytes and + 128 for Masking
      PokeA(*Framebuffer + pos, 0)                  : pos + 1       ; 8 Bytes for payload lenght. We don't support giant packages for now, so first bytes are zero :P
      PokeA(*Framebuffer + pos, 0)                  : pos + 1
      PokeA(*Framebuffer + pos, 0)                  : pos + 1
      PokeA(*Framebuffer + pos, 0)                  : pos + 1
      PokeA(*Framebuffer + pos, MsgLength >> 24)    : pos + 1
      PokeA(*Framebuffer + pos, MsgLength >> 16)    : pos + 1
      PokeA(*Framebuffer + pos, MsgLength >> 8)     : pos + 1
      PokeA(*Framebuffer + pos, MsgLength)          : pos + 1       ; = 10 Byte
    EndIf
    ; Write Masking Bytes
    PokeA(*FrameBuffer + pos, Mask(0))              : pos + 1
    PokeA(*FrameBuffer + pos, Mask(1))              : pos + 1
    PokeA(*FrameBuffer + pos, Mask(2))              : pos + 1
    PokeA(*FrameBuffer + pos, Mask(3))              : pos + 1
    
    ApplyMasking(Mask(), *MsgBuffer)
    
    CopyMemory(*MsgBuffer, *FrameBuffer + pos, MsgLength)
    
    ;For x = 0 To 100 Step 5
      ;Debug Str(PeekA(*FrameBuffer + x)) + " | " + Str(PeekA(*FrameBuffer + x + 1)) + " | " + Str(PeekA(*FrameBuffer + x + 2)) + " | " + Str(PeekA(*FrameBuffer + x + 3)) + " | " + Str(PeekA(*FrameBuffer + x + 4))
    ;Next
    
    If SendNetworkData(connection, *FrameBuffer, Fieldlength + MsgLength) = Fieldlength + MsgLength
      dbg("Textframe send, Bytes: " + Str(Fieldlength + MsgLength))
      ProcedureReturn #True
    Else
      ProcedureReturn #False
    EndIf
    
  EndProcedure
  
  Procedure ReceiveFrame(connection, *MsgBuffer)
    
    *FrameBuffer = AllocateMemory(65536)
      Size = ReceiveNetworkData(connection, *FrameBuffer, 65536)
;     Repeat
;       *FrameBuffer = ReAllocateMemory(*FrameBuffer, 65536)
;       Size = ReceiveNetworkData(connection, *FrameBuffer, 65536)
;       ;Answer.s = Answer.s + PeekS(*FrameBuffer, Size, #PB_UTF8)
;     Until Size <> 65536
;     
    dbg("Received Frame, Bytes: " + Str(Size))
    
    *FrameBuffer = ReAllocateMemory(*FrameBuffer, Size)
    
        ; debug: output any single byte
        If #PB_Compiler_Debugger
          For x = 0 To Size - 1 Step 1
            dbg_bytes.s + Str(PeekA(*FrameBuffer + x)) + " | "
          Next
          dbg(dbg_bytes)
        EndIf
    
    ; Getting informations about package
    If PeekA(*FrameBuffer) & %10000000 > #False
      ;dbg("Frame not fragmented")
      fragmentation.b = #False
    Else
      dbg("Frame fragmented! This not supported for now!")
      fragmentation.b = #True
    EndIf
    
    ; Check for Opcodes
    If PeekA(*FrameBuffer) = %10000001 ; Textframe
      dbg("Text frame")
      frame_typ.w = #frame_text
    ElseIf PeekA(*FrameBuffer) = %10000010 ; Binary Frame
      dbg("Binary frame")
      frame_typ.w = #frame_binary
    ElseIf PeekA(*FrameBuffer) = %10001000 ; Closing Frame
      dbg("Closing frame")
      frame_typ.w = #frame_closing
    ElseIf PeekA(*FrameBuffer) = %10001001 ; Ping
      ; We just answer pings
      *pongbuffer = AllocateMemory(2)
      PokeA(*pongbuffer, 138)
      PokeA(*pongbuffer+1, 0)
      SendNetworkData(connection, *pongbuffer, 2)
      dbg("Received Ping, answered with Pong")
      frame_typ.w = #frame_ping
      ProcedureReturn
    Else
      dbg("Opcode unknown")
      frame_typ.w = #frame_unknown
      ProcedureReturn #False
    EndIf
    
    ; Check masking
    If PeekA(*FrameBuffer + 1) & %10000000 = 128 : masking.b = #True : Else : masking.b = #False : EndIf
    
    dbg("Masking: " + Str(masking))
    
    pos.l = 1
    
    ; check size
    If PeekA(*FrameBuffer + 1) & %01111111 <= 125 ; size is in this byte
      frame_size.l = PeekA(*FrameBuffer + pos) & %01111111 : pos + 1
    ElseIf PeekA(*FrameBuffer + 1) & %01111111 >= 126 ; Size is in 2 extra bytes
      frame_size.l = PeekA(*FrameBuffer + 2) << 8 + PeekA(*FrameBuffer + 3) : pos + 2
    EndIf
    dbg("FrameSize: " + Str(frame_size.l))
    
    If masking = #True
      Dim Mask.a(3)
      Mask(0) = PeekA(*FrameBuffer + pos) : pos + 1
      Mask(1) = PeekA(*FrameBuffer + pos) : pos + 1
      Mask(2) = PeekA(*FrameBuffer + pos) : pos + 1
      Mask(3) = PeekA(*FrameBuffer + pos) : pos + 1
      
      ReAllocateMemory(*MsgBuffer,frame_size)
      CopyMemory(*FrameBuffer + pos, *MsgBuffer, frame_size)
      
      ApplyMasking(Mask(), *MsgBuffer)
    Else
      ReAllocateMemory(*MsgBuffer,frame_size)
      CopyMemory(*FrameBuffer + pos, *MsgBuffer, frame_size)
    EndIf
    
    ProcedureReturn frame_typ
    
  EndProcedure
  
EndModule


CompilerIf #PB_Compiler_IsMainFile
  
  ; Minimal example to send and receive textmessages
  ; The preconfigured testserver "echo.websocket.org" will just echo back everything you've send.

  ;WebsocketClient::SetSSLProxy("https://doko-cafe.de",443)
  Global connection
  
  connection = WebsocketClient::OpenWebsocketConnection("ws://doko-cafe.de:8090")
  
  
  
  
    
  
  ; Proxy Setting:
  ; If you need an encyrpted connection (https/wss), you currently have to use an 
  ; proxy software like stunnel (https://www.stunnel.org) to redirect unencrypted data into an encrypted connection
  ; Example stunnel.conf section:
  ;   [websocket]
  ;   client = yes
  ;   accept = 127.0.0.1:8182
    ; connect = echo.websocket.org:443
  ;WebsocketClient::SetSSLProxy("127.0.0.1",1302)
  
  
  ;connect = "127.0.0.1:1302"
  
  Repeat
    
    If connection
      
      NetworkEvent = NetworkClientEvent(connection)
      
      Select NetworkEvent
          
        Case #PB_NetworkEvent_Data
          Debug "We've got Data"
          *FrameBuffer = AllocateMemory(1)
          Frametyp = WebsocketClient::ReceiveFrame(connection,*FrameBuffer)
          If Frametyp = WebsocketClient::#frame_text
            Debug  "< " + PeekS(*FrameBuffer,MemoryStringLength(*FrameBuffer,#PB_UTF8|#PB_ByteLength),#PB_UTF8|#PB_ByteLength) 
            If WebsocketClient::SendTextFrame(connection, "HelloWorldServer.") = #False
              Debug "Couldn't send. Are we disconnected?"
            EndIf 
          ElseIf Frametyp = WebsocketClient::#frame_binary
            Debug  "< Received Binaryframe" 
          EndIf
          
        Case #PB_NetworkEvent_Disconnect
          If disconnected = #False
            Debug "Disconnected"
          EndIf
          disconnected = #True
          NetworkEvent = #PB_NetworkEvent_None
          
        Case #PB_NetworkEvent_None
          
      EndSelect
      
    EndIf
    Delay(1)
  ForEver
  
CompilerEndIf

Bei Linux (PB6.21 beta 5) stürzt der Client ab mit folgender Nachrcht:

munmap_chunk(): invalid pointer


und unterbricht bei Zeile:
ProcedureReturn frame_typ

Bei Windows fubnktioniert es mit folgender Rückmeldung:

Code: Alles auswählen



WebsocketClient: 2025-04-15 08:20:12 > Connection and Handshake ok
We've got Data
WebsocketClient: 2025-04-15 08:20:12 > Received Frame, Bytes: 37
WebsocketClient: 2025-04-15 08:20:12 > 129 | 35 | 123 | 34 | 85 | 115 | 101 | 114 | 110 | 97 | 109 | 101 | 34 | 58 | 91 | 34 | 34 | 93 | 44 | 34 | 84 | 121 | 112 | 101 | 34 | 58 | 34 | 85 | 115 | 101 | 114 | 108 | 105 | 115 | 116 | 34 | 125 | 
WebsocketClient: 2025-04-15 08:20:12 > Text frame
WebsocketClient: 2025-04-15 08:20:12 > Masking: 0
WebsocketClient: 2025-04-15 08:20:12 > FrameSize: 35
< {"Username":[""],"Type":"Userlist"}
Unter "Userlist"} kommt ein weisses Fragezeichen auf schwarzem Hintergrund, welches ich hier nicht abbilden kann.

Was ist an dem Clientcode falsch? Und gibt es noch einen Linux-Bug?
Ich programmiere nur noch mit Linux.
Linux Mint 21.x
Benutzeravatar
Dadido3
Beiträge: 119
Registriert: 24.02.2007 22:15
Kontaktdaten:

Re: Websocket Client

Beitrag von Dadido3 »

Ich habe den Client-Code nur mal kurz überflogen:
  • Es fehlen überall FreeMemory() aufrufe, das führt zu Speicherlecks.
  • Bei ReAllocateMemory() wird blind die alte Speicheradresse weiterverwendet ohne den Rückgabewert zu beachten.
  • Die vom Server empfangene frame_size wird ohne Überprüfung verwendet um aus einem Speicherbereich zu lesen.
  • Die Rückgabewerte von SendNetworkData und ReceiveNetworkData werden ignoriert.
  • Bei leerem Path wird anscheinend ein doppeltes / im Handshake gesendet.
  • ...
Das nur nach kurzem drüberschauen, ich habe jetzt aber leider keine explizite Stelle gefunden, die deinen Crash erklären würde. Es würde mich aber nicht wundern, wenn der Fehler im irgendwo im Speicherhandling des Client-Codes liegt. "munmap_chunk(): invalid pointer" deutet auch darauf hin:

https://stackoverflow.com/questions/321 ... 4_32118638
Antworten