1

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!

Mark Carols
  • 403
  • 4
  • 20

1 Answers1

1

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.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • Socket.io server is only available in Javascript (Node.js) and I'm not familiar with that. The only server side language I know is PHP, so wouldn't it be possible to just replace the client-side of the connection instead? Starscream, for example, says it IS a websocket client (unlike Socket.io appearantly), so can't I use that instead of Socket.io? – Mark Carols Jul 21 '17 at 01:49
  • @MarkCarols - socket.io server is available in many other environments (Java, PHP, C#, etc...). For PHP see [this](https://www.google.com/search?q=socket.io+php&rlz=1C1CHFX_enUS515US515&oq=socket.io+php&aqs=chrome..69i57j0l5.1720j0j7&sourceid=chrome&ie=UTF-8). Browsers have built-in support for webSocket so you could just use a plain webSocket from the browser. You just wouldn't have some of the socket.io features as listed in this answer: [Moving from socket.io to raw websockets?](https://stackoverflow.com/questions/38546496/moving-from-socket-io-to-raw-websockets/38546537#38546537). – jfriend00 Jul 21 '17 at 02:12
  • Thanks! I'll check out some of those links and see which one's the easiest to implement. – Mark Carols Jul 21 '17 at 02:16