Om gegevens tussen applicaties uit te wisselen, kan je naargelang de situatie verschillende methoden gebruiken. Laten we een oplossing zoeken en ontwikkelen voor de volgende situatie. Je gebruikt webpagina's als interactie met de gebruiker en je wilt gegevens uitwisselen tussen verschillende apparaten en diensten.
Het eenvoudige en open source protocol WebSocket is in dit geval een kandidaat. WebSocket wordt door alle moderne browsers ondersteunt. Het enige wat ontbreekt is een server die ontvangen gegevens kan doorsturen en/of verwerken. Daar het WebSocket protocol vrij beschikbaar is, kan je voor elke programmeertaal de broncode van een WebSocket server vinden. Vanzelfsprekend ga je op zoek naar een WebServer in een programmeertaal die je ligt.
Daar ik overweg kan met PHP ging ik in deze tip op zoek naar een PHP WebSocket server. Twee op het internet gevonden kandidaten kwamen in aanmerking: Build a WebSocket Server in PHP Without Any Library en Websocket Client and Server for PHP.
De eerste webpagina bevatte naast de broncode ook uitleg en vormt de basis voor het volgende PHP WebSocket script. Daarbij vertrek ik deze keer wel vanaf een Debian 13 testsysteem waarop PHP en de webserver Apache geïnstalleerd en geconfigureerd zijn.
Zorg dat je het PHP script in een aparte (nog aan te maken) map in jouw Apache webmap aanmaakt en opslaat (/var/www/html/websocket/websocket.php).
<?php $host = 'localhost'; //$host = '192.168.129.2'; $port = 8080; $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1); socket_bind($socket, $host, $port); socket_listen($socket); $clients = []; echo "WebSocket server started on $host:$port\n"; while (true) { $changedSockets = $clients; $changedSockets[] = $socket; $write = []; $except = []; socket_select($changedSockets, $write, $except, 0, 10); if (in_array($socket, $changedSockets)) { $newSocket = socket_accept($socket); $clients[] = $newSocket; $handshake = false; echo "New client connected\n"; $socketKey = array_search($socket, $changedSockets); unset($changedSockets[$socketKey]); } foreach ($changedSockets as $clientSocket) { $data = @socket_recv($clientSocket, $buffer, 1024, 0); if ($data === false || $data = 0) { echo "Client disconnected\n"; $clientKey = array_search($clientSocket, $clients); unset($clients[$clientKey]); socket_close($clientSocket); continue; } if (!$handshake) { performHandshake($clientSocket, $buffer); $handshake = true; } else if (!is_null($buffer)){ // API and Payload structure: https://en.wikipedia.org/wiki/WebSocket#Web_API $opcode = ord($buffer) & 15; $message = unmask($buffer); switch ($opcode) { case 1: $log="UTF-8 text data: "; if (!empty($message)) { echo $log, "$message\n"; foreach ($clients as $client) { if ($client != $clientSocket) { sendMessage($client, $message); } } } break; case 2: echo "Binary data: Not implemented\n"; break; case 8: $log="Connection close: "; $statuscode=ord($message)*256 + ord($message[1]); switch ($statuscode) { case 1000: echo $log, "Normal closure\n"; break; case 1001: echo $log, "Client going away\n"; break; case 1002: echo $log, "Protocol error\n"; break; case 1003: echo $log, "Unsupported data\n"; break; case 1004: echo $log, "Reserved\n"; break; case 1005: echo $log, "No status received\n"; break; case 1006: echo $log, "Abnormal closure\n"; break; case 1007: echo $log, "Invalid payload data\n"; break; case 1008: echo $log, "Policy violation\n"; break; case 1009: echo $log, "Message too big\n"; break; case 1010: echo $log, "Mandatory extension\n"; break; case 1011: echo $log, "Internal error\n"; break; case 1012: echo $log, "Service restart\n"; break; case 1013: echo $log, "Try again later\n"; break; case 1014: echo $log, "Bad gateway\n"; break; case 1015: echo $log, "TLS handshake\n"; break; default: echo $log, "Unknown Status code: " . $statuscode . "\n"; break; } $clientKey = array_search($clientSocket, $clients); unset($clients[$clientKey]); socket_close($clientSocket); break; case 9: echo "Ping: Not implemented\n"; break; case 10: echo "Pong: Not implemented\n"; break; default: echo "Opcode ", $opcode, " reserved\n"; break; } } } } function performHandshake($clientSocket, $headers) { $headers = parseHeaders($headers); $secKey = $headers['Sec-WebSocket-Key']; $secAccept = base64_encode(pack('H*', sha1($secKey . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'))); $handshakeResponse = "HTTP/1.1 101 Switching Protocols\r\n" . "Upgrade: websocket\r\n" . "Connection: Upgrade\r\n" . "Sec-WebSocket-Accept: $secAccept\r\n\r\n"; socket_write($clientSocket, $handshakeResponse, strlen($handshakeResponse)); } // Parse HTTP Headers function parseHeaders($headers) { $headers = explode("\r\n", $headers); $headerArray = []; foreach ($headers as $header) { $parts = explode(": ", $header); if (count($parts) === 2) { $headerArray[$parts[0]] = $parts[1]; } } return $headerArray; } function unmask($payload) { // Protocol: https://en.wikipedia.org/wiki/WebSocket#Protocol $length = ord($payload[1]) & 127; if ($length == 126) { $masks = substr($payload, 4, 4); $data = substr($payload, 8); $length = ord($payload[2])*256 + ord($payload[3]); } elseif ($length == 127) { $masks = substr($payload, 10, 4); $data = substr($payload, 14); $length = ord($payload[2])*pow(2, 56) + ord($payload[3]*pow(2, 48)); $length += ord($payload[4])*pow(2, 40) + ord($payload[5]*pow(2, 32)); $length += ord($payload[6])*pow(2, 24) + ord($payload[7]*pow(2, 16)); $length += ord($payload[8])*pow(2, 8) + ord($payload[9]); } else { $masks = substr($payload, 2, 4); $data = substr($payload, 6); } $unmaskedtext = ''; for ($i = 0; $i < strlen($data); ++$i) { $unmaskedtext .= $data[$i] ^ $masks[$i % 4]; } return substr($unmaskedtext, 0, $length); } function sendMessage($clientSocket, $message) { $message = mask($message); socket_write($clientSocket, $message, strlen($message)); } function mask($message) { $frame = []; $frame[0] = 129; $length = strlen($message); if ($length <= 125) { $frame[1] = $length; } elseif ($length <= 65535) { $frame[1] = 126; $frame[2] = ($length >> 8) & 255; $frame[3] = $length & 255; } else { $frame[1] = 127; $frame[2] = ($length >> 56) & 255; $frame[3] = ($length >> 48) & 255; $frame[4] = ($length >> 40) & 255; $frame[5] = ($length >> 32) & 255; $frame[6] = ($length >> 24) & 255; $frame[7] = ($length >> 16) & 255; $frame[8] = ($length >> 8) & 255; $frame[9] = $length & 255; } foreach (str_split($message) as $char) { $frame[] = ord($char); } return implode(array_map('chr', $frame)); } ?>
Naast een server heb je één of meerdere clients nodig waarmee de server kan communiceren.
Dit kan eveneens terug met o.a. PHP (/var/www/html/websocket/client.php).
<?php error_reporting(E_ALL); /** * Very basic websocket client. * Supporting handshake from drafts: * draft-hixie-thewebsocketprotocol-76 * draft-ietf-hybi-thewebsocketprotocol-00 * * @author Simon Samtleben <web@lemmingzshadow.net> * @version 2011-09-15 * @author Dany Pinoy https://linux.pindanet.be/faq/tips25/websocket.html * @version 2025-11-16 */ // Use terminal arg as POST en GET arg, example: php -e /var/www/html/websocket/client.php message=Cli%20PHP%20Client // Use terminal arg as POST en GET arg, example: php -e /var/www/html/websocket/client.php message=$(bash /var/www/html/websocket/urlencode.sh '{"target":"server", "message":"JSON bericht"}') if (!isset($_SERVER["HTTP_HOST"])) { parse_str(implode('&', array_slice($argv, 1)), $_GET); parse_str(implode('&', array_slice($argv, 1)), $_POST); } if (json_validate($_POST["message"])) { $message = $_POST["message"]; } else { $message = htmlspecialchars($_POST["message"]); } class WebsocketClient { private $_Socket = null; public function __construct($host, $port, $token) { $this->_connect($host, $port, $token); } public function __destruct() { $this->_disconnect(); } public function sendData($data) { $this->_sendFrame(chr(bindec("10000001")), $data); //Final frame, UTF-8-encoded text /* fwrite($this->_Socket, "\x00" . $data . "\xff" ) or die('Error:' . $errno . ':' . $errstr); $wsData = fread($this->_Socket, 2000); $retData = trim($wsData,"\x00\xff"); return $retData; */ } private function _sendFrame($frame, $data) { $length = strlen($data); $masks = random_bytes(4); if ($length <= 125) { $frame .= chr($length | 128); // Masked, Length $frame .= $masks[0]; // Masking Key $frame .= $masks[1]; $frame .= $masks[2]; $frame .= $masks[3]; } // Masked message for ($i = 0; $i < $length; ++$i) { $frame .= chr(ord($data[$i] ^ $masks[$i % 4])); } // send frame: fwrite($this->_Socket, $frame, strlen($frame)) or die('Error:' . $errno . ':' . $errstr); } private function _connect($host, $port, $token) { $key = base64_encode(($this->_generateRandomString(32))); $header = ""; $header .= "GET /?token={$token} HTTP/1.1\r\n"; $header .= "Host: {$host}:{$port}\r\n"; $header .= "Upgrade: websocket\r\n"; $header .= "Connection: Upgrade\r\n"; $header .= "Sec-WebSocket-Key: {$key}\r\n"; $header .= "Sec-WebSocket-Protocol: chat, superchat\r\n"; $header .= "Sec-WebSocket-Version: 13\r\n"; $header .= "Origin: http://hs.byll.com.br\r\n"; $header .= "\r\n"; $this->_Socket = fsockopen($host, $port, $errno, $errstr, 2); fwrite($this->_Socket, $header) or die('Error: ' . $errno . ':' . $errstr); $response = fread($this->_Socket, 2000); print_r($response); /** * @todo: check response here. Currently not implemented cause "2 key handshake" is already deprecated. * See: http://en.wikipedia.org/wiki/WebSocket#WebSocket_Protocol_Handshake */ return true; } private function _disconnect() { echo "Disconnecting...\r\n"; $data = chr(bindec("00000011")) . chr(bindec("11101001")); //Status code: 1001 Going away. $this->_sendFrame(chr(bindec("10001000")), $data); //Final frame, Close fclose($this->_Socket); } private function _generateRandomString($length = 10, $addSpaces = true, $addNumbers = true) { $characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"§$%&/()=[]{}'; $useChars = array(); // select some random chars: for($i = 0; $i < $length; $i++) { $useChars[] = $characters[mt_rand(0, strlen($characters)-1)]; } // add spaces and numbers: if($addSpaces === true) { array_push($useChars, ' ', ' ', ' ', ' ', ' ', ' '); } if($addNumbers === true) { array_push($useChars, rand(0,9), rand(0,9), rand(0,9)); } shuffle($useChars); $randomString = trim(implode('', $useChars)); $randomString = substr($randomString, 0, $length); return $randomString; } } $WebSocketClient = new WebsocketClient('localhost', 8080, '5624c7d1f10b1'); echo $WebSocketClient->sendData($message); unset($WebSocketClient); ?>
Tijd voor een eerste test. Daarvoor gebruiken we twee terminalvensters. Het eerste terminalvenster zorgt voor het uitvoeren van de PHP WebSocket server:
dany@pindabook:~$ php /var/www/html/websocket/websocket.php
WebSocket server started on localhost:8080
Via het tweede terminalvenster sturen we met het PHP WebSocket client script berichten naar de PHP WebSocket server.
dany@pindabook:~$ php -e /var/www/html/websocket/client.php message=Cli%20PHP%20Client
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: ukpkJoikWYRtYHdmzkpC3dqfmHc=
Disconnecting...
Let daarbij op het gebruik van de code %20, wat in een URL een spatie voorstelt.
Het PHP WebSocket client script bouwt daarbij een verbinding op (Upgrade) met de WebSocket server, stuurt het bericht en verbreekt onmiddellijk de verbinding.
Ondertussen zie je in het eerste terminalvenster dat de PHP WebSocket server het bericht heeft ontvangen.
New client connected
UTF-8 text data: Cli PHP Client
Connection close: Client going away
Om ons geen zorgen te hoeven maken over de te gebruiken URL codes, gaan we ook dit via een script automatiseren.
Maak daarvoor in het tweede terminalvenster het volgende Bash script aan (op de meeste Apache webserver systemen moet je voor de nano opdracht sudo gebuiken):
dany@pindabook:~$ nano /var/www/html/websocket/urlencode.sh
Het volgende script gaat wat bruut te werk, want het vervangt alle niet letters en cijfers door de URL code. Hoewel er mooiere oplossingen zijn, werkt het.
#!/bin/bash
string="${1}"
strlen=${#string}
encoded=""
for (( pos=0 ; pos<strlen ; pos++ )); do
c=${string:$pos:1}
case "$c" in
[-_.~a-zA-Z0-9] ) o="${c}" ;;
* ) printf -v o '%%%02x' "'$c"
esac
encoded+="${o}"
done
echo "${encoded}"
Hetzelfde WebSocket bericht kan je nu sturen met het PHP WebSocket script, geholpen door het urlencode.sh bash script:
dany@pindabook:~$ php -e /var/www/html/websocket/client.php message=$(bash /var/www/html/websocket/urlencode.sh "Cli PHP Client")
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: QxKvy6BWESl7mY6BqE8I2qBGNvo=
Disconnecting...
Let hierbij op het verkrijgen van een andere Sec-WebSocket-Accept code.
Deze zorgt dat elke WebSocket verbinding een unieke identificatie krijgt, waardoor meerdere clients tegelijk met de server kunnen communiceren.
Dankzij het urlencode.sh Bash script, kunnen we nu ook complexere berichten sturen, zoals veel gebruikte JSON berichten. Een voorbeeld:
dany@pindabook:~$ php -e /var/www/html/websocket/client.php message=$(bash /var/www/html/websocket/urlencode.sh '{"target":"server", "message":"JSON bericht"}')
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: aEdsBELpGegL/Kx6iIla8XeU9lE=
Disconnecting...
In het eerste terminalvenster verschijnt het JSON bericht.
New client connected
UTF-8 text data: {"target":"server", "message":"JSON bericht"}
Connection close: Client going away
Breek in het eerste terminalvenster de PHP WebSocket server af met de toetscombinatie +.
Het laten verwerken van ontvangen WebSocket berichten kan handig zijn om bijvoorbeeld acties uit te voeren door een andere gebruiker. Zo kan je sedert Debian Bookworm geen ICY-META data uit een MP3 stream meer opslaan in een bestand (zie Reactie op de pagina Online radio streams afspelen.)
Om ontvangen berichten te verwerken kan je ofwel het PHP WebSocket script aanpassen en de berichten intern verwerken of de berichten extern verwerken. Persoonlijk vind ik de tweede methode handiger. Maak een Bash script aan om de uitvoer van de PHP WebSocket server te verwerken.
dany@pindabook:~$ nano websocket_processor.sh
Het script start de PHP WebSocket server en luistert constant naar de uitvoer ervan. Op basis van bepaalde ontvangen berichten (in de uitvoer) kunnen dan bepaalde acties uitgevoerd worden. Onderstaande script zal elk ontvangen tekstbericht omzetten naar een melding op het scherm.
#!/bin/bash
# Blijf constant luisteren
while read message
do
# De volgende twee regels activeren om te testen
timestamp=$(date)
echo "$timestamp: $message"
# Reageer op geldige berichten
if [[ "$message" == "UTF-8 text data: "* ]]; then
# Ontvangen bericht weergeven
echo "${message#*UTF-8 text data: }"
kdialog --title "Websocket bericht" --passivepopup "${message#*UTF-8 text data: }" 5
fi
# naar de Websocket berichten
done < <(php /var/www/html/websocket/websocket.php)
Start de PHP WebSocket server nu met het Bash script:
dany@pindabook:~$ bash websocket_processor.sh
zo 16 nov 2025 17:39:15 CET: WebSocket server started on localhost:8080
Je merkt onmiddellijk de invloed van het Bash script, nml. door de toevoeging van de datum en tijd aan elke regel van de PHP WebSocket server uitvoer.
In het tweede terminal venster sturen we opnieuw het eenvoudige tekstbericht naar de WebSocket server.
dany@pindabook:~$ php -e /var/www/html/websocket/client.php message=$(bash /var/www/html/websocket/urlencode.sh "Cli PHP Client")
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: MhyGR2wepanLpZKx7NEm8py9fo4=
Disconnecting...
Op het scherm verschijnt de melding met het ontvangen tekstbericht. In het tweede terminalvenster verschijnen naast de uitvoer van de PHP WebSocket server (regels met datum en tijd) ook het afgezonderde tekstbericht.
zo 16 nov 2025 17:46:17 CET: New client connected
zo 16 nov 2025 17:46:17 CET: UTF-8 text data: Cli PHP Client
Cli PHP Client
zo 16 nov 2025 17:46:18 CET: Connection close: Client going away
Maar kan onze PHP WebSocket server ook communiceren met de in browsers ingebouwde WebSocket clients. Wel indien ons script en de browserontwikkelaars de WebSocket regels correct hebben toegepast moet dit lukken.
We testen dit met een eenvoudige chat webpagina (/var/www/html/websocket/index.html).
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <style> #verzonden { text-align: left; vertical-align: top; } #ontvangen { text-align: right; vertical-align: top; } #verzonden strong, #verzonden span { padding: .1em .5em; margin: .1em; display: inline-block; border-radius: 1em; background-color: Gainsboro; color: black; } #ontvangen span { padding: .1em .5em; margin: .1em; display: inline-block; border-radius: 1em; padding: 5px; background-color: DodgerBlue; color: white; } </style> <title>Chatten met Websocket</title> </head> <body> <h1>Chatten met Websocket</h1> <input type="text" id="bericht"> <input type="submit" value="Verzenden" onclick="sendMessage(document.getElementById('bericht').value);"> <table width="100%"> <tr> <th style="text-align: left;">Verzonden</th> <th style="text-align: right;">Ontvangen</th> </tr> <tr> <td id="verzonden"></td> <td id="ontvangen"></td> </tr> </table> <div id="ontvangen"></div> <script> function connect() { socket = new WebSocket("ws://localhost:8080"); socket.onopen = function(event) { document.getElementById('verzonden').innerHTML += "<strong>Verbonden met server</strong><br>"; }; socket.onmessage = function(event) { document.getElementById('ontvangen').innerHTML += "<span>" + event.data + "</span><br>"; }; socket.onclose = function(event) { document.getElementById('verzonden').innerHTML += "<strong style='color: red;'>Verbinding met server verbroken</strong><br>"; }; socket.onerror = function(event) { document.getElementById('verzonden').innerHTML += "<strong style='color: red;'>Serverfout</strong><br>"; }; } function sendMessage(message) { if (typeof socket !== 'undefined') { if (socket.readyState) { socket.send(message); document.getElementById('verzonden').innerHTML += "<span>" + message + "</span><br>"; } } else { console.log("Websocket not ready yet!"); } } // Start Websocket connect(); // Connect to Websocket server </script> </body> </html>
Surf met een browser naar de Chat webpagina (http://localhost/websocket/).
De webpagina toont een melding dat deze is verbonden met onze WebSocket server. Typ in het invoerveld een bericht en verzend deze. Na het verzenden verschijnt ook het bericht dat naar de WebSocket server werd verzonden op de webpagina. Terwijl in het eerste terminalvenster het door de PHP WebSocket ontvangen bericht verschijnt.
Sturen we opnieuw in het tweede terminalvenster een eenvoudig bericht en een complex JSON bericht naar de PHP WebSocket server:
dany@pindabook:~$ php -e /var/www/html/websocket/client.php message=$(bash /var/www/html/websocket/urlencode.sh "Cli PHP Client")
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: xCHIUKJHOPaIHRGv8EtqiV1tYrA=
Disconnecting...
dany@pindabook:~$ php -e /var/www/html/websocket/client.php message=$(bash /var/www/html/websocket/urlencode.sh '{"target":"server", "message":"JSON bericht"}')
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: z1Wfi7vfeq9RMN6KOOzLA63LYPg=
Disconnecting...
In het eerste terminalvenster breken we de PHP WebSocket server af met de toetscombinatie +.
Verschillende meldingen en de twee berichten verschijnen op de Chat webpagina.
Op de afbeelding zie je een afwijkende URL in de adresbalk, maar dit is te wijten aan de configuratie van mijn Apache webserver.
De URL op een standaard geconfigureerde Apache webserver is http://localhost/websocket/.

Sluit de browser af.
Om enkel bepaalde WebSocket berichten te verwerken, moeten we het Bash script websocket_processor aanpassen. We gebruiken daarvoor de jq opdracht om JSON berichten in Bash te lezen en te interpreteren. M.a.w. zorg dat jq op jouw systeem is geïnstalleerd.
Kopieer het originele Bash script naar websocket_processor_json.sh en open het met een teksteditor:
dany@pindabook:~$ cp websocket_processor.sh websocket_processor_json.sh
dany@pindabook:~$ nano websocket_processor_json.sh
Pas het script aan tot:
#!/bin/bash
# Blijf constant luisteren
while read message
do
# De volgende twee regels activeren om te testen
timestamp=$(date)
echo "$timestamp: $message"
# Reageer op geldige berichten
if [[ "$message" == "UTF-8 text data: "* ]]; then
# Ontvangen JSON bericht afzonderen
json=${message#*UTF-8 text data: }
target=$(echo $json | jq -r '.target')
if [[ $target == 'server' ]]; then
servermessage=$(echo $json | jq -r '.message')
echo "$servermessage"
kdialog --title "Websocket bericht" --passivepopup "$servermessage" 5
fi
fi
# naar de Websocket berichten
done < <(php /var/www/html/websocket/websocket.php)
Het Bash script zondert het JSON bericht af.
Daarna wordt onderzocht of het JSON bericht de eigenschap target de waarde server bevat.
Indien dit zo is, wordt de waarde in de eigenschap message als melding op het scherm getoond.
Test dit door het aangepaste Bash script op te starten:
dany@pindabook:~$ bash websocket_processor_json.sh
zo 16 nov 2025 18:34:58 CET: WebSocket server started on localhost:8080
In het tweede terminalvenster sturen we het JSON bericht:
dany@pindabook:~$ php -e /var/www/html/websocket/client.php message=$(bash /var/www/html/websocket/urlencode.sh '{"target":"server", "message":"JSON bericht"}')
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: YNccuWYBxgq+XZrALkjAzK7XpfE=
Disconnecting...
In het eerste terminalvenster verschijnt:
zo 16 nov 2025 18:43:13 CET: New client connected
zo 16 nov 2025 18:43:13 CET: UTF-8 text data: {"target":"server", "message":"JSON bericht"}
JSON bericht
zo 16 nov 2025 18:43:13 CET: Connection close: Client going away
En op het scherm verschijnt de melding:

Je kunt deze tip verder aanvullen bijvoorbeeld om de webserver automatisch op te starten m.b.v. een systemd service.
Mits enkele kleine aanpassingen in de scripts kan je de server en client op verschillende apparaten draaien om deze met elkaar te laten communiceren.
Enz.
dany@pindabook:~$ sudo rm -r /var/www/html/websocket
[sudo] wachtwoord voor dany:
dany@pindabook:~$ rm websocket_processor*.sh