Search code examples
ioswebsocketsocket.iophpwebsocket

Socket.io handshake with PHP websocket?


I'm using a PHP web socket on my server side. I'm currently only testing the connection between the client and the server, so I haven't configured the socket yet to respond to specific events. This is what the basic template looks like:

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

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

$master  = WebSocket("example.com",8080);
$sockets = array($master);
$users   = array();
$debug   = false;

while(true){
  $changed = $sockets;
  socket_select($changed,$write=NULL,$except=NULL,NULL);
  foreach($changed as $socket){
    if($socket==$master){
      $client=socket_accept($master);
      if($client<0){ console("socket_accept() failed"); continue; }
      else{ connect($client); }
    }
    else{
      $bytes = @socket_recv($socket,$buffer,2048,0);
      if($bytes==0){ disconnect($socket); }
      else{
        $user = getuserbysocket($socket);
        if(!$user->handshake){ dohandshake($user,$buffer); }
        else{ process($user,$buffer); }
      }
    }
  }
}

//---------------------------------------------------------------
function process($user,$msg){
  $action = unwrap($msg);
  say("< ".$action);
  switch($action){
case "hello" : send($user->socket,"hello human");                       break;
case "hi"    : send($user->socket,"zup human");                         break;
case "name"  : send($user->socket,"my name is Multivac, silly I know"); break;
case "age"   : send($user->socket,"I am older than time itself");       break;
case "date"  : send($user->socket,"today is ".date("Y.m.d"));           break;
case "time"  : send($user->socket,"server time is ".date("H:i:s"));     break;
case "thanks": send($user->socket,"you're welcome");                    break;
case "bye"   : send($user->socket,"bye");                               break;
default      : send($user->socket,$action." not understood");           break;
  }
}

function send($client,$msg){
  say("> ".$msg);
  $msg = wrap($msg);
  socket_write($client,$msg,strlen($msg));
}

function WebSocket($address,$port){
  $master=socket_create(AF_INET, SOCK_STREAM, SOL_TCP)     or die("socket_create() failed");
  socket_set_option($master, SOL_SOCKET, SO_REUSEADDR, 1)  or die("socket_option() failed");
  socket_bind($master, $address, $port)                    or die("socket_bind() failed");
  socket_listen($master,20)                                or die("socket_listen() failed");
  echo "Server Started : ".date('Y-m-d H:i:s')."\n";
  echo "Master socket  : ".$master."\n";
  echo "Listening on   : ".$address." port ".$port."\n\n";
  return $master;
}

function connect($socket){
  global $sockets,$users;
  $user = new User();
  $user->id = uniqid();
  $user->socket = $socket;
  array_push($users,$user);
  array_push($sockets,$socket);
  console($socket." CONNECTED!");
}

function disconnect($socket){
  global $sockets,$users;
  $found=null;
  $n=count($users);
  for($i=0;$i<$n;$i++){
    if($users[$i]->socket==$socket){ $found=$i; break; }
  }
  if(!is_null($found)){ array_splice($users,$found,1); }
  $index = array_search($socket,$sockets);
  socket_close($socket);
  console($socket." DISCONNECTED!");
  if($index>=0){ array_splice($sockets,$index,1); }
}

function dohandshake($user,$buffer){
  console("\nRequesting handshake...");
  console($buffer);
  list($resource,$host,$origin,$strkey1,$strkey2,$data) = getheaders($buffer);
  console("Handshaking...");

  $pattern = '/[^\d]*/';
  $replacement = '';
  $numkey1 = preg_replace($pattern, $replacement, $strkey1);
  $numkey2 = preg_replace($pattern, $replacement, $strkey2);

  $pattern = '/[^ ]*/';
  $replacement = '';
  $spaces1 = strlen(preg_replace($pattern, $replacement, $strkey1));
  $spaces2 = strlen(preg_replace($pattern, $replacement, $strkey2));

  if ($spaces1 == 0 || $spaces2 == 0 || $numkey1 % $spaces1 != 0 || $numkey2 % $spaces2 != 0) {
    socket_close($user->socket);
    console('failed');
    return false;
  }

  $ctx = hash_init('md5');
  hash_update($ctx, pack("N", $numkey1/$spaces1));
  hash_update($ctx, pack("N", $numkey2/$spaces2));
  hash_update($ctx, $data);
  $hash_data = hash_final($ctx,true);

  $upgrade  = "HTTP/1.1 101 WebSocket Protocol Handshake\r\n" .
              "Upgrade: WebSocket\r\n" .
              "Connection: Upgrade\r\n" .
              "Sec-WebSocket-Origin: " . $origin . "\r\n" .
              "Sec-WebSocket-Location: ws://" . $host . $resource . "\r\n" .
              "\r\n" .
              $hash_data;

  socket_write($user->socket,$upgrade.chr(0),strlen($upgrade.chr(0)));
  $user->handshake=true;
  console($upgrade);
  console("Done handshaking...");
  return true;
}

function getheaders($req){
  $r=$h=$o=null;
  if(preg_match("/GET (.*) HTTP/"   ,$req,$match)){ $r=$match[1]; }
  if(preg_match("/Host: (.*)\r\n/"  ,$req,$match)){ $h=$match[1]; }
  if(preg_match("/Origin: (.*)\r\n/",$req,$match)){ $o=$match[1]; }
  if(preg_match("/Sec-WebSocket-Key2: (.*)\r\n/",$req,$match)){ $key2=$match[1]; }
  if(preg_match("/Sec-WebSocket-Key1: (.*)\r\n/",$req,$match)){ $key1=$match[1]; }
  if(preg_match("/\r\n(.*?)\$/",$req,$match)){ $data=$match[1]; }
  return array($r,$h,$o,$key1,$key2,$data);
}

function getuserbysocket($socket){
  global $users;
  $found=null;
  foreach($users as $user){
    if($user->socket==$socket){ $found=$user; break; }
  }
  return $found;
}

function     say($msg=""){ echo $msg."\n"; }
function    wrap($msg=""){ return chr(0).$msg.chr(255); }
function  unwrap($msg=""){ return substr($msg,1,strlen($msg)-2); }
function console($msg=""){ global $debug; if($debug){ echo $msg."\n"; } }

class User{
  var $id;
  var $socket;
  var $handshake;
}

?>

And I want to connect to this socket from my Swift client with Socket.io:

let io:SocketIOClient = SocketIOClient(socketURL: URL(string: "example.com:8080/server.php")!, config: [.log(true), .compress])

override func viewDidLoad() {
    super.viewDidLoad()

    self.io.on(clientEvent: .connect) { (data:[Any], ack:SocketAckEmitter) in
        NSLog("Socket connected!")
    }
    self.io.on(clientEvent: .disconnect) { (data:[Any], ack:SocketAckEmitter) in
        NSLog("Socket disconnected!")
    }
    self.io.connect()
}

I'm running the server on my Mac's Terminal with SSH. The PHP web socket says it's listening for connections, but it doesn't respond to the socket.io connecting. The server nor the client is giving me any errors, so I'm assuming I missed a crucial step in the connection process. I was wondering if it's possible to perform a handshake with the Socket.IO swift client when you're trying to connect to a PHP web socket. Is this a compatibility issue or am I just forgetting something? By the way, by now you may have noticed I'm very new to web socket programming (I found out about websockets less than a week ago), so please excuse me if this is a really noob question. I appreciate any help I can get. Thank you!


Solution

  • socket.io != webSocket. You can't connect a socket.io client to a webSocket server. Socket.io adds its own protocol on top of a webSocket. While socket.io uses a webSocket transport under the covers, the client will fail to connect if you only have a webSocket server.

    You must connect a webSocket client to a webSocket server. Or, connect a socket.io client to a socket.io server.

    So, in your case, if you have a socket.io client, then you need to get a socket.io server for your server environment and use that instead.