Javascript 如何在 PHP 中创建 websockets 服务器

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14512182/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-24 17:02:55  来源:igfitidea点击:

How to create websockets server in PHP

phpjavascriptwebsocket

提问by Dharman

Are there any tutorials or guides showing how to write myself a simple websockets server in PHP? I have tried looking for it on google but I didn't find many. I found phpwebsockets but it is outdated now and doesn't support newest protocol. I tried updating it myself but it doesn't seem to work.

是否有任何教程或指南展示了如何用 PHP 为自己编写一个简单的 websockets 服务器?我试过在谷歌上寻找它,但我没有找到很多。我找到了 phpwebsockets 但它现在已经过时并且不支持最新的协议。我尝试自己更新它,但它似乎不起作用。

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

?>

and the client:

和客户:

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

If there is anything wrong in my code can you help me fix it? Concole in firefox says Firefox can't establish a connection to the server at ws://localhost:12345/.

如果我的代码有问题,你能帮我修复吗?Firefox 中的 Concole 说Firefox can't establish a connection to the server at ws://localhost:12345/.

EDIT
Since there is much interest in this question I decided to provide you with what I finally came up with. Here is my full code.

编辑
由于对这个问题很感兴趣,我决定向您提供我最终想到的内容。这是我的完整代码。

回答by HartleySan

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

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

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.)

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

2) 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.

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

3) I read the actual WebSocket draft (please do a web search for it, as I can't post more than two links per post). 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) 我阅读了实际的 WebSocket 草案(请在网上搜索它,因为我不能在每个帖子中发布两个以上的链接)。我不得不多次阅读这件事,直到它最终开始陷入困境。在整个过程中,您可能不得不一次又一次地回到这份文档,因为它是一个具有正确、最新的权威资源有关 WebSocket API 的信息。

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

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

5) 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: http://srchea.com/blog/2011/12/build-a-real-time-application-using-html5-websockets/

5) 握手后,我不断收到从客户端发送到服务器的一堆乱码,直到我意识到数据已编码并且必须取消屏蔽后,我才弄清楚原因。以下链接在这里对我帮助很大:http: //srchea.com/blog/2011/12/build-a-real-time-application-using-html5-websockets/

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

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

6) 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?

6) 然后我遇到了以下 SO 线程,它清楚地解释了如何正确编码和解码来回发送的消息:如何在服务器端发送和接收 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 草案时咨询它。这将有助于使草案的内容更有意义。

7) 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. To reference the question and answer, please do a web search for "SO What is this data at the end of WebRTC candidate info?" (without the quotation marks).

7) 到此我差不多完成了,但是我使用 WebSocket 制作的 WebRTC 应用程序遇到了一些问题,所以我最终在 SO 上提出了我自己的问题,我最终解决了这个问题。要参考问题和答案,请在网络上搜索“WebRTC 候选信息末尾的数据是什么?” (不带引号)。

8) 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.

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

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. Good luck!

这个过程总共花了我大约两个星期。好消息是,我现在非常了解 WebSocket,并且能够从头开始制作自己的客户端和服务器脚本,效果很好。希望所有这些信息的最终结果将为您提供足够的指导和信息来编写您自己的 WebSocket 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

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

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.

编辑 #2:虽然我仍然喜欢使用 PHP 来处理很多与服务器端相关的事情,但我不得不承认我最近真的很喜欢 Node.js,主要原因是它的设计更好比 PHP(或任何其他服务器端语言)更能处理 WebSocket。因此,我最近发现在您的服务器上设置 Apache/PHP 和 Node.js 并使用 Node.js 运行 WebSocket 服务器和使用 Apache/PHP 运行其他所有内容要容易得多。如果您处于无法为 WebSocket 安装/使用 Node.js 的共享托管环境中,您可以使用 Heroku 等免费服务来设置 Node.js WebSocket 服务器并进行跨域从您的服务器向它发出请求。

回答by leggetter

As far as I'm aware Ratchetis the best PHP WebSocket solution available at the moment. And since it's open sourceyou can see how the author has built this WebSocket solution using PHP.

据我所知,Ratchet是目前可用的最佳 PHP WebSocket 解决方案。由于它是开源的,您可以看到作者是如何使用 PHP 构建这个 WebSocket 解决方案的。

回答by Lukasz Kujawa

Why not to use sockets http://uk1.php.net/manual/en/book.sockets.php? It's well documented (not only in PHP context) and has good examples http://uk1.php.net/manual/en/sockets.examples.php

为什么不使用套接字http://uk1.php.net/manual/en/book.sockets.php?它有据可查(不仅在 PHP 上下文中)并且有很好的例子http://uk1.php.net/manual/en/sockets.examples.php

回答by user2288650

Need to convert the the key from hex to dec before base64_encoding and then send it for handshake.

需要在 base64_encoding 之前将密钥从十六进制转换为十进制,然后将其发送以进行握手。

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

Let me know if this helps.

如果这有帮助,请告诉我。

回答by M.Z.

I was in your shoes for a while and finally ended up using node.js, because it can do hybrid solutions like having web and socket server in one. So php backend can submit requests thru http to node web server and then broadcast it with websocket. Very efficiant way to go.

我在你的鞋子里有一段时间,最后最终使用了 node.js,因为它可以做混合解决方案,比如将 Web 和套接字服务器合二为一。所以php后端可以通过http向节点Web服务器提交请求,然后用websocket广播它。非常有效的方法。

回答by Dipak Yadav

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

from one terminal run : php server.php

从一个终端运行:php server.php

from another terminal run: echo "hello woerld" | nc 127.0.0.1 8002

从另一个终端运行: echo "hello woerld" | 数控 127.0.0.1 8002