PHP and HTML5 WebSocket server and client communication

PHP and HTML5 WebSocket server and client communication

WebSocket is a web technology providing communications channels over a single TCP connection. The WebSocket Protocol is an independent TCP-based protocol. Its only relationship to HTTP is that its handshake is interpreted by HTTP servers as an Upgrade request.

Few years back HTML5 opened WebSocket technology, now we can use WebSockets using javascript. A secure version of the WebSocket protocol is implemented in Firefox version 6, Google Chrome version 14, Opera version 12.10 and Internet Explorer version 10.

WebSocket connection has following steps

1. Client try to connect to WebSocket server
2. Server recognize client
3. If client is not registered with server then add client (this is known as handshaking process which is based on headers transmission)
4. Send and receive data
5. Close connection

WebSocket protocol handshake

This very important step to establish a WebSocket connection, client sends a WebSocket handshake request, and the server sends a WebSocket handshake response, as shown in the following example:

Request headers from client

GET /mychat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat
Sec-WebSocket-Version: 13
Origin: http://example.com
Response headers from server

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat

The client sends a Sec-WebSocket-Key which is base64 encoded. To form a response, the magic string 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 is appended to this (undecoded) key. The resulting string is then hashed with SHA-1, then base64 encoded. Finally, the resulting reply occurs in the header Sec-WebSocket-Accept.

Set up your own PHP socket server

<?php	/*	>php -q server.php	*/

error_reporting(E_ALL);
set_time_limit(0);
ob_implicit_flush();

$address = "127.0.0.1";
$port = "1222";
GLOBAL $clients;
GLOBAL $client_list;

// socket creation
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1);

if (!is_resource($socket))
	console("socket_create() failed: ".socket_strerror(socket_last_error()), true);

if (!socket_bind($socket, $address, $port))
	console("socket_bind() failed: ".socket_strerror(socket_last_error()), true);

if(!socket_listen($socket, 20))
	console("socket_listen() failed: ".socket_strerror(socket_last_error()), true);

console("Server started on $address : $port\n\n");
$master = $socket;
$sockets = array($socket);

while(true){
	$changed = $sockets;
	foreach($changed as $socket){

		if($socket==$master){

			// new client will enter in this case and connect with server

			socket_select($changed,$write=NULL,$except=NULL,NULL);
			console("Master Socket Changed.\n\n");
			$client=socket_accept($master);
			if($client<0)
				{ console("socket_accept() failed\n\n"); continue; }
			else
				{ console("Connecting socket.\n\n"); fnConnectacceptedSocket($socket,$client); $master=null; }
		}else{

			// clients who are connected with server will enter into this case
			// first client will handshake with server and then exchange data with server
$client = getClientBySocket($socket);
			if($client) {

				if ($clients[$socket]["handshake"] == false){
					$bytes = @socket_recv($client, $data, 2048, MSG_DONTWAIT);
					if ((int)$bytes == 0)
						continue;

					console("Handshaking headers from client:".$data);
					if (handshake($client, $data, $socket))
						$clients[$socket]["handshake"] = true;
				} else if ($clients[$socket]["handshake"] == true){
					$bytes = @socket_recv($client, $data, 2048, MSG_DONTWAIT);
					if ($data != ""){
						$decoded_data = unmask($data);
						socket_write($client, encode("You have entered: ".$decoded_data));
						console("Data from client:".$decoded_data);
						socket_close($socket);
					}
				}
}
		}
	}
}

# Close the master sockets
socket_close($socket);

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);
	}

	$text = '';
for ($i = 0; $i < strlen($data); ++$i) {
		$text .= $data[$i] ^ $masks[$i%4];
	}
	return $text;
}

function encode($text)
{
	// 0x1 text frame (FIN + opcode)
	$b1 = 0x80 | (0x1 & 0x0f);
	$length = strlen($text);

	if($length <= 125) 		$header = pack('CC', $b1, $length); 	elseif($length > 125 && $length < 65536) 		$header = pack('CCS', $b1, 126, $length); 	elseif($length >= 65536)
		$header = pack('CCN', $b1, 127, $length);

	return $header.$text;
}

function fnConnectacceptedSocket($socket,$client) {

	GLOBAL $clients;
	GLOBAL $client_list;
$clients[$socket]["id"] = uniqid();
	$clients[$socket]["socket"] = $socket;
	$clients[$socket]["handshake"] = false;
	console("Accepted client \n\n");

	$client_list[$socket] = $client;
}

function getClientBySocket($socket) {
	GLOBAL $client_list;
	return $client_list[$socket];
}

function handshake($client, $headers, $socket) {

	if(preg_match("/Sec-WebSocket-Version: (.*)\r\n/", $headers, $match))
		$version = $match[1];
	else {
		console("The client doesn't support WebSocket");
		return false;
	}

	if($version == 13) {
		// Extract header variables
if(preg_match("/GET (.*) HTTP/", $headers, $match))
			$root = $match[1];
		if(preg_match("/Host: (.*)\r\n/", $headers, $match))
			$host = $match[1];
		if(preg_match("/Origin: (.*)\r\n/", $headers, $match))
			$origin = $match[1];
		if(preg_match("/Sec-WebSocket-Key: (.*)\r\n/", $headers, $match))
			$key = $match[1];

		$acceptKey = $key.'258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
		$acceptKey = base64_encode(sha1($acceptKey, true));

		$upgrade = "HTTP/1.1 101 Switching Protocols\r\n".
				   "Upgrade: websocket\r\n".
				   "Connection: Upgrade\r\n".
				   "Sec-WebSocket-Accept: $acceptKey".
				   "\r\n\r\n";

		socket_write($client, $upgrade);
		return true;
	}
	else {
		console("WebSocket version 13 required (the client supports version {$version})");
		return false;
}
}

function console($text){
	$File = "log.txt"; 
	$Handle = fopen($File, 'a');
	fwrite($Handle, $text); 
	fclose($Handle);
}

?>

Copy above code and create PHP file socket_server.php

Test socket server with HTML5 client

<html>
<head>
<title>WebSocket</title>

<style>
 html,body{font:normal 0.9em arial,helvetica;}
 #log {width:440px; height:200px; border:1px solid #7F9DB9; overflow:auto;}
 #msg {width:330px;}
</style>

<script>
var socket;

function init(){
	var host = "ws://127.0.0.1:1222";
  try{
    socket = new WebSocket(host);
    log('WebSocket - status '+socket.readyState);

    socket.onopen    = function(msg){ log("Welcome - status "+this.readyState); };
    socket.onmessage = function(msg){ log("Received: "+msg.data); };
    socket.onclose   = function(msg){ log("Disconnected - status "+this.readyState); };
  }
  catch(ex){ log(ex); }
  $("msg").focus();
}

function send(){
  var txt,msg;
  txt = $("msg");
  msg = txt.value;
  if(!msg){ alert("Message can not be empty"); return; }
  txt.value="";
  txt.focus();
  try{ socket.send(msg); log('Sent: '+msg); } catch(ex){ log(ex); }
}
function quit(){
  log("Goodbye!");
  socket.close();
  socket=null;
}

// Utilities
function $(id){ return document.getElementById(id); }
function log(msg){ $("log").innerHTML+="\n"+msg; }
function onkey(event){ if(event.keyCode==13){ send(); } }
</script>

</head>
<body onload="init()">
<h3>WebSocket v2.00</h3>
 <div id="log"></div>
 <input id="msg" type="textbox" onkeypress="onkey(event)"/>
 <button onclick="send()">Send</button>
 <button onclick="quit()">Quit</button>
 <div>Commands: hello, hi, name, age, date, time, thanks, bye</div>
</body>
</html>

Copy above code and create HTML file, socket_client.html. After that execute following commands.

nohup php -q socket_server.php &

Above command will start your socket server. Now open HTML file in browser and it will connect to socket server.

You are all set to use you PHP socket server.