Come creare server websocket in PHP


89

Esistono tutorial o guide che mostrano come scrivere un semplice server websocket in PHP? Ho provato a cercarlo su google ma non ne ho trovati molti. Ho trovato phpwebsockets ma ora è obsoleto e non supporta il protocollo più recente. Ho provato ad aggiornarlo da solo ma non sembra funzionare.

#!/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;
}

?>

e il cliente:

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);
};

Se c'è qualcosa di sbagliato nel mio codice, puoi aiutarmi a risolverlo? Concole in Firefox diceFirefox can't establish a connection to the server at ws://localhost:12345/.

EDIT
Dato che c'è molto interesse per questa domanda, ho deciso di fornirti quello che ho finalmente trovato. Ecco il mio codice completo.


1
Questa pagina elenca che anche loro hanno avuto problemi con gli attuali phpwebsocket e include le modifiche apportate nel codice src di esempi: net.tutsplus.com/tutorials/javascript-ajax/…
scrappedcola

1
Una libreria utile che può essere utilizzata per le applicazioni WebSocket. includere sia il lato client che quello PHP. techzonemind.com/…
Jithin Jose,

1
Penso che sia meglio implementarlo in C ++.
Michael Chourdakis

Risposte:


119

Ero sulla stessa barca di te di recente, ed ecco cosa ho fatto:

  1. Ho usato il codice phpwebsockets come riferimento per come strutturare il codice lato server. (Sembra che tu lo stia già facendo e, come hai notato, il codice in realtà non funziona per una serie di motivi.)

  2. Ho usato PHP.net per leggere i dettagli su ogni funzione socket utilizzata nel codice phpwebsockets. In questo modo, sono stato finalmente in grado di capire come funziona concettualmente l'intero sistema. Questo è stato un grosso ostacolo.

  3. Ho letto l'attuale bozza di WebSocket . Ho dovuto leggere questa cosa un sacco di volte prima che finalmente iniziasse ad affondare. Probabilmente dovrai tornare a questo documento ancora e ancora durante tutto il processo, poiché è l'unica risorsa definitiva con corretti, aggiornati informazioni sull'API WebSocket.

  4. Ho codificato la corretta procedura di stretta di mano in base alle istruzioni nella bozza in # 3. Non era poi così male.

  5. Continuavo a ricevere un mucchio di testo confuso inviato dai client al server dopo l'handshake e non riuscivo a capire il motivo finché non mi sono reso conto che i dati sono codificati e devono essere smascherati. Il seguente link mi ha aiutato molto qui: (originale collegamento interrotto) Copia archiviata .

    Si noti che il codice disponibile a questo collegamento presenta una serie di problemi e non funzionerà correttamente senza ulteriori modifiche.

  6. Mi sono quindi imbattuto nel seguente thread SO, che spiega chiaramente come codificare e decodificare correttamente i messaggi inviati avanti e indietro: Come posso inviare e ricevere messaggi WebSocket sul lato server?

    Questo collegamento è stato davvero utile. Consiglio di consultarlo guardando la bozza di WebSocket. Aiuterà a dare più senso a ciò che dice la bozza.

  7. Avevo quasi finito a questo punto, ma ho avuto alcuni problemi con un'app WebRTC che stavo creando utilizzando WebSocket, quindi ho finito per porre la mia domanda su SO, che alla fine ho risolto: quali sono questi dati alla fine delle informazioni sul candidato WebRTC?

  8. A questo punto, ho praticamente funzionato tutto. Dovevo solo aggiungere una logica aggiuntiva per gestire la chiusura delle connessioni e ho finito.

Questo processo mi ha richiesto circa due settimane in totale. La buona notizia è che ora capisco molto bene WebSocket e sono stato in grado di creare da zero i miei script client e server che funzionano alla grande. Si spera che il culmine di tutte queste informazioni ti fornirà indicazioni e informazioni sufficienti per codificare il tuo script PHP WebSocket.

In bocca al lupo!


Modifica : questa modifica è un paio d'anni dopo la mia risposta originale e, sebbene disponga ancora di una soluzione funzionante, non è davvero pronta per la condivisione. Fortunatamente, qualcun altro su GitHub ha un codice quasi identico al mio (ma molto più pulito), quindi consiglio di utilizzare il seguente codice per una soluzione PHP WebSocket funzionante:
https://github.com/ghedipunk/PHP-Websockets/blob/master/ websockets.php


Modifica n. 2 : sebbene mi piaccia ancora usare PHP per molte cose relative al lato server, devo ammettere che recentemente mi sono appassionato molto a Node.js e il motivo principale è perché è progettato meglio dal ground up per gestire WebSocket rispetto a PHP (o qualsiasi altro linguaggio lato server). Come tale, ho scoperto di recente che è molto più semplice configurare Apache / PHP e Node.js sul tuo server e utilizzare Node.js per eseguire il server WebSocket e Apache / PHP per tutto il resto. E nel caso in cui ti trovi in ​​un ambiente di hosting condiviso in cui non puoi installare / utilizzare Node.js per WebSocket, puoi utilizzare un servizio gratuito come Herokuper configurare un server WebSocket Node.js e inviargli richieste interdominio dal tuo server. Assicurati solo di farlo per impostare il tuo server WebSocket in modo che sia in grado di gestire le richieste cross-origin.


Grazie, proverò a farlo a modo tuo. Come valuti le prestazioni di questo server PHP?
Dharman

@Dharman, è difficile da dire perché sono stato in grado di eseguirlo solo sul mio localhost. Ovviamente funziona bene lì, ma su un server reale con un carico pesante, non lo so. Immagino che funzionerebbe abbastanza bene, dato che non c'è gonfiore nel mio codice.
HartleySan

1
Al momento non ce l'ho, ma nel prossimo futuro ho intenzione di scrivere un tutorial sull'intero processo con tutto il codice. Una volta fatto ciò, posterò il link.
HartleySan

1
@HartleySan: Ciao! Sarei molto interessato a guardare il tuo codice. Potresti metterlo online o inviarmelo personalmente?
Trhrin

Sì, presto sarà online. Scusa a tutti quelli che l'hanno chiesto. Sono stato così impegnato di recente. Ci metterò presto.
HartleySan


8

Perché non usare i socket http://uk1.php.net/manual/en/book.sockets.php ? È ben documentato (non solo nel contesto PHP) e ha buoni esempi http://uk1.php.net/manual/en/sockets.examples.php


2
Sì, puoi avere una connessione continua tra semplici socket PHP e una pagina web, l'ho testato più volte.
WiMantis

@WiMantis: Ciao! Potresti mettere un esempio di codice che lo faccia online o facoltativamente inviarmelo personalmente?
Trhrin

Puoi usarlo insieme a una normale connessione HTTP? Stavo costruendo un framework DDD e vorrei creare come una classe wrapper in cima a questo e fornire funzionalità socket, preferibilmente in php vanilla utilizzando l'estensione core

2

Sono stato nei tuoi panni per un po 'e alla fine ho finito per usare node.js, perché può fare soluzioni ibride come avere server web e socket in uno. Quindi il backend php può inviare richieste tramite http al server web del nodo e quindi trasmetterle con websocket. Modo molto efficace per andare.


quindi dobbiamo usare un client http per fare richieste http da php al server del nodo giusto?
Kiren Siva

Kiren Siva, esatto. Curl o simile, quindi il nodo trasmette un messaggio tramite websocket
MZ

1

È necessario convertire la chiave da hex a dec prima di base64_encoding e quindi inviarla per l'handshake.

$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";

Fammi sapere se questo aiuta.


-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);
    }
}

da un terminale eseguire: php server.php

da un altro terminale eseguire: echo "ciao woerld" | nc 127.0.0.1 8002


1
Cosa vuol dire essere?
Dharman

8
Questo è socket, non WebSocket. C'è una grande differenza.
Chris

@techexpander, come da domanda devi spiegare da zero invece di ex. che non funziona.
Jaymin
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.