So erstellen Sie einen Websockets-Server in PHP

84

Gibt es Tutorials oder Anleitungen, die zeigen, wie ich mir einen einfachen Websockets-Server in PHP schreibe? Ich habe versucht, es bei Google zu suchen, aber ich habe nicht viele gefunden. Ich habe phpwebsockets gefunden, aber es ist jetzt veraltet und unterstützt nicht das neueste Protokoll. Ich habe versucht, es selbst zu aktualisieren, aber es scheint nicht zu funktionieren.

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

?>

und der Kunde:

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

Wenn in meinem Code etwas nicht stimmt, können Sie mir helfen, das Problem zu beheben? Concole in Firefox sagtFirefox can't establish a connection to the server at ws://localhost:12345/.

BEARBEITEN
Da an dieser Frage großes Interesse besteht, habe ich beschlossen, Ihnen das zu liefern, was ich mir schließlich ausgedacht habe. Hier ist mein vollständiger Code.

Dharman
quelle
1
Diese Seite zeigt , dass auch sie hatte Probleme mit den aktuellen phpwebsockets und enthält die Änderungen , die sie in den Beispielen src Code gemacht: net.tutsplus.com/tutorials/javascript-ajax/...
scrappedcola
1
Eine nützliche Bibliothek, die für WebSockets-Anwendungen verwendet werden kann. sowohl Client- als auch PHP-Seite einbeziehen. techzonemind.com/…
Jithin Jose
1
Ich denke, es ist besser, es in C ++ zu implementieren.
Michael Chourdakis

Antworten:

111

Ich war vor kurzem im selben Boot wie Sie, und hier ist, was ich getan habe:

1) Ich habe den phpwebsockets-Code als Referenz für die Strukturierung des serverseitigen Codes verwendet. (Sie scheinen dies bereits zu tun, und wie Sie bemerkt haben, funktioniert der Code aus verschiedenen Gründen nicht.)

2) Ich habe PHP.net verwendet, um die Details zu jeder im phpwebsockets-Code verwendeten Socket-Funktion zu lesen. Auf diese Weise konnte ich endlich verstehen, wie das gesamte System konzeptionell funktioniert. Dies war eine ziemlich große Hürde.

3) Ich habe den aktuellen WebSocket-Entwurf gelesen (bitte führen Sie eine Websuche durch, da ich nicht mehr als zwei Links pro Beitrag veröffentlichen kann). Ich musste dieses Ding ein paar Mal lesen, bevor es endlich einsetzte. Sie werden wahrscheinlich während des gesamten Prozesses immer wieder auf dieses Dokument zurückgreifen müssen, da es die einzige definitive Ressource mit der richtigen, aktuellen ist Informationen zur WebSocket-API.

4) Ich habe das richtige Handshake-Verfahren basierend auf den Anweisungen im Entwurf in Nr. 3 codiert. Das war nicht so schlimm.

5) Ich habe nach dem Handshake immer wieder verstümmelten Text von den Clients an den Server gesendet und konnte nicht herausfinden, warum, bis mir klar wurde, dass die Daten codiert sind und entlarvt werden müssen. Der folgende Link hat mir hier sehr geholfen: http://srchea.com/blog/2011/12/build-a-real-time-application-using-html5-websockets/

Bitte beachten Sie, dass der unter diesem Link verfügbare Code eine Reihe von Problemen aufweist und ohne weitere Änderungen nicht ordnungsgemäß funktioniert.

6) Ich bin dann auf den folgenden SO-Thread gestoßen, in dem klar erklärt wird, wie hin und her gesendete Nachrichten ordnungsgemäß codiert und decodiert werden: Wie kann ich WebSocket-Nachrichten auf der Serverseite senden und empfangen?

Dieser Link war wirklich hilfreich. Ich empfehle, es beim Betrachten des WebSocket-Entwurfs zu konsultieren. Es wird helfen, mehr Sinn aus dem zu machen, was der Entwurf sagt.

7) Ich war zu diesem Zeitpunkt fast fertig, hatte aber einige Probleme mit einer WebRTC-App, die ich mit WebSocket erstellt habe, und stellte schließlich meine eigene Frage zu SO, die ich schließlich löste. Um auf die Frage und Antwort zu verweisen, führen Sie bitte eine Websuche nach "SO Was sind diese Daten am Ende der WebRTC-Kandidateninformationen?" Durch. (ohne Anführungszeichen).

8) Zu diesem Zeitpunkt hatte ich so ziemlich alles zum Laufen gebracht. Ich musste nur eine zusätzliche Logik für das Schließen von Verbindungen hinzufügen, und ich war fertig.

Dieser Prozess dauerte insgesamt etwa zwei Wochen. Die gute Nachricht ist, dass ich WebSocket jetzt sehr gut verstehe und meine eigenen Client- und Server-Skripte von Grund auf neu erstellen konnte, die hervorragend funktionieren. Hoffentlich gibt Ihnen der Höhepunkt all dieser Informationen genügend Anleitungen und Informationen, um Ihr eigenes WebSocket-PHP-Skript zu codieren. Viel Glück!

Bearbeiten : Diese Bearbeitung erfolgt einige Jahre nach meiner ursprünglichen Antwort, und obwohl ich noch eine funktionierende Lösung habe, ist sie nicht wirklich zum Teilen bereit. Glücklicherweise hat jemand anderes auf GitHub fast den gleichen Code wie ich (aber viel sauberer), daher empfehle ich, den folgenden Code für eine funktionierende PHP WebSocket-Lösung zu verwenden:
https://github.com/ghedipunk/PHP-Websockets/blob/master/ websockets.php

Edit # 2 : Obwohl ich PHP immer noch gerne für viele serverseitige Dinge verwende, muss ich zugeben, dass ich mich in letzter Zeit sehr auf Node.js aufgewärmt habe, und der Hauptgrund ist, dass es besser aus dem entwickelt wurde gemahlen, um WebSocket als PHP (oder eine andere serverseitige Sprache) zu handhaben. Daher habe ich kürzlich festgestellt, dass es viel einfacher ist, sowohl Apache / PHP als auch Node.js auf Ihrem Server einzurichten und Node.js zum Ausführen des WebSocket-Servers und Apache / PHP für alles andere zu verwenden. Und wenn Sie sich in einer gemeinsam genutzten Hosting-Umgebung befinden, in der Sie Node.js für WebSocket nicht installieren / verwenden können, können Sie einen kostenlosen Dienst wie Heroku verwenden, um einen Node.js-WebSocket-Server einzurichten und domänenübergreifend zu erstellen Anfragen von Ihrem Server.

HartleySan
quelle
Thx, ich werde versuchen, es auf deine Weise zu tun. Wie bewerten Sie die Leistung dieses PHP-Servers?
Dharman
@Dharman, es ist schwer zu sagen, weil ich es nur auf meinem lokalen Host ausführen konnte. Natürlich funktioniert es dort gut, aber auf einem echten Server mit hoher Last weiß ich es nicht. Ich kann mir vorstellen, dass es ziemlich gut laufen würde, da mein Code kein Aufblähen enthält.
HartleySan
1
Ich habe es momentan noch nicht, aber in naher Zukunft plane ich, ein Tutorial über den gesamten Prozess mit dem gesamten Code zu schreiben. Sobald ich das mache, werde ich den Link posten.
Hartley San
1
@ HartleySan: Hallo! Ich würde mich sehr für Ihren Code interessieren. Könnten Sie es online stellen oder mir persönlich senden?
Forthrin
Ja, es wird bald online sein. Entschuldigung an alle, die danach gefragt haben. Ich war in letzter Zeit so beschäftigt. Ich werde es bald tun.
HartleySan
23

Soweit mir bekannt ist, ist Ratchet die derzeit beste verfügbare PHP-WebSocket-Lösung. Und da es Open Source ist , können Sie sehen, wie der Autor diese WebSocket-Lösung mit PHP erstellt hat.

leggetter
quelle
2
Ich füge hier meine Lösung hinzu, die Ratchet und Silex verwendet: github.com/eole-io/sandstone Ich weiß nicht, ob Sie es nützlich finden werden
Alcalyn
8

Warum nicht Sockets http://uk1.php.net/manual/en/book.sockets.php verwenden ? Es ist gut dokumentiert (nicht nur im PHP-Kontext) und hat gute Beispiele http://uk1.php.net/manual/en/sockets.examples.php

Lukasz Kujawa
quelle
2
Ja, Sie können eine kontinuierliche Verbindung zwischen einfachen PHP-Sockets und einer Webseite herstellen. Ich habe sie mehrmals getestet.
WiMantis
@ WiMantis: Hallo! Könnten Sie ein Codebeispiel dafür online stellen oder es mir optional persönlich senden?
Forthrin
Können Sie dies neben einer normalen HTTP-Verbindung verwenden? Ich habe ein DDD-Framework erstellt und möchte darüber hinaus eine Wrapper-Klasse erstellen und Socket-Funktionen bereitstellen, vorzugsweise in Vanille-PHP mithilfe der
1

Sie müssen den Schlüssel vor base64_encoding von hex in dec konvertieren und ihn dann zum Handshake senden.

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

Lassen Sie mich wissen, ob das hilft.

user2288650
quelle
1

Ich war eine Weile in Ihren Schuhen und habe schließlich node.js verwendet, weil es hybride Lösungen wie Web- und Socket-Server in einem kann. Das PHP-Backend kann also Anfragen über http an den Node-Webserver senden und diese dann mit Websocket senden. Sehr effizienter Weg.

MZ
quelle
Also müssen wir einen http-Client verwenden, um eine http-Anfrage von PHP an den Knotenserver zu stellen, oder?
Kiren Siva
Kiren Siva, richtig. Curl oder ähnliches, dann sendet der Knoten eine Nachricht über den 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);
    }
}

von einem Terminal ausführen: php server.php

von einem anderen Terminal laufen: echo "hallo woerld" | nc 127.0.0.1 8002

Dipak Yadav
quelle
1
Was soll das sein?
Dharman
8
Dies sind Sockets, keine WebSockets. Es gibt einen großen Unterschied.
Chris
@techexpander, Laut Frage muss man von Grund auf neu erklären anstatt ex. was nicht funktioniert.
Jaymin