Cara membuat server websockets di PHP


89

Apakah ada tutorial atau panduan yang menunjukkan cara menulis sendiri server websockets sederhana di PHP? Saya sudah mencoba mencarinya di google tetapi saya tidak menemukan banyak. Saya menemukan phpwebsockets tetapi sekarang sudah usang dan tidak mendukung protokol terbaru. Saya mencoba memperbaruinya sendiri tetapi sepertinya tidak berhasil.

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

?>

dan klien:

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

Jika ada yang salah dalam kode saya, dapatkah Anda membantu saya memperbaikinya? Konsol di firefox mengatakanFirefox can't establish a connection to the server at ws://localhost:12345/.

EDIT
Karena ada banyak minat dalam pertanyaan ini, saya memutuskan untuk memberi Anda apa yang akhirnya saya temukan. Ini kode lengkap saya.


1
Halaman ini mencantumkan bahwa mereka juga memiliki masalah dengan phpwebsockets saat ini dan menyertakan perubahan yang mereka buat dalam contoh kode src: net.tutsplus.com/tutorials/javascript-ajax/…
scrappedcola

1
Perpustakaan berguna yang dapat digunakan untuk aplikasi WebSockets. sertakan sisi klien dan PHP. techzonemind.com/…
Jithin Jose

1
Saya pikir lebih baik menerapkannya di C ++.
Michael Chourdakis

Jawaban:


118

Saya berada di perahu yang sama dengan Anda baru-baru ini, dan inilah yang saya lakukan:

  1. Saya menggunakan kode phpwebsockets sebagai referensi untuk bagaimana menyusun kode sisi server. (Sepertinya Anda sudah melakukan ini, dan seperti yang Anda catat, kode sebenarnya tidak berfungsi karena berbagai alasan.)

  2. Saya menggunakan PHP.net untuk membaca detail tentang setiap fungsi soket yang digunakan dalam kode phpwebsockets. Dengan melakukan ini, saya akhirnya dapat memahami bagaimana keseluruhan sistem bekerja secara konseptual. Ini adalah rintangan yang cukup besar.

  3. Saya membaca draf WebSocket yang sebenarnya . Saya harus membaca hal ini beberapa kali sebelum akhirnya mulai meresap. Anda kemungkinan besar harus kembali ke dokumen ini lagi dan lagi selama proses, karena ini adalah satu-satunya sumber daya definitif dengan benar, terkini informasi tentang API WebSocket.

  4. Saya mengkodekan prosedur jabat tangan yang tepat berdasarkan instruksi dalam draf di # 3. Ini tidak terlalu buruk.

  5. Saya terus mendapatkan banyak teks kacau yang dikirim dari klien ke server setelah jabat tangan dan saya tidak tahu mengapa sampai saya menyadari bahwa data dikodekan dan harus dibuka kedoknya. Tautan berikut sangat membantu saya di sini: (tautan asli rusak) Salinan yang diarsipkan .

    Harap dicatat bahwa kode yang tersedia di tautan ini memiliki sejumlah masalah dan tidak akan berfungsi dengan baik tanpa modifikasi lebih lanjut.

  6. Saya kemudian menemukan utas SO berikut, yang dengan jelas menjelaskan cara menyandikan dan mendekode pesan yang dikirim bolak-balik dengan benar: Bagaimana saya dapat mengirim dan menerima pesan WebSocket di sisi server?

    Tautan ini sangat membantu. Saya sarankan untuk berkonsultasi sambil melihat draf WebSocket. Ini akan membantu lebih memahami apa yang dikatakan draf.

  7. Saya hampir selesai pada titik ini, tetapi memiliki beberapa masalah dengan aplikasi WebRTC yang saya buat menggunakan WebSocket, jadi saya akhirnya menanyakan pertanyaan saya sendiri tentang SO, yang akhirnya saya pecahkan: Data apa ini di akhir info kandidat WebRTC?

  8. Pada titik ini, saya sudah cukup banyak bekerja. Saya hanya perlu menambahkan beberapa logika tambahan untuk menangani penutupan koneksi, dan saya selesai.

Proses itu memakan waktu total sekitar dua minggu. Kabar baiknya adalah saya memahami WebSocket dengan sangat baik sekarang dan saya dapat membuat skrip klien dan server saya sendiri dari awal yang berfungsi dengan baik. Semoga hasil puncak dari semua informasi itu akan memberi Anda panduan dan informasi yang cukup untuk membuat kode skrip PHP WebSocket Anda sendiri.

Semoga berhasil!


Sunting : Suntingan ini beberapa tahun setelah jawaban asli saya, dan meskipun saya masih memiliki solusi yang berfungsi, itu belum benar-benar siap untuk dibagikan. Untungnya, orang lain di GitHub memiliki kode yang hampir identik dengan milik saya (tetapi jauh lebih bersih), jadi saya sarankan menggunakan kode berikut untuk solusi WebSocket PHP yang berfungsi:
https://github.com/ghedipunk/PHP-Websockets/blob/master/ websockets.php


Sunting # 2 : Meskipun saya masih menikmati menggunakan PHP untuk banyak hal terkait sisi server, saya harus mengakui bahwa saya telah benar-benar melakukan pemanasan dengan Node.js baru-baru ini, dan alasan utamanya adalah karena dirancang lebih baik dari dasar untuk menangani WebSocket daripada PHP (atau bahasa sisi server lainnya). Karena itu, saya baru-baru ini menemukan bahwa jauh lebih mudah untuk menyiapkan Apache / PHP dan Node.js di server Anda dan menggunakan Node.js untuk menjalankan server WebSocket dan Apache / PHP untuk yang lainnya. Dan jika Anda berada di lingkungan hosting bersama di mana Anda tidak dapat menginstal / menggunakan Node.js untuk WebSocket, Anda dapat menggunakan layanan gratis seperti Herokuuntuk menyiapkan server WebSocket Node.js dan membuat permintaan lintas domain dari server Anda. Pastikan jika Anda melakukannya untuk menyetel server WebSocket Anda agar dapat menangani permintaan lintas sumber.


Thx, saya akan mencoba melakukannya dengan cara Anda. Bagaimana Anda menilai kinerja server PHP ini?
Dharman

@Dharman, sulit dikatakan karena saya hanya bisa menjalankannya di localhost saya. Tentu saja ini berfungsi dengan baik di sana, tetapi di server nyata dengan beban berat, saya tidak tahu. Saya membayangkan itu akan berjalan cukup baik, karena tidak ada gembung dalam kode saya.
HartleySan

1
Saya tidak memilikinya saat ini, tetapi dalam waktu dekat, saya berencana menulis tutorial tentang seluruh proses dengan semua kode. Setelah saya melakukannya, saya akan memposting tautannya.
HartleySan

1
@HartSan: Halo! Saya akan sangat tertarik melihat kode Anda. Bisakah Anda menaruhnya secara online, atau mengirimkannya kepada saya secara pribadi?
menerus

Ya, ini akan segera online. Maaf untuk semua orang yang memintanya. Aku baru saja sibuk belakangan ini. Saya akan segera melakukannya.
HartleySan

26

Sejauh yang saya tahu, Ratchet adalah solusi PHP WebSocket terbaik yang tersedia saat ini. Dan karena ini open source, Anda dapat melihat bagaimana penulis membuat solusi WebSocket ini menggunakan PHP.


2
Saya menambahkan di sini solusi saya yang menggunakan Ratchet dan Silex: github.com/eole-io/sandstone Saya tidak tahu apakah Anda akan menganggapnya berguna
Alcalyn

8

Mengapa tidak menggunakan soket http://uk1.php.net/manual/en/book.sockets.php ? Ini didokumentasikan dengan baik (tidak hanya dalam konteks PHP) dan memiliki contoh yang baik http://uk1.php.net/manual/en/sockets.examples.php


2
Ya, Anda dapat memiliki koneksi berkelanjutan antara soket PHP biasa dan halaman web, saya telah mengujinya beberapa kali.
WiMantis

@WiMantis: Halo! Dapatkah Anda memberikan contoh kode yang melakukan ini secara online, atau secara opsional mengirimkannya kepada saya secara pribadi?
menerus

Bisakah Anda menggunakan ini bersama koneksi HTTP biasa? Saya sedang membangun kerangka kerja DDD dan saya ingin membuat seperti kelas pembungkus di atas ini dan menyediakan fungsionalitas soket, lebih disukai di vanilla php dengan menggunakan ekstensi inti

1

Perlu mengonversi kunci dari hex ke dec sebelum base64_encoding dan kemudian mengirimkannya untuk jabat tangan.

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

Beri tahu saya jika ini membantu.


1

Saya berada di posisi Anda untuk sementara dan akhirnya menggunakan node.js, karena dapat melakukan solusi hybrid seperti memiliki web dan server soket dalam satu. Jadi php backend dapat mengirimkan permintaan melalui http ke server web node dan kemudian menyiarkannya dengan websocket. Cara yang sangat efisien untuk pergi.


jadi kita harus menggunakan klien http untuk membuat permintaan http dari php ke server node kan?
Kiren Siva

Kiren Siva, benar. Curl atau serupa, kemudian node menyiarkan pesan melalui 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);
    }
}

dari satu terminal run: php server.php

dari terminal lain yang dijalankan: echo "hello woerld" | nc 127.0.0.1 8002


1
Apa yang dimaksud dengan itu?
Dharman

8
Ini soket, bukan WebSockets. Ada perbedaan yang besar.
Chris

@techexpander, Sesuai pertanyaan Anda harus menjelaskan dari awal, bukan dari mantan. yang tidak berfungsi.
Jaymin
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.