如何在 PHP 中创建 websockets 服务器

2021-12-25 00:00:00 websocket php javascript

我正在寻找一个简单的代码来创建一个 WebSocket 服务器.我找到了 phpwebsockets 但它现在已经过时并且不支持最新的协议.我尝试自己更新它,但它似乎不起作用.

I am looking for a simple code to create a WebSocket server. I found phpwebsockets but it is outdated now and doesn't support the newest protocol. I tried updating it myself but it doesn't seem to work.

#!/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')."
";
  echo "Master socket  : ".$master."
";
  echo "Listening on   : ".$address." port ".$port."

";
  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("
Requesting 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
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: $acceptkey
";

  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: (.*)
/"  ,$req,$match)){ $h=$match[1]; }
    if(preg_match("/Upgrade: (.*)
/",$req,$match)){ $u=$match[1]; }
    if(preg_match("/Connection: (.*)
/",$req,$match)){ $c=$match[1]; }
    if(preg_match("/Sec-WebSocket-Key: (.*)
/",$req,$match)){ $key=$match[1]; }
    if(preg_match("/Sec-WebSocket-Protocol: (.*)
/",$req,$match)){ $protocol=$match[1]; }
    if(preg_match("/Sec-WebSocket-Version: (.*)
/",$req,$match)){ $version=$match[1]; }
    if(preg_match("/Origin: (.*)
/",$req,$match)){ $o=$match[1]; }
    if(preg_match("/
(.*?)$/",$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."
"; }
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."
"; } }

class User{
  var $id;
  var $socket;
  var $handshake;
}

?>

和客户:

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

如果我的代码有问题,你能帮我修复吗?Firefox 中的 Concole 说

If there is anything wrong in my code can you help me fix it? Concole in firefox says

Firefox 无法与位于 ws://localhost:12345/的服务器建立连接.

Firefox can't establish a connection to the server at ws://localhost:12345/.

推荐答案

我最近和你在同一条船上,这是我所做的:

I was in the same boat as you recently, and here is what I did:

  1. 我使用 phpwebsockets 代码作为如何构建服务器端代码的参考.(您似乎已经在这样做了,而且正如您所指出的,由于各种原因,该代码实际上不起作用.)

  1. I used the phpwebsockets code as a reference for how to structure the server-side code. (You seem to already be doing this, and as you noted, the code doesn't actually work for a variety of reasons.)

我使用 PHP.net 阅读了有关 每个套接字函数的详细信息 在 phpwebsockets 代码中使用.通过这样做,我终于能够从概念上理解整个系统是如何工作的.这是一个相当大的障碍.

I used PHP.net to read the details about every socket function used in the phpwebsockets code. By doing this, I was finally able to understand how the whole system works conceptually. This was a pretty big hurdle.

我阅读了实际的 WebSocket 草案.我不得不多次阅读这件事,最后才开始深入了解它.在整个过程中,您可能必须一次又一次地返回此文档,因为它是具有正确、最新的权威资源有关 WebSocket API 的信息.

I read the actual WebSocket draft. I had to read this thing a bunch of times before it finally started to sink in. You will likely have to go back to this document again and again throughout the process, as it is the one definitive resource with correct, up-to-date information about the WebSocket API.

我根据 #3 中的草案中的说明编写了正确的握手程序.这还不错.

I coded the proper handshake procedure based on the instructions in the draft in #3. This wasn't too bad.

握手后,我不断收到从客户端发送到服务器的一堆乱码,直到我意识到数据已编码并且必须取消屏蔽后,我才弄清楚原因.以下链接在这里对我帮助很大:(original link 损坏) 存档副本.

I kept getting a bunch of garbled text sent from the clients to the server after the handshake and I couldn't figure out why until I realized that the data is encoded and must be unmasked. The following link helped me a lot here: (original link broken) Archived copy.

请注意,此链接提供的代码存在许多问题,如果不进一步修改将无法正常工作.

Please note that the code available at this link has a number of problems and won't work properly without further modification.

然后我遇到了以下 SO 线程,它清楚地解释了如何正确编码和解码来回发送的消息:如何在服务器端发送和接收WebSocket消息?

I then came across the following SO thread, which clearly explains how to properly encode and decode messages being sent back and forth: How can I send and receive WebSocket messages on the server side?

这个链接真的很有帮助.我建议在查看 WebSocket 草案时咨询它.这将有助于使草案的内容更有意义.

This link was really helpful. I recommend consulting it while looking at the WebSocket draft. It'll help make more sense out of what the draft is saying.

此时我差不多完成了,但是我使用 WebSocket 制作的 WebRTC 应用程序遇到了一些问题,所以我最终在 SO 上提出了我自己的问题,我最终解决了这个问题:WebRTC 候选信息末尾的数据是什么?

I was almost done at this point, but had some issues with a WebRTC app I was making using WebSocket, so I ended up asking my own question on SO, which I eventually solved: What is this data at the end of WebRTC candidate info?

在这一点上,我几乎已经完成了所有工作.我只需要添加一些额外的逻辑来处理连接的关闭,就大功告成了.

At this point, I pretty much had it all working. I just had to add some additional logic for handling the closing of connections, and I was done.

这个过程总共花了我大约两周的时间.好消息是,我现在非常了解 WebSocket,并且能够从头开始制作自己的客户端和服务器脚本,效果很好.希望所有这些信息的总结将为您提供足够的指导和信息来编写您自己的 WebSocket PHP 脚本.

That process took me about two weeks total. The good news is that I understand WebSocket really well now and I was able to make my own client and server scripts from scratch that work great. Hopefully the culmination of all that information will give you enough guidance and information to code your own WebSocket PHP script.

祝你好运!

编辑:这个编辑是在我最初的答案之后几年的,虽然我仍然有一个可行的解决方案,但它并没有真正准备好分享.幸运的是,GitHub 上的其他人拥有与我几乎相同的代码(但更清晰),因此我建议将以下代码用于有效的 PHP WebSocket 解决方案:
https://github.com/ghedipunk/PHP-Websockets/blob/master/websockets.php

Edit: This edit is a couple of years after my original answer, and while I do still have a working solution, it's not really ready for sharing. Luckily, someone else on GitHub has almost identical code to mine (but much cleaner), so I recommend using the following code for a working PHP WebSocket solution:
https://github.com/ghedipunk/PHP-Websockets/blob/master/websockets.php

编辑 #2:虽然我仍然喜欢使用 PHP 来处理许多与服务器端相关的事情,但我必须承认,我最近对 ​​Node.js 非常感兴趣,并且主要原因是因为与 PHP(或任何其他服务器端语言)相比,它从一开始就更好地设计来处理 WebSocket.因此,我最近发现在您的服务器上设置 Apache/PHP 和 Node.js 并使用 Node.js 运行 WebSocket 服务器和使用 Apache/PHP 运行其他所有内容要容易得多.如果您处于无法为 WebSocket 安装/使用 Node.js 的共享托管环境中,您可以使用免费服务,例如 Heroku 设置一个 Node.js WebSocket 服务器并从您的服务器向它发出跨域请求.只要确保您这样做是为了将 WebSocket 服务器设置为能够处理跨域请求.

Edit #2: While I still enjoy using PHP for a lot of server-side related things, I have to admit that I've really warmed up to Node.js a lot recently, and the main reason is because it's better designed from the ground up to handle WebSocket than PHP (or any other server-side language). As such, I've found recently that it's a lot easier to set up both Apache/PHP and Node.js on your server and use Node.js for running the WebSocket server and Apache/PHP for everything else. And in the case where you're on a shared hosting environment in which you can't install/use Node.js for WebSocket, you can use a free service like Heroku to set up a Node.js WebSocket server and make cross-domain requests to it from your server. Just make sure if you do that to set your WebSocket server up to be able to handle cross-origin requests.

相关文章