Websocket Client
Websocket Client
I've developed an websocket client.
This is not a SERVER, this is a CLIENT who connect to a server.
I've developed it to query an external API/Service running on a websocket-server.
It's not fully tested, and misses some features (look TODO in source), but works with the testserver at echo.websocket.org.
https://gist.github.com/Netzvamp/8623def14501de15c9e4
My question to improve it further:
Is there an easy way to open an encrypted (TSL/SSL) connections to send raw data? Looks like Win32-API is only for HTTPS, but not usable for websockets and not crossplatform.
I've seen cryptlib, does anyone have experience with this lib? Are there better solutions?
This is not a SERVER, this is a CLIENT who connect to a server.
I've developed it to query an external API/Service running on a websocket-server.
It's not fully tested, and misses some features (look TODO in source), but works with the testserver at echo.websocket.org.
https://gist.github.com/Netzvamp/8623def14501de15c9e4
My question to improve it further:
Is there an easy way to open an encrypted (TSL/SSL) connections to send raw data? Looks like Win32-API is only for HTTPS, but not usable for websockets and not crossplatform.
I've seen cryptlib, does anyone have experience with this lib? Are there better solutions?
Last edited by Netzvamp on Wed Dec 02, 2015 5:55 pm, edited 2 times in total.
Re: Websocket Client
Hi,
you know thhese threads:
http://www.purebasic.fr/english/viewtop ... 13&t=56873
http://www.purebasic.fr/english/viewtop ... 12&t=61606
Bernd
you know thhese threads:
http://www.purebasic.fr/english/viewtop ... 13&t=56873
http://www.purebasic.fr/english/viewtop ... 12&t=61606
Bernd
Re: Websocket Client
Yes, i know them. But these are SERVERS, this is an CLIENT who connect to an server. I've developed it, cause i have an external API-Server/Service based on websocket and i needed to query data with purebasic.infratec wrote:Hi,
you know thhese threads:
http://www.purebasic.fr/english/viewtop ... 13&t=56873
http://www.purebasic.fr/english/viewtop ... 12&t=61606
Bernd
Re: Websocket Client
Hello, just to notice a little bug: pong answer has to be masked, or the server disconnects you. Great script though, thank you Netzvamp
- Kwai chang caine
- Always Here
- Posts: 5357
- Joined: Sun Nov 05, 2006 11:42 pm
- Location: Lyon - France
Re: Websocket Client
Someone have a simple PHP websocket server code who works with this great client ?
I have found this and not understand how use it, because there are also an IP to enter on the server and the client
https://medium.com/@cn007b/super-simple ... 2cd5893575
I have found this and not understand how use it, because there are also an IP to enter on the server and the client
https://medium.com/@cn007b/super-simple ... 2cd5893575
Code: Select all
<?php
$address = '0.0.0.0';
$port = 12345;
// Create WebSocket.
$server = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($server, SOL_SOCKET, SO_REUSEADDR, 1);
socket_bind($server, $address, $port);
socket_listen($server);
$client = socket_accept($server);
// Send WebSocket handshake headers.
$request = socket_read($client, 5000);
preg_match('#Sec-WebSocket-Key: (.*)\r\n#', $request, $matches);
$key = base64_encode(pack(
'H*',
sha1($matches[1] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
));
$headers = "HTTP/1.1 101 Switching Protocols\r\n";
$headers .= "Upgrade: websocket\r\n";
$headers .= "Connection: Upgrade\r\n";
$headers .= "Sec-WebSocket-Version: 13\r\n";
$headers .= "Sec-WebSocket-Accept: $key\r\n\r\n";
socket_write($client, $headers, strlen($headers));
// Send messages into WebSocket in a loop.
while (true) {
sleep(1);
$content = 'Now: ' . time();
$response = chr(129) . chr(strlen($content)) . $content;
socket_write($client, $response);
}?>
The happiness is a road...
Not a destination
Not a destination
Re: Websocket Client
You don't need to enter tne server IP
0.0.0.0 means that it listens on all interfaces.
Only if you want to restrict it to one interface, you have to change it to the IP of the interface you want to use.
But since a server has fixed IP addresses you only need to do this once.
0.0.0.0 means that it listens on all interfaces.
Only if you want to restrict it to one interface, you have to change it to the IP of the interface you want to use.
But since a server has fixed IP addresses you only need to do this once.
- Kwai chang caine
- Always Here
- Posts: 5357
- Joined: Sun Nov 05, 2006 11:42 pm
- Location: Lyon - France
Re: Websocket Client
Waaaahh !!! finaly a good news after all this search without succes
So you say to me, normally this two codes above must work ?
I have try several time without succes yesterday ....
Good...i try another time today, now i know that can works, perhaps i have missing something
What adress i must write ?
So you say to me, normally this two codes above must work ?
I have try several time without succes yesterday ....
Good...i try another time today, now i know that can works, perhaps i have missing something
What adress i must write ?
Code: Select all
Http://www.myserver.fr/my/path/index.php
Http://www.myserver.fr
www.myserver.fr
The happiness is a road...
Not a destination
Not a destination
Re: Websocket Client
To start the 'server' you have to call
http://www.myserver.fr/my/path/index.php
But yout client needs to communicate with www.myserver.fr port 12345
http://www.myserver.fr/my/path/index.php
But yout client needs to communicate with www.myserver.fr port 12345
- Kwai chang caine
- Always Here
- Posts: 5357
- Joined: Sun Nov 05, 2006 11:42 pm
- Location: Lyon - France
Re: Websocket Client
Thanks INFRATEC for your precious explanation, that works now
But after several try, i discovered WebSocket lock the loop and wait client request
So one time more, it's not what i search to do
I want each part can send to other everytime, and the other answer if he want....but apparently, it's not simple in PHP
I continue my searchs.....again thanks
But after several try, i discovered WebSocket lock the loop and wait client request
So one time more, it's not what i search to do
I want each part can send to other everytime, and the other answer if he want....but apparently, it's not simple in PHP
I continue my searchs.....again thanks
The happiness is a road...
Not a destination
Not a destination
- skinkairewalker
- Enthusiast
- Posts: 637
- Joined: Fri Dec 04, 2015 9:26 pm
Re: Websocket Client
I'm trying to use this code to stress test a websocket server, but the client simply crashes without reporting any errors.
screenshot : https://prnt.sc/9OhwqmUCwg7y
Does anyone have any idea what could be happening?
screenshot : https://prnt.sc/9OhwqmUCwg7y
Does anyone have any idea what could be happening?
Code: Select all
; Websocketclient by Netzvamp
; Version: 2016/01/08
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)
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.
Global connection
connection = WebsocketClient::OpenWebsocketConnection("ws://localhost:80/game/ws")
Debug "Connected on game/ws"
; 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",8182)
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
ForEver
CompilerEndIf