Mise en place d'un serveur Websocket PHP sur Debian

WRInaute discret
Bonjour,

Depuis que j'ai appris l'existence des Websockets, je me suis rendu compte que l'un de mes projets majeurs n'allait plus du tout être viable car développé en AJAX, ce qui était bien trop lent. J'ai donc regardé quelques tutoriels de base sur les websockets mais malheureusement sans réel succès...

La majeure partie de ces derniers conseillent "PhpWebsocket" (http://code.google.com/p/phpwebsocket/), je suis donc allé sur le site et ai tenté d'en suivre les instructions. Là, j'apprends qu'il me faut créer deux fichiers, que je crée alors et place sur mon serveur dans le dossier /var/www/mon_site/websocket

Le premier, client.php :
Code:
<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://localhost:12345/websocket/server.php";
  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+="<br>"+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>

Le second, server.php :
Code:
<?php  /*  >php -q server.php  */

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

$master  = WebSocket("localhost",12345);
$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;
}

?>

Je change le port indiqué dans le fichier, ajoute l'exception au Linux Firewall (est-ce vraiment nécessaire, en fait ?), place les deux fichiers en FTP dans le dossier et ouvre chrome. Là, je m'aperçois que dans le fichier client.php j'ai une ligne qui contient ws://localhost:12345/websocket/server.php. Je me dis alors que je devrais sûrement la renommer mais que devrais-je y placer ? Est-ce que je devrais mettre quelque chose du genre ws://monsite.com/websocket/server.php ? Est-ce que je devrais mettre quelque chose du genre ws://88.553.(ip):12345/var/www/mon_site/websocket/server.php ? En fait, je n'y connais rien et je sais pas trop où mène ce port et comment le configurer... Dois-je toucher à qqch de la config Apache ou je ne sais quoi ?

Sinon, encore une petite question pour l'activation du serveur websocket. J'avais bien sûr préalablement lancé la commande php -f /var/www/mon_site/websocket/server.php mais que se passe-t-il si je modifie le fichier server.php, par exemple par FTP ? Dois-je relancer le serveur ? Et si je souhaite ajouter des fonctionnalités au serveur, suis-je obligé à chaque fois de modifier l'intégralité du fichier et de le relancer ?

Merci d'avance de votre grande aide :)

7804j
 
WRInaute passionné
Si tu modifies ton fichier server.php, oui tu dois relancer ton socket.
Aussi, tu dois "bind" ton websocket sur une IP (ou un hostname comme "ton site") publique, surtout pas sur localhost (sauf si tu dev).
Par contre, attends un peu, car "rien" n'est compatible pour le moment.
Pour avoir fait quelques tests, ça bourrine quand même bien le serveur :p
 
WRInaute discret
"Rien" ? Tu veux dire tout sauf IE ? Enfin c'est ce que j'ai cru comprendre :/
De toute manière, même avec AJAX mon site (une sorte de jeu par navigateur) ne serait pas compatible IE ^^'

Qu'entends-tu par bourrinage du serveur ? J'ai un dédié à 70 euros par mois chez OVH qui a quand même des bons caracs, ça risque quand même de le ralentir beaucoup ?

Pour "binder" mon websocket, je n'ai pas bien compris : si mon serveur se trouve à l'url http://www.example.com/websocket/server.php, suffit-il de remplacer cela par ws://www.example.com/websocket/server.php ? Ou dois-je spécifier le port qqpart dans l'url ou dans ma config debian ? J'ai essayé comme ceci mais cela n'a pas vraiment fonctionné :/

P.S : Ma page de test est actuellement ici, mais je reçois une erreur dans la console javascript "Unexpected response code : 200" : http://dofus2.org/ajax/client.php
Qu'est-ce que cela veut dire ?
 
WRInaute discret
Je n'y connais rien, mais cela a l'air intéressant. Sont-ce les trois des technologies utilisant ce protocole ws et permettant d'éviter les requêtes http inutiles ? Quelles sont les différences principales, etc. ?
 
WRInaute discret
Donc si j'ai bien compris, tous ces systèmes ne fonctionneraient qu'avec du javascript tournant côté serveur. Le problème, c'est que je n'ai absolument aucune expérience ni aucune idée de comment je devrais m'y prendre, si en plus je dois rendre le tout compatible aux bases de données :/ J'ai passé une heure à essayer de comprendre un peu mais je m'en sors pas très bien avec les tutos qui existent (je suis même pas parvenu à terminer l'installation...)

Je pense que ce serait bien si j'arrivais à faire fonctionner cela en php en fait, mais merci quand même pour le lien, je connaissais pas tout ça.

J'en reviens donc au même problème :
"Rien" ? Tu veux dire tout sauf IE ? Enfin c'est ce que j'ai cru comprendre :/
De toute manière, même avec AJAX mon site (une sorte de jeu par navigateur) ne serait pas compatible IE ^^'

Qu'entends-tu par bourrinage du serveur ? J'ai un dédié à 70 euros par mois chez OVH qui a quand même des bons caracs, ça risque quand même de le ralentir beaucoup ?

Pour "binder" mon websocket, je n'ai pas bien compris : si mon serveur se trouve à l'url http://www.example.com/websocket/server.php, suffit-il de remplacer cela par ws://www.example.com/websocket/server.php ? Ou dois-je spécifier le port qqpart dans l'url ou dans ma config debian ? J'ai essayé comme ceci mais cela n'a pas vraiment fonctionné :/

P.S : Ma page de test est actuellement ici, mais je reçois une erreur dans la console javascript "Unexpected response code : 200" : http://dofus2.org/ajax/client.php
Qu'est-ce que cela veut dire ?
 
WRInaute discret
Je n'ai toujours pas pu résoudre le problème, mais j'ai remarqué que php me retournait cela quand j'allais sur la page server.php avec mon navigateur :

Warning: socket_bind(): unable to bind address [98]: Address already in use in /var/www/dofus2_org/ajax/server.php on line 59 socket_bind() failed

Une idée ?
 
Discussions similaires
Haut