Wednesday, 29 November 2017

Simple PHP Chat using WebSocket

In this tutorial, we are going to create a simple chat application using WebSocket and PHP socket programming. The WebSocket is used to create a bridge to send or receive messages from the PHP chat server. In the web world, we generally use HTTP request methods to communicate between the client and server side. In this chat example, we use sockets to communicate with the server.
For establishing a socket connection between the client and the server, we use the WebSocket protocol (ws://) to specify the address of the PHP page where the WebSocket handshake is handled. After creating WebSocket there are callbacks to handle the events that occur between the client and the server during the chat process.

Creating WebSocket and Callback Event Handlers

The following script is used to create WebSocket in client side and define callback handlers to handle the different chat events. These handlers give acknowledgments about the connection state, chat messages and the errors if any. The chat message is encoded in JSON format and sent to the server on submit.
<script>  
 function showMessage(messageHTML) {
  $('#chat-box').append(messageHTML);
 }

 $(document).ready(function(){
  var websocket = new WebSocket("ws://localhost:8090/demo/php-socket.php"); 
  websocket.onopen = function(event) { 
   showMessage("<div class='chat-connection-ack'>Connection is established!</div>");  
  }
  websocket.onmessage = function(event) {
   var Data = JSON.parse(event.data);
   showMessage("<div class='"+Data.message_type+"'>"+Data.message+"</div>");
   $('#chat-message').val('');
  };
  
  websocket.onerror = function(event){
   showMessage("<div class='error'>Problem due to some Error</div>");
  };
  websocket.onclose = function(event){
   showMessage("<div class='chat-connection-ack'>Connection Closed</div>");
  }; 
  
  $('#frmChat').on("submit",function(event){
   event.preventDefault();
   $('#chat-user').attr("type","hidden");  
   var messageJSON = {
    chat_user: $('#chat-user').val(),
    chat_message: $('#chat-message').val()
   };
   websocket.send(JSON.stringify(messageJSON));
  });
 });
</script>

PHP Socket Programming for the Chat Application

This PHP code checks for the new socket connection request. If any of a new connection request is found, then it will accept and perform the handshake with the new socket resource. Then, it sends an acknowledgment to the client about the connectivity by sealing the encoded acknowledgment message.
It receives the socket data sent via the existing connections and unseals and decodes it to bundle the received data and send it to the chat client. The handshake, seal, unseal, send functionalities are handled by using the ChatHandler class.
<?php
define('HOST_NAME',"localhost"); 
define('PORT',"8090");
$null = NULL;

require_once("class.chathandler.php");
$chatHandler = new ChatHandler();

$socketResource = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($socketResource, SOL_SOCKET, SO_REUSEADDR, 1);
socket_bind($socketResource, 0, PORT);
socket_listen($socketResource);

$clientSocketArray = array($socketResource);
while (true) {
 $newSocketArray = $clientSocketArray;
 socket_select($newSocketArray, $null, $null, 0, 10);
 
 if (in_array($socketResource, $newSocketArray)) {
  $newSocket = socket_accept($socketResource);
  $clientSocketArray[] = $newSocket;
  
  $header = socket_read($newSocket, 1024);
  $chatHandler->doHandshake($header, $newSocket, HOST_NAME, PORT);
  
  socket_getpeername($newSocket, $client_ip_address);
  $connectionACK = $chatHandler->newConnectionACK($client_ip_address);
  
  $chatHandler->send($connectionACK);
  
  $newSocketIndex = array_search($socketResource, $newSocketArray);
  unset($newSocketArray[$newSocketIndex]);
 }
 
 foreach ($newSocketArray as $newSocketArrayResource) { 
  while(socket_recv($newSocketArrayResource, $socketData, 1024, 0) >= 1){
   $socketMessage = $chatHandler->unseal($socketData);
   $messageObj = json_decode($socketMessage);
   
   $chat_box_message = $chatHandler->createChatBoxMessage($messageObj->chat_user, $messageObj->chat_message);
   $chatHandler->send($chat_box_message);
   break 2;
  }
  
  $socketData = @socket_read($newSocketArrayResource, 1024, PHP_NORMAL_READ);
  if ($socketData === false) { 
   socket_getpeername($newSocketArrayResource, $client_ip_address);
   $connectionACK = $chatHandler->connectionDisconnectACK($client_ip_address);
   $chatHandler->send($connectionACK);
   $newSocketIndex = array_search($newSocketArrayResource, $clientSocketArray);
   unset($clientSocketArray[$newSocketIndex]);   
  }
 }
}
socket_close($socketResource);
?>
and the chat handler class is
<?php
class ChatHandler {
 function send($message) {
  global $clientSocketArray;
  $messageLength = strlen($message);
  foreach($clientSocketArray as $clientSocket)
  {
   @socket_write($clientSocket,$message,$messageLength);
  }
  return true;
 }

 function unseal($socketData) {
  $length = ord($socketData[1]) & 127;
  if($length == 126) {
   $masks = substr($socketData, 4, 4);
   $data = substr($socketData, 8);
  }
  elseif($length == 127) {
   $masks = substr($socketData, 10, 4);
   $data = substr($socketData, 14);
  }
  else {
   $masks = substr($socketData, 2, 4);
   $data = substr($socketData, 6);
  }
  $socketData = "";
  for ($i = 0; $i < strlen($data); ++$i) {
   $socketData .= $data[$i] ^ $masks[$i%4];
  }
  return $socketData;
 }

 function seal($socketData) {
  $b1 = 0x80 | (0x1 & 0x0f);
  $length = strlen($socketData);
  
  if($length <= 125)
   $header = pack('CC', $b1, $length);
  elseif($length > 125 && $length < 65536)
   $header = pack('CCn', $b1, 126, $length);
  elseif($length >= 65536)
   $header = pack('CCNN', $b1, 127, $length);
  return $header.$socketData;
 }

 function doHandshake($received_header,$client_socket_resource, $host_name, $port) {
  $headers = array();
  $lines = preg_split("/\r\n/", $received_header);
  foreach($lines as $line)
  {
   $line = chop($line);
   if(preg_match('/\A(\S+): (.*)\z/', $line, $matches))
   {
    $headers[$matches[1]] = $matches[2];
   }
  }

  $secKey = $headers['Sec-WebSocket-Key'];
  $secAccept = base64_encode(pack('H*', sha1($secKey . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')));
  $buffer  = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" .
  "Upgrade: websocket\r\n" .
  "Connection: Upgrade\r\n" .
  "WebSocket-Origin: $host_name\r\n" .
  "WebSocket-Location: ws://$host_name:$port/demo/shout.php\r\n".
  "Sec-WebSocket-Accept:$secAccept\r\n\r\n";
  socket_write($client_socket_resource,$buffer,strlen($buffer));
 }
 
 function newConnectionACK($client_ip_address) {
  $message = 'New client ' . $client_ip_address.' joined';
  $messageArray = array('message'=>$message,'message_type'=>'chat-connection-ack');
  $ACK = $this->seal(json_encode($messageArray));
  return $ACK;
 }
 
 function connectionDisconnectACK($client_ip_address) {
  $message = 'Client ' . $client_ip_address.' disconnected';
  $messageArray = array('message'=>$message,'message_type'=>'chat-connection-ack');
  $ACK = $this->seal(json_encode($messageArray));
  return $ACK;
 }
 
 function createChatBoxMessage($chat_user,$chat_box_message) {
  $message = $chat_user . ": <div class='chat-box-message'>" . $chat_box_message . "</div>";
  $messageArray = array('message'=>$message,'message_type'=>'chat-box-html');
  $chatMessage = $this->seal(json_encode($messageArray));
  return $chatMessage;
 }
}
?>

Establish Connection using Command Line

The following image shows the command line screen to establish the connection to start chatting by using this application.

PHP Chat Output

Download

0 comments:

Post a Comment