简体   繁体   English

如何在 PHP 中创建 websockets 服务器

[英]How to create websockets server in PHP

I am looking for a simple code to create a WebSocket server.我正在寻找一个简单的代码来创建一个 WebSocket 服务器。 I found phpwebsockets but it is outdated now and doesn't support the newest protocol.我找到了 phpwebsockets 但它现在已经过时并且不支持最新的协议。 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')."\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 中的 Concole 说

Firefox can't establish a connection to the server at ws://localhost:12345/. Firefox 无法与位于 ws://localhost:12345/ 的服务器建立连接。

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.我使用phpwebsockets代码作为如何构建服务器端代码的参考。 (You seem to already be doing this, and as you noted, the code doesn't actually work for a variety of reasons.) (您似乎已经这样做了,正如您所指出的,由于各种原因,该代码实际上并不能正常工作。)

  2. I used PHP.net to read the details about every socket function used in the phpwebsockets code.我使用 PHP.net 阅读有关 phpwebsockets 代码中使用的每个套接字函数的详细信息。 By doing this, I was finally able to understand how the whole system works conceptually.通过这样做,我终于能够从概念上理解整个系统是如何工作的。 This was a pretty big hurdle.这是一个相当大的障碍。

  3. I read the actual WebSocket draft .我阅读了实际的WebSocket 草案 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.我不得不多次阅读这件事,直到它最终开始陷入困境。在整个过程中,您可能不得不一次又一次地回到这份文档,因为它是一个具有正确、最新的权威资源有关 WebSocket API 的信息。

  4. I coded the proper handshake procedure based on the instructions in the draft in #3.我根据 #3 中草案中的说明编写了正确的握手程序。 This wasn't too bad.这还不错。

  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: (以下链接在这里对我帮助很大:( 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.请注意,此链接提供的代码存在许多问题,如果不进一步修改将无法正常工作。

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

    This link was really helpful.这个链接真的很有帮助。 I recommend consulting it while looking at the WebSocket draft.我建议在查看 WebSocket 草案时咨询它。 It'll help make more sense out of what the draft is saying.这将有助于使草案的内容更有意义。

  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: What is this data at the end of WebRTC candidate info?我几乎完成了这一点,但是我使用 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.我只需要添加一些额外的逻辑来处理连接的关闭,我就完成了。

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.好消息是,我现在非常了解 WebSocket,并且能够从头开始制作自己的客户端和服务器脚本,效果很好。 Hopefully the culmination of all that information will give you enough guidance and information to code your own WebSocket PHP script.希望所有这些信息的总结将为您提供足够的指导和信息来编写您自己的 WebSocket PHP 脚本。

Good luck!祝你好运!


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:幸运的是,GitHub 上的其他人拥有与我几乎相同的代码(但更清晰),因此我建议将以下代码用于有效的 PHP WebSocket 解决方案:
https://github.com/ghedipunk/PHP-Websockets/blob/master/websockets.php 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).编辑 #2 :虽然我仍然喜欢使用 PHP 来处理很多与服务器端相关的事情,但我不得不承认我最近真的很喜欢 Node.js,主要原因是因为它的设计更好比 PHP(或任何其他服务器端语言)更能处理 WebSocket。 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.因此,我最近发现在您的服务器上设置 Apache/PHP 和 Node.js 并使用 Node.js 运行 WebSocket 服务器和使用 Apache/PHP 运行其他所有内容要容易得多。 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.如果您处于无法为 WebSocket 安装/使用 Node.js 的共享托管环境中,您可以使用Heroku等免费服务来设置 Node.js WebSocket 服务器并进行跨域从您的服务器向它发出请求。 Just make sure if you do that to set your WebSocket server up to be able to handle cross-origin requests.只要确保您这样做是为了将 WebSocket 服务器设置为能够处理跨域请求。

As far as I'm aware Ratchet is the best PHP WebSocket solution available at the moment.据我所知, Ratchet是目前可用的最佳 PHP WebSocket 解决方案。 And since it's open source you can see how the author has built this WebSocket solution using PHP.由于它是开源的,您可以看到作者是如何使用 PHP 构建这个 WebSocket 解决方案的。

Why not to use sockets http://uk1.php.net/manual/en/book.sockets.php ? 为什么不使用套接字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 它有很好的文档记录(不仅在PHP上下文中)并且有很好的例子http://uk1.php.net/manual/en/sockets.examples.php

I've searched the minimal solution possible to do PHP + WebSockets during hours, until I found this article:我已经搜索了几个小时内执行 PHP + WebSockets 的最小解决方案,直到我找到了这篇文章:

Super simple PHP WebSocket example 超级简单的 PHP WebSocket 示例

It doesn't require any third-party library.它不需要任何第三方库。

Here is how to do it: create a index.html containing this:这是如何做到的:创建一个包含以下内容的index.html

<html>
<body>
    <div id="root"></div>
    <script>
        var host = 'ws://<<<IP_OF_YOUR_SERVER>>>:12345/websockets.php';
        var socket = new WebSocket(host);
        socket.onmessage = function(e) {
            document.getElementById('root').innerHTML = e.data;
        };
    </script>
</body>
</html>

and open it in the browser, just after you have launched php websockets.php in the command-line (yes, it will be an event loop, constantly running PHP script), with this websockets.php file.并在浏览器中打开它,就在您在命令行中启动php websockets.php之后(是的,它将是一个事件循环,不断运行 PHP 脚本),使用这个websockets.php文件。

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

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.我在你的鞋子里有一段时间,最后最终使用了 node.js,因为它可以做混合解决方案,比如将 Web 和套接字服务器合二为一。 So php backend can submit requests thru http to node web server and then broadcast it with websocket.所以php后端可以通过http向节点Web服务器提交请求,然后用websocket广播它。 Very efficiant way to go.非常有效的方法。

<?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" |从另一个终端运行: echo "hello woerld" | nc 127.0.0.1 8002数控 127.0.0.1 8002

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM