Hoe maak je een websockets-server in PHP

Ik ben op zoek naar een eenvoudige code om een ​​WebSocket-server te maken. Ik heb phpwebsockets gevonden, maar het is nu verouderd en ondersteunt het nieuwste protocol niet. Ik heb geprobeerd het zelf te updaten, maar het lijkt niet te werken.

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

en de klant:

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

Als er iets mis is in mijn code, kun je me dan helpen dit op te lossen? Concole in firefox zegt

Firefox kan geen verbinding maken met de server op ws://localhost:12345/.


Antwoord 1, autoriteit 100%

Ik zat onlangs in hetzelfde schuitje als jij, en dit is wat ik deed:

  1. Ik heb de code phpwebsockets gebruikt als referentie voor het structureren van de server-side code. (Het lijkt erop dat je dit al doet, en zoals je hebt opgemerkt, werkt de code om verschillende redenen niet echt.)

  2. Ik heb PHP.net gebruikt om de details over elke socketfunctie te lezen gebruikt in de phpwebsockets-code. Door dit te doen, kon ik eindelijk begrijpen hoe het hele systeem conceptueel werkt. Dit was een behoorlijk grote hindernis.

  3. Ik heb het daadwerkelijke WebSocket-concept gelezen. Ik moest dit ding een aantal keren lezen voordat het eindelijk tot me doordrong. Je zult waarschijnlijk tijdens het hele proces keer op keer naar dit document moeten terugkeren, omdat het de enige definitieve bron is met correcte, up-to-date informatie over de WebSocket API.

  4. Ik heb de juiste handdrukprocedure gecodeerd op basis van de instructies in het concept in #3. Dit viel mee.

  5. Ik kreeg steeds een hoop onleesbare tekst die van de clients naar de server werd gestuurd na de handdruk en ik begreep niet waarom totdat ik me realiseerde dat de gegevens gecodeerd zijn en ontmaskerd moeten worden. De volgende link heeft me hier erg geholpen: (originele link verbroken) Gearchiveerde kopie.

    Houd er rekening mee dat de code die beschikbaar is via deze link een aantal problemen heeft en niet correct zal werken zonder verdere aanpassingen.

  6. Toen kwam ik de volgende SO-thread tegen, waarin duidelijk wordt uitgelegd hoe berichten die heen en weer worden verzonden, correct worden gecodeerd en gedecodeerd: Hoe kan ik WebSocket-berichten verzenden en ontvangen aan de serverzijde?

    Deze link was erg nuttig. Ik raad aan om het te raadplegen terwijl je naar het ontwerp van WebSocket kijkt. Het zal helpen om meer betekenis te geven aan wat het concept zegt.

  7. Ik was op dit punt bijna klaar, maar had wat problemen met een WebRTC-app die ik maakte met WebSocket, dus uiteindelijk stelde ik mijn eigen vraag over SO, die ik uiteindelijk heb opgelost: Wat zijn deze gegevens aan het einde van WebRTC kandidaat-info?

  8. Op dit moment had ik het vrijwel allemaal werkend. Ik moest alleen wat extra logica toevoegen voor het afhandelen van het sluiten van verbindingen, en ik was klaar.

Dat proces kostte me in totaal ongeveer twee weken. Het goede nieuws is dat ik WebSocket nu heel goed begrijp en dat ik mijn eigen client- en serverscripts helemaal opnieuw heb kunnen maken die geweldig werken.
Hopelijk geeft het hoogtepunt van al die informatie je voldoende begeleiding en informatie om je eigen WebSocket PHP-script te coderen.

Veel succes!


Bewerken: deze bewerking is een paar jaar na mijn oorspronkelijke antwoord, en hoewel ik nog steeds een werkende oplossing heb, is deze nog niet echt klaar om te delen. Gelukkig heeft iemand anders op GitHub bijna identieke code als de mijne (maar veel schoner), dus ik raad aan om de volgende code te gebruiken voor een werkende PHP WebSocket-oplossing:
https://github.com/ghedipunk/PHP-Websockets/blob/master /websockets.php


Bewerken #2: hoewel ik PHP nog steeds graag gebruik voor veel servergerelateerde dingen, moet ik toegeven dat ik de laatste tijd heel erg gewend ben geraakt aan Node.js, en de belangrijkste reden is dat het vanaf het begin beter is ontworpen om WebSocket te verwerken dan PHP (of een andere server-side taal). Als zodanig heb ik onlangs ontdekt dat het een stuk eenvoudiger is om zowel Apache/PHP als Node.js op uw server in te stellen en Node.js te gebruiken voor het uitvoeren van de WebSocket-server en Apache/PHP voor al het andere. En in het geval dat u zich in een gedeelde hostingomgeving bevindt waarin u Node.js voor WebSocket niet kunt installeren/gebruiken, kunt u een gratis service gebruiken zoals Heroku om een ​​Node.js WebSocket-server op te zetten en er domeinoverschrijdende verzoeken vanaf uw server naar toe te sturen. Zorg er wel voor dat als u dat doet, uw WebSocket-server zo is ingesteld dat deze cross-origin-verzoeken kan verwerken.


Antwoord 2, autoriteit 21%

Voor zover ik weet is Ratchet de beste PHP WebSocket-oplossing die momenteel beschikbaar is. En aangezien het open source is, kun je zien hoe de auteur deze WebSocket-oplossing heeft gebouwd met PHP.


Antwoord 3

Ik heb een tijdje in jouw schoenen gestaan ​​en ben uiteindelijk node.js gaan gebruiken, omdat het hybride oplossingen kan bieden, zoals een web- en socketserver in één. Dus php-backend kan verzoeken indienen via http naar node-webserver en deze vervolgens uitzenden met websocket. Zeer efficiënte manier om te gaan.


Antwoord 4

Ik heb gezocht naar de minimaal mogelijke oplossing om gedurende uren PHP + WebSockets te gebruiken, totdat ik dit artikel vond:

Super eenvoudig PHP WebSocket-voorbeeld

Het vereist geen bibliotheek van derden.

Zo doet u het: maak een index.html aan die dit bevat:

<html>
<body>
    <div id="root"></div>
    <script>
        var host = 'ws://<<<IP_OF_YOUR_SERVER>>>:12345/websockets.php';
        var socket = new WebSocket(host);
        socket.onmessage = function(e) {
            document.getElementById('root').innerHTML = e.data;
        };
    </script>
</body>
</html>

en open het in de browser, net nadat je php websockets.php in de opdrachtregel hebt gestart (ja, het zal een gebeurtenislus zijn, waarbij constant PHP-script wordt uitgevoerd), met deze websockets.php


Antwoord 5

Moet de sleutel van hex naar dec converteren vóór base64_encoding en vervolgens verzenden voor 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";

Antwoord 6

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

voer vanaf één terminal : php server.php

vanaf een andere terminal run: echo “hallo woerld” | nc 127.0.0.1 8002

LEAVE A REPLY

Please enter your comment!
Please enter your name here

11 + 18 =

Other episodes