web-dev-qa-db-fra.com

Comment créer un serveur websockets dans PHP

Existe-t-il des tutoriels ou des guides montrant comment écrire moi-même un simple serveur websockets en PHP? J'ai essayé de le rechercher sur Google mais je n'en ai pas trouvé beaucoup. J'ai trouvé phpwebsockets mais il est obsolète et ne supporte pas le protocole le plus récent. J'ai essayé de le mettre à jour moi-même mais cela ne semble pas fonctionner. 

#!/php -q
<?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) 
  list($resource,$Host,$u,$c,$key,$protocol,$version,$Origin,$data) = getheaders($buffer);
  console("Handshaking...");

    $acceptkey = base64_encode(sha1($key . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11",true));
  $upgrade  = "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: $acceptkey\r\n";

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

function getheaders($req){
    $r=$h=$u=$c=$key=$protocol=$version=$o=$data=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("/Upgrade: (.*)\r\n/",$req,$match)){ $u=$match[1]; }
    if(preg_match("/Connection: (.*)\r\n/",$req,$match)){ $c=$match[1]; }
    if(preg_match("/Sec-WebSocket-Key: (.*)\r\n/",$req,$match)){ $key=$match[1]; }
    if(preg_match("/Sec-WebSocket-Protocol: (.*)\r\n/",$req,$match)){ $protocol=$match[1]; }
    if(preg_match("/Sec-WebSocket-Version: (.*)\r\n/",$req,$match)){ $version=$match[1]; }
    if(preg_match("/Origin: (.*)\r\n/",$req,$match)){ $o=$match[1]; }
    if(preg_match("/\r\n(.*?)\$/",$req,$match)){ $data=$match[1]; }
    return array($r,$h,$u,$c,$key,$protocol,$version,$o,$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;
}

?>

et le client: 

var connection = new WebSocket('ws://localhost:12345');
connection.onopen = function () {
  connection.send('Ping'); // Send the message 'Ping' to the server
};

// Log errors
connection.onerror = function (error) {
  console.log('WebSocket Error ' + error);
};

// Log messages from the server
connection.onmessage = function (e) {
  console.log('Server: ' + e.data);
};

Si quelque chose ne va pas dans mon code, pouvez-vous m'aider à le réparer? Concole dans Firefox dit Firefox can't establish a connection to the server at ws://localhost:12345/.

MODIFIER
Comme cette question suscite beaucoup d’intérêt, j’ai décidé de vous présenter ce que j’ai finalement trouvé. Voici mon code complet.

67
Dharman

J'étais dans le même bateau que vous récemment et voici ce que j'ai fait:

1) J'ai utilisé le code phpwebsockets comme référence pour structurer le code côté serveur. (Vous semblez déjà le faire et, comme vous l'avez indiqué, le code ne fonctionne pas pour diverses raisons.)

2) J'ai utilisé PHP.net pour lire les détails de chaque fonction de socket utilisée dans le code phpwebsockets. Ce faisant, j'ai enfin pu comprendre le fonctionnement conceptuel de l'ensemble du système. C'était un assez gros obstacle.

3) J'ai lu le brouillon WebSocket actuel (veuillez faire une recherche sur le Web, je ne peux pas poster plus de deux liens par message). J'ai dû lire cet ouvrage plusieurs fois avant qu'il ne commence enfin à faire son chemin. Vous devrez probablement revenir à ce document encore et encore tout au long du processus, car il s'agit de la seule ressource définitive avec des informations correctes et à jour. informations sur l'API WebSocket.

4) J'ai codé la procédure de négociation appropriée en me basant sur les instructions du brouillon au point 3. Ce n'était pas trop grave.

5) Après la poignée de main, je recevais sans cesse une série de textes incohérents envoyés par les clients au serveur et je ne comprenais pas pourquoi avant de comprendre que les données étaient codées et devaient être non masquées. Le lien suivant m'a beaucoup aidé ici: http://srchea.com/blog/2011/12/build-a-rreal-time-application-using-html5-websockets/

Veuillez noter que le code disponible sur ce lien présente un certain nombre de problèmes et qu'il ne fonctionnera pas correctement sans autre modification.

6) Je suis ensuite tombé sur le fil SO suivant, qui explique clairement comment coder et décoder correctement les messages échangés: Comment puis-je envoyer et recevoir des messages WebSocket côté serveur?

Ce lien était vraiment utile. Je recommande de le consulter tout en consultant le brouillon WebSocket. Cela aidera à donner plus de sens à ce que dit le projet.

7) J'avais presque fini à ce stade, mais je rencontrais des problèmes avec une application WebRTC que je réalisais avec WebSocket. J'ai donc fini par poser ma propre question sur les SO, que j'ai finalement résolue. Pour référencer la question et la réponse, veuillez rechercher sur le Web "SO Quelles sont ces données à la fin des informations sur les candidats au WebRTC?" (sans les guillemets).

8) À ce stade, j'ai à peu près tout fait fonctionner. Je devais juste ajouter une logique supplémentaire pour gérer la fermeture des connexions, et j'avais terminé.

Ce processus m'a pris environ deux semaines au total. La bonne nouvelle est que je comprends très bien WebSocket et que j'ai pu créer à partir de rien mes propres scripts client et serveur qui fonctionnent très bien… .. Espérons que l'aboutissement de toutes ces informations vous donnera suffisamment de conseils et d'informations pour coder vos propres WebSocket PHP script . Bonne chance!

Edit: cette modification a lieu quelques années après ma réponse initiale et, bien que ma solution fonctionne toujours, elle n'est pas vraiment prête pour le partage. Heureusement, quelqu'un d'autre sur GitHub a un code presque identique au mien (mais beaucoup plus propre), je vous recommande donc d'utiliser le code suivant pour une solution WebSocket PHP opérationnelle:
https://github.com/ghedipunk/PHP-Websockets/blob/master/websockets.php

Edit # 2: Bien que j'aime toujours utiliser PHP pour beaucoup de choses liées au serveur, je dois admettre que je me suis beaucoup habitué à Node.js récemment, et le principal La raison en est qu’il est mieux conçu dès le départ pour gérer WebSocket que PHP (ou tout autre langage côté serveur). En tant que tel, j'ai récemment découvert qu'il était beaucoup plus facile de configurer Apache/PHP et Node.js sur votre serveur et d'utiliser Node.js pour exécuter le serveur WebSocket et Apache/PHP pour tout le reste. Et dans le cas où vous êtes sur un environnement d'hébergement partagé dans lequel vous ne pouvez pas installer/utiliser Node.js pour WebSocket, vous pouvez utiliser un service gratuit tel que Heroku pour configurer un serveur WebSocket Node.js et effectuer une connexion entre domaines. les demandes de votre serveur. Assurez-vous simplement que si vous procédez ainsi, vous configurerez votre serveur WebSocket de manière à pouvoir gérer les requêtes croisées.

84
HartleySan

Autant que je sache Ratchet est la meilleure solution WebSocket PHP actuellement disponible. Et puisque c'est open source , vous pouvez voir comment l'auteur a construit cette solution WebSocket en utilisant PHP.

19
leggetter

Pourquoi ne pas utiliser sockets http://uk1.php.net/manual/en/book.sockets.php ? Il est bien documenté (pas seulement dans le contexte PHP) et contient de bons exemples http://uk1.php.net/manual/fr/sockets.examples.php

6
Lukasz Kujawa

J'ai été à votre place pendant un certain temps et j'ai finalement fini par utiliser node.js, car il permet de réaliser des solutions hybrides comme avoir un serveur Web et un serveur de socket. Ainsi, le backend php peut soumettre des requêtes via http au serveur Web du nœud, puis le diffuser avec websocket. Très efficace chemin à parcourir.

0
M.Z.

Nécessité de convertir la clé hexadécimale en décimale avant l’encodage base64, puis de l’envoyer en échange.

$hashedKey = sha1($key. "258EAFA5-E914-47DA-95CA-C5AB0DC85B11",true);

$rawToken = "";
    for ($i = 0; $i < 20; $i++) {
      $rawToken .= chr(hexdec(substr($hashedKey,$i*2, 2)));
    }
$handshakeToken = base64_encode($rawToken) . "\r\n";

$handshakeResponse = "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: $handshakeToken\r\n";

Faites-moi savoir si cela aide. 

0
user2288650