Comment créer un serveur Websockets en PHP

88

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 chercher sur google mais je n'en ai pas trouvé beaucoup. J'ai trouvé phpwebsockets mais il est maintenant obsolète et ne prend pas en charge 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);
};

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

EDIT
Comme il y a beaucoup d'intérêt pour cette question, j'ai décidé de vous fournir ce que j'ai finalement proposé. Voici mon code complet.

Dharman
la source
1
Cette page liste qu'ils avaient aussi des problèmes avec les phpwebsockets actuelle et comprend les changements qu'ils ont apportés dans le code des exemples: net.tutsplus.com/tutorials/javascript-ajax/...
scrappedcola
1
Une bibliothèque utile qui peut être utilisée pour les applications WebSockets. inclure le côté client et PHP. techzonemind.com
Jithin Jose
1
Je pense qu'il vaut mieux l'implémenter en C ++.
Michael Chourdakis

Réponses:

114

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 savoir comment structurer le code côté serveur. (Vous semblez déjà le faire, et comme vous l'avez noté, le code ne fonctionne pas réellement 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. En faisant cela, j'ai enfin pu comprendre comment tout le système fonctionne conceptuellement. C'était un très gros obstacle.

  3. J'ai lu le brouillon de WebSocket . J'ai dû lire ce document plusieurs fois avant qu'il ne commence à s'imposer. Vous devrez probablement revenir sur ce document encore et encore tout au long du processus, car il s'agit de la seule ressource définitive avec des ressources correctes et à jour informations sur l'API WebSocket.

  4. J'ai codé la procédure de prise de contact appropriée en fonction des instructions du brouillon du n ° 3. Ce n'était pas trop mal.

  5. J'ai continué à recevoir un tas de texte déformé envoyé des clients au serveur après la poignée de main et je ne pouvais pas comprendre pourquoi jusqu'à ce que je réalise que les données sont encodées et doivent être démasquées. Le lien suivant m'a beaucoup aidé ici: (lien d' origine cassé) Copie archivée .

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

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

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

  7. J'avais presque terminé à ce stade, mais j'avais quelques problèmes avec une application WebRTC que je faisais en utilisant WebSocket, alors j'ai fini par poser ma propre question sur SO, que j'ai finalement résolue: Quelles sont ces données à la fin des informations sur les candidats WebRTC?

  8. À ce stade, j'avais à peu près tout fonctionné. 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 maintenant et que j'ai pu créer mes propres scripts client et serveur à partir de zéro qui fonctionnent très bien. Espérons que le point culminant de toutes ces informations vous donnera suffisamment de conseils et d'informations pour coder votre propre script PHP WebSocket.

Bonne chance!


Edit : Cette modification est quelques années après ma réponse initiale, et bien que j'aie encore une solution de travail, 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 recommande donc d'utiliser le code suivant pour une solution PHP WebSocket fonctionnelle:
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 vraiment beaucoup familiarisé avec Node.js récemment, et la raison principale est qu'il est mieux conçu à partir du mis à la terre 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 comme Herokupour configurer un serveur Node.js WebSocket et lui envoyer des requêtes interdomaines à partir de votre serveur. Assurez-vous simplement que vous le faites pour configurer votre serveur WebSocket afin qu'il puisse gérer les demandes d'origine croisée.

HartleySan
la source
Thx, je vais essayer de le faire à votre façon. Comment évaluez-vous les performances de ce serveur PHP?
Dharman
@Dharman, c'est difficile à dire car je n'ai pu l'exécuter que sur mon hôte local. Bien sûr, cela fonctionne bien là-bas, mais sur un vrai serveur avec une charge lourde, je ne sais pas. J'imagine que cela fonctionnerait plutôt bien, car il n'y a pas de ballonnement dans mon code.
HartleySan
1
Je ne l'ai pas pour le moment, mais dans un proche avenir, je prévois d'écrire un tutoriel sur l'ensemble du processus avec tout le code. Une fois que je ferai cela, je publierai le lien.
HartleySan
1
@HartleySan: Bonjour! Je serais très intéressé par votre code. Pourriez-vous le mettre en ligne ou me l'envoyer personnellement?
avant le
Oui, il sera bientôt en ligne. Désolé pour tous ceux qui l'ont demandé. J'ai été tellement occupé récemment. J'y reviendrai bientôt.
HartleySan le
26

Pour autant que je sache, Ratchet est la meilleure solution PHP WebSocket disponible pour le moment. Et comme il est open source, vous pouvez voir comment l'auteur a construit cette solution WebSocket en utilisant PHP.

leggetter
la source
2
J'ajoute ici ma solution qui utilise Ratchet et Silex: github.com/eole-io/sandstone Je ne sais pas si vous la trouverez utile
Alcalyn
8

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

Lukasz Kujawa
la source
2
Oui, vous pouvez avoir une connexion continue entre des sockets PHP simples et une page Web, je l'ai testé plusieurs fois.
WiMantis
@WiMantis: Bonjour! Pourriez-vous mettre un exemple de code qui fait cela en ligne, ou éventuellement me l'envoyer personnellement?
avant le
Pouvez-vous l'utiliser avec une connexion HTTP classique? Je construisais un framework DDD et j'aimerais créer comme une classe wrapper en plus de cela et fournir des fonctionnalités de socket, de préférence en vanilla php en utilisant l'extension de base
1

Besoin de convertir la clé hexadécimale en déc avant l'encodage base64, puis de l'envoyer pour une prise de contact

$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 vous aide.

user2288650
la source
1

J'étais à votre place pendant un moment et j'ai finalement fini par utiliser node.js, car il peut faire des solutions hybrides comme avoir un serveur Web et un serveur socket en un. Ainsi, le backend php peut soumettre des requêtes via http au serveur Web du nœud, puis les diffuser avec websocket. Une manière très efficace d'aller.

MZ
la source
nous devons donc utiliser un client http pour faire une requête http de php au serveur de nœud, n'est-ce pas?
Kiren Siva
Kiren Siva, c'est exact. Curl ou similaire, puis node diffuse un message via websocket
MZ
-2
<?php

// server.php

$server = stream_socket_server("tcp://127.0.0.1:8001", $errno, $errorMessage);

if($server == false) {
    throw new Exception("Could not bind to socket: $errorMessage");

}

for(;;) {
    $client = @stream_socket_accept($server);

    if($client) {
        stream_copy_to_stream($client, $client);
        fclose($client);
    }
}

à partir d'un seul terminal: php server.php

depuis une autre exécution de terminal: echo "bonjour monde" | nc 127.0.0.1 8002

Dipak Yadav
la source
1
Qu'est-ce que cela veut dire?
Dharman
8
Ce sont des sockets, pas des WebSockets. Il y a une grande différence.
Chris
@techexpander, selon la question, vous devez expliquer à partir de zéro au lieu d'ex. qui ne fonctionne pas.
Jaymin le