PHP'de websockets sunucusu nasıl oluşturulur


89

Kendime PHP'de basit bir websockets sunucusu yazmayı gösteren herhangi bir öğretici veya kılavuz var mı? Google'da aramayı denedim ama çok bulamadım. Phpwebsockets buldum ama artık güncel değil ve en yeni protokolü desteklemiyor. Kendim güncellemeyi denedim ama işe yaramıyor.

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

?>

ve müşteri:

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

Kodumda bir sorun varsa düzeltmeme yardım edebilir misin? Firefox'ta Concole diyorFirefox can't establish a connection to the server at ws://localhost:12345/.

DÜZENLE
Bu soruya çok ilgi duyduğum için, sonunda bulduğum şeyi size sunmaya karar verdim. İşte tam kodum.


1
: Onlar da güncel phpwebsockets ile sorunları vardı ve onlar examples src kodunda yapılan değişiklikler içerdiğini Bu sayfa listeleri net.tutsplus.com/tutorials/javascript-ajax/...
scrappedcola

1
WebSockets uygulamaları için kullanılabilen kullanışlı bir kitaplık. hem istemci hem de PHP tarafını içerir. techzonemind.com/…
Jithin Jose

1
Bence bunu C ++ 'da uygulamak daha iyidir.
Michael Chourdakis

Yanıtlar:


118

Geçenlerde seninle aynı gemideydim ve işte yaptığım şey:

  1. Phpwebsockets kodunu sunucu tarafı kodunun nasıl yapılandırılacağına dair bir referans olarak kullandım . (Bunu zaten yapıyor görünüyorsunuz ve belirttiğiniz gibi, kod aslında çeşitli nedenlerle çalışmıyor.)

  2. Phpwebsockets kodunda kullanılan her soket işlevinin ayrıntılarını okumak için PHP.net'i kullandım. Bunu yaparak, nihayet tüm sistemin kavramsal olarak nasıl çalıştığını anlayabildim. Bu oldukça büyük bir engeldi.

  3. Gerçek WebSocket taslağını okudum . Bu şeyi nihayet anlamaya başlamadan önce birkaç kez okumak zorunda kaldım. Büyük olasılıkla süreç boyunca bu belgeye tekrar tekrar geri dönmeniz gerekecek, çünkü bu doğru ve güncel olan tek kesin kaynaktır. WebSocket API hakkında bilgi.

  4. # 3'teki taslaktaki talimatlara göre uygun el sıkışma prosedürünü kodladım. Bu çok da kötü değildi.

  5. El sıkışmadan sonra istemcilerden sunucuya gönderilen bir sürü bozuk metin almaya devam ettim ve verilerin kodlandığını ve maskesinin kaldırılması gerektiğini anlayana kadar nedenini anlayamadım. Aşağıdaki bağlantı bana burada çok yardımcı oldu: (orijinal bağlantı bozuk) Arşivlenmiş kopya .

    Lütfen bu bağlantıda bulunan kodun bir takım sorunları olduğunu ve daha fazla değişiklik yapılmadan düzgün çalışmayacağını unutmayın.

  6. Daha sonra, ileri geri gönderilen mesajların nasıl düzgün şekilde kodlanıp çözüleceğini açık bir şekilde açıklayan aşağıdaki SO iş parçacığıyla karşılaştım: Sunucu tarafında WebSocket mesajlarını nasıl gönderip alabilirim?

    Bu bağlantı gerçekten yardımcı oldu. WebSocket taslağına bakarken danışmanızı tavsiye ederim. Taslağın söylediği şeyi daha anlamlı hale getirmeye yardımcı olacaktır.

  7. Bu noktada neredeyse bitirdim, ancak WebSocket kullanarak yaptığım bir WebRTC uygulamasıyla ilgili bazı sorunlar yaşadım, bu yüzden sonunda çözdüğüm SO'da kendi sorumu sordum: WebRTC aday bilgilerinin sonundaki bu veriler nedir?

  8. Bu noktada, hemen hemen hepsini çalıştırdım. Bağlantıların kapatılmasıyla ilgili bazı ek mantık eklemek zorunda kaldım ve işim bitti.

Bu süreç toplamda yaklaşık iki hafta sürdü. İyi haber şu ki, WebSocket'i şimdi çok iyi anlıyorum ve sıfırdan harika çalışan kendi istemci ve sunucu komut dosyalarımı oluşturabildim. Umarım tüm bu bilgilerin doruk noktası size kendi WebSocket PHP betiğinizi kodlamak için yeterli rehberlik ve bilgi sağlar.

İyi şanslar!


Düzenleme : Bu düzenleme, orijinal cevabımdan birkaç yıl sonradır ve hala çalışan bir çözümüm olsa da, gerçekten paylaşıma hazır değil. Neyse ki, GitHub'daki bir başkası benimkiyle neredeyse aynı koda sahip (ancak çok daha temiz), bu nedenle çalışan bir PHP WebSocket çözümü için şu kodu kullanmanızı öneririm:
https://github.com/ghedipunk/PHP-Websockets/blob/master/ websockets.php


# 2'yi : PHP'yi sunucu tarafı ile ilgili pek çok şey için kullanmaktan hala keyif alsam da, son zamanlarda Node.js'ye gerçekten çok ısındığımı itiraf etmeliyim ve bunun ana nedeni, daha iyi tasarlanmış olmasıdır. WebSocket'i PHP'den (veya başka herhangi bir sunucu tarafı dilinden) işlemek için temel. Bu nedenle, son zamanlarda sunucunuzda hem Apache / PHP hem de Node.js'yi kurmanın ve WebSocket sunucusunu çalıştırmak için Node.js ve diğer her şey için Apache / PHP'yi kullanmanın çok daha kolay olduğunu keşfettim. WebSocket için Node.js yükleyemeyeceğiniz / kullanamayacağınız paylaşılan bir barındırma ortamında olmanız durumunda, Heroku gibi ücretsiz bir hizmeti kullanabilirsiniz.bir Node.js WebSocket sunucusu kurmak ve sunucunuzdan bu sunucuya etki alanları arası isteklerde bulunmak için. WebSocket sunucunuzu, çapraz kaynaklı istekleri işleyebilecek şekilde ayarlamak için yapıp yapmadığınıza emin olun.


Thx, kendi yönteminizle yapmaya çalışacağım. Bu PHP sunucusunun performansını nasıl değerlendiriyorsunuz?
Dharman

@Dharman, bunu söylemek zor çünkü onu yalnızca localhost'umda çalıştırabildim. Elbette orada gayet iyi çalışıyor, ancak ağır yüklü gerçek bir sunucuda bilmiyorum. Kodumda hiçbir şişkinlik olmadığı için oldukça iyi çalışacağını hayal ediyorum.
HartleySan

1
Şu anda bilmiyorum, ancak yakın gelecekte, tüm kodla tüm süreç hakkında bir eğitim yazmayı planlıyorum. Bunu yaptıktan sonra bağlantıyı göndereceğim.
HartleySan

1
@HartleySan: Merhaba! Kodunuza bakmakla çok ilgilenirim. İnternete koyabilir misin yoksa şahsen bana gönderebilir misin?
forthrin

Evet, yakında çevrimiçi olacak. Bunu isteyen herkese özür dilerim. Son zamanlarda çok meşguldüm. Yakında ilgileneceğim.
HartleySan


8

Neden http://uk1.php.net/manual/en/book.sockets.php soketleri kullanmıyorsunuz ? İyi belgelenmiştir (yalnızca PHP bağlamında değil) ve iyi örnekleri vardır http://uk1.php.net/manual/en/sockets.examples.php


2
Evet, düz PHP soketleri ve bir web sayfası arasında sürekli bir bağlantı kurabilirsiniz, bunu birçok kez test ettim.
WiMantis

@WiMantis: Merhaba! Bunu çevrimiçi yapan bir kod örneği koyabilir misiniz yoksa isteğe bağlı olarak şahsen bana gönderebilir misiniz?
forthrin

Bunu normal bir HTTP bağlantısının yanında kullanabilir misiniz? Bir DDD çerçevesi oluşturuyordum ve bunun üzerine bir sarmalayıcı sınıfı oluşturmak ve tercihen çekirdek uzantısını kullanarak vanilya

1

Base64_encoding işleminden önce anahtarı hex'den dec'e dönüştürmeniz ve ardından el sıkışma için göndermeniz gerekir.

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

Bunun yardımcı olup olmadığını bana bildirin.


1

Bir süredir senin yerindeydim ve sonunda node.js'yi kullandım çünkü web ve soket sunucusunun bir arada olması gibi hibrit çözümler yapabiliyor. Böylece php arka ucu, http üzerinden istekleri düğüm web sunucusuna gönderebilir ve ardından websocket ile yayınlayabilir. Gitmenin çok verimli yolu.


bu yüzden php'den düğüm sunucusuna http talebinde bulunmak için bir http istemcisi kullanmalıyız değil mi?
Kiren Siva

Kiren Siva, doğru. Kıvrılma veya benzeri, ardından düğüm web soketi aracılığıyla bir mesaj yayınlar
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);
    }
}

bir terminalden çalıştırma: php server.php

başka bir terminal çalışmasından: echo "merhaba woerld" | nc 127.0.0.1 8002


1
Bunun anlamı ne?
dharman

8
Bu soketlerdir, WebSockets değil. Büyük bir fark var.
Chris

@techexpander, Soruya göre eski yerine sıfırdan açıklamalısınız. hangi çalışmıyor.
Jaymin
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.