Cách tạo máy chủ websockets bằng PHP


88

Có bất kỳ hướng dẫn hoặc hướng dẫn nào chỉ ra cách viết cho tôi một máy chủ websockets đơn giản bằng PHP không? Tôi đã thử tìm kiếm nó trên google nhưng tôi không tìm thấy nhiều. Tôi đã tìm thấy phpwebsockets nhưng hiện tại nó đã lỗi thời và không hỗ trợ giao thức mới nhất. Tôi đã thử tự cập nhật nó nhưng có vẻ như nó không hoạt động.

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

?>

và khách hàng:

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

Nếu có gì sai trong mã của tôi, bạn có thể giúp tôi sửa chữa nó không? Concole trong firefox nóiFirefox can't establish a connection to the server at ws://localhost:12345/.

CHỈNH SỬA
Vì có nhiều sự quan tâm đến câu hỏi này, tôi quyết định cung cấp cho bạn những gì cuối cùng tôi đã nghĩ ra. Đây là mã đầy đủ của tôi.


1
Trang này liệt kê rằng họ cũng có vấn đề với phpwebsockets hiện và bao gồm các thay đổi mà họ thực hiện trong ví dụ src mã: net.tutsplus.com/tutorials/javascript-ajax/...
scrappedcola

1
Một thư viện hữu ích có thể được sử dụng cho các ứng dụng WebSockets. bao gồm cả máy khách và phía PHP. techzonemind.com/…
Jithin Jose

1
Tôi nghĩ tốt hơn nên triển khai nó bằng C ++.
Michael Chourdakis

Câu trả lời:


114

Tôi đã ở cùng thuyền với bạn gần đây, và đây là những gì tôi đã làm:

  1. Tôi đã sử dụng mã phpwebsockets làm tài liệu tham khảo về cách cấu trúc mã phía máy chủ. (Có vẻ như bạn đã làm việc này và như bạn đã lưu ý, mã không thực sự hoạt động vì nhiều lý do.)

  2. Tôi đã sử dụng PHP.net để đọc chi tiết về mọi hàm socket được sử dụng trong mã phpwebsockets. Bằng cách làm này, cuối cùng tôi đã có thể hiểu cách toàn bộ hệ thống hoạt động về mặt khái niệm. Đây là một trở ngại khá lớn.

  3. Tôi đã đọc bản nháp WebSocket thực tế . Tôi đã phải đọc tài liệu này nhiều lần trước khi nó bắt đầu chìm vào trong. Bạn có thể sẽ phải quay lại tài liệu này nhiều lần trong suốt quá trình, vì nó là một nguồn tài liệu chính xác, cập nhật thông tin về API WebSocket.

  4. Tôi đã viết mã quy trình bắt tay thích hợp dựa trên hướng dẫn trong bản nháp ở mục # 3. Điều này không quá tệ.

  5. Tôi tiếp tục nhận được một loạt văn bản bị cắt xén được gửi từ máy khách đến máy chủ sau khi bắt tay và tôi không thể tìm ra lý do tại sao cho đến khi tôi nhận ra rằng dữ liệu đã được mã hóa và phải được hiển thị. Liên kết sau đã giúp tôi rất nhiều ở đây: (liên kết ban đầu bị hỏng) Bản sao lưu trữ .

    Xin lưu ý rằng mã có sẵn tại liên kết này có một số vấn đề và sẽ không hoạt động bình thường nếu không được sửa đổi thêm.

  6. Sau đó, tôi xem qua chuỗi SO sau, trong đó giải thích rõ ràng cách mã hóa và giải mã đúng cách các thư được gửi qua lại: Làm cách nào để gửi và nhận các thư WebSocket ở phía máy chủ?

    Liên kết này thực sự hữu ích. Tôi khuyên bạn nên tham khảo nó trong khi xem bản nháp WebSocket. Nó sẽ giúp hiểu rõ hơn những gì bản nháp đang nói.

  7. Tôi gần như đã hoàn thành vào thời điểm này, nhưng có một số vấn đề với ứng dụng WebRTC mà tôi đang tạo bằng cách sử dụng WebSocket, vì vậy tôi đã đặt câu hỏi của riêng mình trên SO, câu hỏi cuối cùng tôi đã giải quyết được: Dữ liệu này ở cuối thông tin ứng viên WebRTC là gì?

  8. Tại thời điểm này, tôi đã có khá nhiều thứ đã hoạt động. Tôi chỉ cần thêm một số logic bổ sung để xử lý việc đóng các kết nối và tôi đã hoàn thành.

Quá trình đó mất tổng cộng hai tuần của tôi. Tin tốt là bây giờ tôi hiểu rõ về WebSocket và tôi đã có thể tạo các tập lệnh máy khách và máy chủ của riêng mình từ đầu hoạt động tốt. Hy vọng rằng đỉnh cao của tất cả thông tin đó sẽ cung cấp cho bạn đủ hướng dẫn và thông tin để viết mã tập lệnh PHP WebSocket của riêng bạn.

Chúc may mắn!


Chỉnh sửa : Chỉnh sửa này sau một vài năm sau câu trả lời ban đầu của tôi và mặc dù tôi vẫn còn một giải pháp đang hoạt động, nhưng nó vẫn chưa thực sự sẵn sàng để chia sẻ. May mắn thay, ai đó trên GitHub có mã gần như giống hệt mã của tôi (nhưng rõ ràng hơn nhiều), vì vậy tôi khuyên bạn nên sử dụng mã sau cho giải pháp PHP WebSocket đang hoạt động:
https://github.com/ghedipunk/PHP-Websockets/blob/master/ websockets.php


Chỉnh sửa # 2 : Mặc dù tôi vẫn thích sử dụng PHP cho nhiều thứ liên quan đến phía máy chủ, nhưng tôi phải thừa nhận rằng gần đây tôi đã thực sự thích Node.js rất nhiều và lý do chính là vì nó được thiết kế tốt hơn từ nền tảng để xử lý WebSocket hơn PHP (hoặc bất kỳ ngôn ngữ phía máy chủ nào khác). Do đó, gần đây tôi đã nhận thấy rằng việc thiết lập cả Apache / PHP và Node.js trên máy chủ của bạn và sử dụng Node.js để chạy máy chủ WebSocket và Apache / PHP cho mọi thứ khác dễ dàng hơn rất nhiều. Và trong trường hợp bạn đang sử dụng môi trường lưu trữ chia sẻ mà bạn không thể cài đặt / sử dụng Node.js cho WebSocket, bạn có thể sử dụng dịch vụ miễn phí như Herokuđể thiết lập máy chủ Node.js WebSocket và thực hiện các yêu cầu miền chéo từ máy chủ của bạn. Chỉ cần đảm bảo nếu bạn làm điều đó để thiết lập máy chủ WebSocket của bạn để có thể xử lý các yêu cầu nguồn gốc chéo.


Thx, tôi sẽ thử làm theo cách của bạn. Bạn đánh giá thế nào về hiệu suất của máy chủ PHP này?
Dharman

@Dharman, thật khó nói vì tôi chỉ có thể chạy nó trên localhost của mình. Tất nhiên nó hoạt động tốt ở đó, nhưng trên một máy chủ thực sự có tải nặng thì tôi không biết. Mặc dù vậy, tôi tưởng tượng nó sẽ chạy khá tốt, vì mã của tôi không có gì cồng kềnh.
HartleySan

1
Tôi chưa có nó vào lúc này, nhưng trong tương lai gần, tôi đang lên kế hoạch viết một hướng dẫn về toàn bộ quá trình với tất cả mã. Một khi tôi làm điều đó, tôi sẽ đăng liên kết.
HartleySan

1
@HartleySan: Xin chào! Tôi rất muốn xem mã của bạn. Bạn có thể đặt nó trực tuyến, hoặc gửi nó cho cá nhân tôi?
outrin

Có, nó sẽ sớm được trực tuyến. Xin lỗi tất cả những người đã yêu cầu nó. Gần đây tôi rất bận rộn. Tôi sẽ nhận được trên nó sớm.
HartleySan

26

Theo như tôi biết thì Ratchet là giải pháp PHP WebSocket tốt nhất hiện có. Và vì nó là mã nguồn mở nên bạn có thể thấy tác giả đã xây dựng giải pháp WebSocket này bằng PHP như thế nào.


2
Tôi thêm ở đây giải pháp của tôi trong đó sử dụng Ratchet và silex: github.com/eole-io/sandstone Tôi không biết nếu bạn sẽ tìm thấy nó hữu ích
Alcalyn

8

Tại sao không sử dụng socket http://uk1.php.net/manual/en/book.sockets.php ? Nó được tài liệu hóa tốt (không chỉ trong ngữ cảnh PHP) và có các ví dụ hay http://uk1.php.net/manual/en/sockets.examples.php


2
Có, bạn có thể có kết nối liên tục giữa các ổ cắm PHP thuần túy và một trang web, tôi đã thử nghiệm nó nhiều lần.
WiMantis

@WiMantis: Xin chào! Bạn có thể đặt một ví dụ mã thực hiện điều này trực tuyến, hoặc tùy chọn gửi nó cho cá nhân tôi?
outrin

Bạn có thể sử dụng điều này cùng với kết nối HTTP thông thường không? Tôi đang xây dựng một khung DDD và tôi muốn tạo giống như một lớp trình bao bọc trên lớp này và cung cấp chức năng socket, tốt nhất là trong vani php bằng cách sử dụng tiện ích mở rộng cốt lõi

1

Cần chuyển đổi khóa từ hex sang dec trước khi base64_encoding và sau đó gửi nó để bắt tay.

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

Hãy cho tôi biết nếu điều này sẽ giúp.


1

Tôi đã quan tâm đến bạn một thời gian và cuối cùng đã sử dụng node.js, vì nó có thể thực hiện các giải pháp kết hợp như có máy chủ web và ổ cắm trong một. Vì vậy, php backend có thể gửi các yêu cầu thông qua http đến máy chủ web node và sau đó phát nó với websocket. Cách rất hiệu quả để đi.


vì vậy chúng ta phải sử dụng một máy khách http để thực hiện yêu cầu http từ php đến máy chủ nút phải không?
Kiren Siva

Kiren Siva, đúng. Curl hoặc tương tự, sau đó nút phát một thông báo qua 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);
    }
}

từ một đầu cuối chạy: php server.php

từ một thiết bị đầu cuối chạy: echo "hello woerld" | nc 127.0.0.1 8002


1
Điều đó có nghĩa là gì?
Dharman

8
Đây là ổ cắm, không phải WebSockets. Có một sự khác biệt lớn.
Chris

@techexpander, Theo câu hỏi, bạn phải giải thích từ đầu thay vì người yêu cũ. mà không hoạt động.
Jaymin
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.