Rabikant
Posted on November 24th
Build a WebSocket Server in PHP Without Any Library.
"Lets build a php websocket app without a library"
Socket Creation and Setup
$host = '127.0.0.1';
$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);
This section involves setting of the WebSocket server. socket_create(): Creates a socket using IPv4 and TCP which is Transmission Control Protocol and using the SOL_TCP which is the TCP protocol. socket_bind(): Ties the socket to the IP address and port. socket_listen(): Brings the socket to a state where it waits for clients to connect to it through a server.
Handling Client Connections
$clients = [];
echo "WebSocket server started on $host:$portn";
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 connectedn";
$socketKey = array_search($socket, $changedSockets);
unset($changedSockets[$socketKey]); }
This section deals with establishing new client link and manage the connected client accordingly.
$clients[]: A list where all active client will be stored. socket_select():It scans all sockets (including the server’s and the clients’ ones) for new events, like connection or messages. socket_accept() : Takes a connection request from a new client and includes the new client socket with the list of contacted clients.
Handling Incoming Data and Client Disconnections
foreach ($changedSockets as $clientSocket) {
$data = @socket_recv($clientSocket, $buffer, 1024, 0);
if ($data === false || $data = 0) {
echo "client disconnectedn";
$clientKey = array_search($clientSocket, $clients);
unset($clients[$clientKey]);
socket_close($clientSocket);
continue;
}
if (!$handshake) {
performHandshake($clientSocket, $buffer);
$handshake = true;
} else {
$message = unmask($buffer);
if (!empty($message)) {
echo "Received: $messagen";
foreach ($clients as $client) {
if ($client != $clientSocket) {
sendMessage($client, $message);
}
}
}
}
}
- socket_recv(): Data send by client is received by this function.@ Symbol is used to disable any warnings if the client disconnects.
- Client disconnection: ($data === false) if data is not received,client gets disconnected and removed from the list and socket is closed.
- performHandshake():Perform a WebSocket handshake if a client is connected. After the connection is established, communication can begin.
- unmask():this function decodes the WebSocket frames to get the raw messages sent by the client.
- Broadcasting : When a client sends a message it is sent to other users that are connected.
WebSocket Handshake
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 Protocolsrn" .
"Upgrade: websocketrn" .
"Connection: Upgradern" .
"Sec-WebSocket-Accept: $secAcceptrnrn";
socket_write($clientSocket, $handshakeResponse, strlen($handshakeResponse));
}
Performs the WebSocket handshake to establish the connection.
- parseHeaders(): When the client sends the initial request, this function extracts the WebSocket headers from the request.
- Sec-WebSocket-Key: It is the key that is sent by the client to make a connection to W ebSocket
- Sec-WebSocket-Accept: A calculated value that is returned to the client to coSec-WebSocket-Accept: A calculated value that is returned to the client to confirm the WebSocket upgrade.Sec-WebSocket-Accept: A calculated value that is returned to the client to confirm the WebSocket upgrade.Sec-WebSocket-Accept: A calculated value that is returned to the client to confirm the WebSocket upgrade.Sec-WebSocket-Accept: A calculated value that is returned to the client to confirm the WebSocket upgrade.Sec-WebSocket-Accept: A calculated value that is returned to the client to confirm the WebSocket upgrade.
Parsing 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;
}
Parses the HTTP headers sent by the client during the WebSocket connection upgrade request.
Unmasking and Masking Messages
function unmask($payload)
{
$length = ord($payload[1]) & 127;
if ($length == 126) {
$masks = substr($payload, 4, 4);
$data = substr($payload, 8);
} elseif ($length == 127) {
$masks = substr($payload, 10, 4);
$data = substr($payload, 14);
} 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 $unmaskedtext;
}
WebSocket messages are masked for security reasons, so this function "unmasks" the payload by applying the XOR operation on the data with the masking key.
function sendMessage($clientSocket, $message)
{
$message = mask($message);
socket_write($clientSocket, $message, strlen($message));
}
Sends a message to a specific client by first masking the message using the mask() function.
Masking Messages for Sending
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));
}
Prepares a message to be sent to the client by framing and masking it as required by the WebSocket protocol.
HTML Structure
<! DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebSocket Client</title>
<!-- CSS Styles Here -->
</head>
<body>
<div class="container">
<h2>WebSocket Client</h2>
<button onclick="connect()">Connect</button>
<input type="text" id="messageInput" placeholder="Type your message here...">
<button onclick="sendMessage()">Send Message</button>
<div id="output"></div>
</div>
<script>
// JavaScript code here
</script>
</body>
</html>
- HTML Tags: Defines the structure of the WebSocket client page.
- : A text input for users to type their message.
- Buttons:
- The Connect button triggers the connect() function to establish a WebSocket connection with the server.
- The Send Message button triggers the sendMessage() function to send a message to the WebSocket server.
- : Displays messages (connection status and chat messages) received from the WebSocket server.
CSS Styling
body {
font-family: Arial, sans-serif;
background-color: #f4f4f9;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
.container {
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 15px rgba(0, 0, 0, 0.1);
width: 300px;
text-align: center;
}
h2 {
color: #333;
}
button {
background-color: #4CAF50;
color: white;
border: none;
padding: 10px 20px;
margin: 10px 0;
border-radius: 5px;
cursor: pointer;
}
button:hover {
background-color: #45a049;
}
input {
width: calc(100% - 22px);
padding: 10px;
margin-bottom: 10px;
border-radius: 5px;
border: 1px solid #ccc;
}
#output {
margin-top: 20px;
background-color: #f9f9f9;
padding: 10px;
border-radius: 5px;
height: 150px;
overflow-y: auto;
border: 1px solid #ddd;
color: #555;
font-size: 14px;
}
- General styling: Makes the interface clean and user-friendly.
- Container: Centers the WebSocket client interface in the middle of the screen and adds padding and a shadow for visual effect.
- Buttons: Styled with green background color and hover effect for interaction.
- Message Input: Provides space for typing messages with a border and padding.
- Output Box: Displays messages with a scrollbar when the content exceeds the box size.
JavaScript for WebSocket Connection
let socket;
function connect() {
socket = new WebSocket("ws://127.0.0.1:8080");
socket.onopen = function(event) {
document.getElementById("output").innerHTML += "<span style='color: green;'>Connected to server</span><br>";
};
socket.onmessage = function(event) {
document.getElementById("output").innerHTML += "<span style='color: blue;'>Message received:</span> " + event.data + "<br>";
};
socket.onclose = function(event) {
document.getElementById("output").innerHTML += "<span style='color: red;'>Disconnected from server</span><br>";
};
}
function sendMessage() {
let message = document.getElementById("messageInput").value || "Hello, Server!";
socket.send(message);
document.getElementById("output").innerHTML += "<span style='color: purple;'>Message sent:</span> " + message + "<br>";
document.getElementById("messageInput").value = "";
}
- WebSocket Connection:
- The connect() function creates a WebSocket connection to the server at ws://127.0.0.1:8080.
- onopen: Triggers when the connection is successfully opened. Displays a message in green saying "Connected to server".
- onmessage: Triggers when a message is received from the server. Displays the message in blue under "Message received".
- onclose: Triggers when the connection is closed. Displays a message in red saying "Disconnected from server".
Sending and Displaying Messages
function sendMessage($clientSocket, $message)
{
$message = mask($message);
socket_write($clientSocket, $message, strlen($message));
}
- Purpose: Sends a message to the WebSocket server.
- Message Input: The message is retrieved from the input box. If no message is typed, it defaults to "Hello, Server!".
- socket.send(message): Sends the message to the server through the WebSocket.
- Display: Shows the sent message in purple and then clears the message input field.
Summary
- Connect: Establishes a WebSocket connection.
- Send Message: Sends typed messages to the server and displays both sent and received messages.
- Live Feedback: Shows connection status and received messages dynamically in the output section.
Create a file name websocket.php and paste the following code:
<?php
$host = '127.0.0.1';
$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:$portn";
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 connectedn";
$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 disconnectedn";
$clientKey = array_search($clientSocket, $clients);
unset($clients[$clientKey]);
socket_close($clientSocket);
continue;
}
if (!$handshake) {
performHandshake($clientSocket, $buffer);
$handshake = true;
} else {
$message = unmask($buffer);
if (!empty($message)) { $handshake = true;
echo "Received: $messagen";
foreach ($clients as $client) {
if ($client != $clientSocket) {
sendMessage($client, $message);
}
}
}
}
}
}
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 Protocolsrn" .
"Upgrade: websocketrn" .
"Connection: Upgradern" .
"Sec-WebSocket-Accept: $secAcceptrnrn";
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)
{
$length = ord($payload[1]) & 127;
if ($length == 126) {
$masks = substr($payload, 4, 4);
$data = substr($payload, 8);
} elseif ($length == 127) {
$masks = substr($payload, 10, 4);
$data = substr($payload, 14);
} 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 $unmaskedtext;
}
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));
}
Create a file named index.html and paste the following code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebSocket Client</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f9;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
.container {
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 15px rgba(0, 0, 0, 0.1);
width: 300px;
text-align: center;
}
h2 {
color: #333;
}
button {
background-color: #4CAF50;
color: white;
border: none;
padding: 10px 20px;
margin: 10px 0;
border-radius: 5px;
cursor: pointer;
}
button:hover {
background-color: #45a049;
}
input {
width: calc(100% - 22px);
padding: 10px;
margin-bottom: 10px;
border-radius: 5px;
border: 1px solid #ccc;
}
#output {
margin-top: 20px;
background-color: #f9f9f9;
padding: 10px;
border-radius: 5px;
height: 150px;
overflow-y: auto;
border: 1px solid #ddd;
color: #555;
font-size: 14px;
}
</style>
</head>
<body>
<div class="container">
<h2>WebSocket Client</h2>
<button onclick="connect()">Connect</button>
<input type="text" id="messageInput" placeholder="Type your message here...">
<button onclick="sendMessage()">Send Message</button>
<div id="output"></div>
</div>
<script>
let socket;
function connect() {
socket = new WebSocket("ws://127.0.0.1:8080");
socket.onopen = function(event) {
document.getElementById("output").innerHTML += "<span style='color: green;'>Connected to server</span><br>";
};
socket.onmessage = function(event) {
document.getElementById("output").innerHTML += "<span style='color: blue;'>Message received:</span> " + event.data + "<br>";
};
socket.onclose = function(event) {
document.getElementById("output").innerHTML += "<span style='color: red;'>Disconnected from server</span><br>";
};
}
function sendMessage() {
let message = document.getElementById("messageInput").value || "Hello, Server!";
socket.send(message);
document.getElementById("output").innerHTML += "<span style='color: purple;'>Message sent:</span> " + message + "<br>";
document.getElementById("messageInput").value = "";
}
</script>
</body>
</html>
To start the websocket server run the following code:
php websocket.php
Next, open the index.html file and connect to the WebSocket server and starting chatting.

Complete Code
The project is available on our GitHub : https://github.com/piehostHQ/php-native-ws-server
