【问题标题】:How to create websockets server in PHP如何在 PHP 中创建 websockets 服务器
【发布时间】:2013-01-08 20:22:16
【问题描述】:

我正在寻找创建 WebSocket 服务器的简单代码。我找到了 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;
}

?>

和客户:

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 说

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

【问题讨论】:

标签: php javascript websocket


【解决方案1】:

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

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

  2. 我使用 PHP.net 阅读了有关 phpwebsockets 代码中使用的every socket function 的详细信息。通过这样做,我终于能够从概念上理解整个系统是如何工作的。这是一个很大的障碍。

  3. 我阅读了实际的WebSocket draft。在它最终开始深入理解之前,我不得不读了很多遍。在整个过程中,你可能不得不一次又一次地回到这个文档,因为它是一个具有正确、最新的权威资源有关 WebSocket API 的信息。

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

  5. 在握手之后,我不断收到从客户端发送到服务器的一堆乱码文本,直到我意识到数据已编码并且必须取消屏蔽,我才弄清楚为什么。以下链接在这里帮助了我很多:(original link brokenArchived copy

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

  6. 然后我遇到了以下 SO 线程,它清楚地解释了如何正确编码和解码来回发送的消息:How can I send and receive WebSocket messages on the server side?

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

  7. 此时我几乎完成了,但是我使用 WebSocket 制作的 WebRTC 应用程序出现了一些问题,所以我最终在 SO 上提出了自己的问题,我最终解决了这个问题:What is this data at the end of WebRTC candidate info?

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

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

祝你好运!


编辑:这个编辑是在我最初的答案之后几年进行的,虽然我仍然有一个可行的解决方案,但它还没有真正准备好分享。幸运的是,GitHub 上的其他人的代码与我的几乎相同(但更简洁),因此我建议使用以下代码作为有效的 PHP WebSocket 解决方案:
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 服务器设置为能够处理跨域请求。

【讨论】:

  • 谢谢,我会按照你的方式尝试。您如何评价这个 PHP 服务器的性能?
  • @Dharman,很难说,因为我只能在本地主机上运行它。当然它在那里工作得很好,但在一个负载很重的真实服务器上,我不知道。我想它会运行得相当好,因为我的代码没有臃肿。
  • 我暂时没有,但在不久的将来,我打算用所有代码写一个关于整个过程的教程。完成后,我将发布链接。
  • @HartleySan:你好!我会对查看您的代码非常感兴趣。你能把它放在网上,还是亲自发给我?
  • 是的,很快就会上线。向所有要求它的人道歉。我最近太忙了。我会尽快上手的。
【解决方案2】:

据我所知,Ratchet 是目前可用的最好的 PHP WebSocket 解决方案。因为它是open source,所以你可以看到作者是如何使用 PHP 构建这个 WebSocket 解决方案的。

【讨论】:

【解决方案3】:
<?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);
    }
}

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

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

【讨论】:

  • 这是套接字,而不是 WebSocket。差别很大。
  • @techexpander,根据问题,您必须从头开始解释,而不是前任。这是行不通的。
【解决方案4】:

需要在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";

【讨论】:

    【解决方案5】:

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

    【讨论】:

    • 所以我们必须使用http客户端从php向节点服务器发出http请求吗?
    • Kiren Siva,正确的。卷曲或类似,然后节点通过 websocket 广播消息
    【解决方案6】:

    我已经在几个小时内搜索了可能使用 PHP + WebSockets 的最小解决方案,直到找到这篇文章:

    Super simple PHP WebSocket example

    它不需要任何第三方库。

    操作方法如下:创建一个包含此内容的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>
    

    并在浏览器中打开它,就在你在命令行中启动 php websockets.php 之后(是的,这将是一个事件循环,不断运行 PHP 脚本),带有这个 websockets.php 文件。

    【讨论】:

    • 这很好地介绍了 websockets 如何与 PHP 一起工作,但在现实世界中实现可能并不值得,因为它使用while(true) .. sleep(1)(称为“长轮询”,它不好)。另一方面,Ratchet 在其核心中使用ZeroMQ,它可以正确处理它。
    • 这是一个很好的例子。它很好而简单地展示了如何监听连接并将数据发送到网络浏览器。我希望还会添加示例如何从 webbrowser 接收数据到服务器。当我在handahek 数据似乎不是ASCII 之后使用socket_read 时。
    • @evilReiko 不不,这根本不是不是长轮询; while / sleep(1) 只是让服务器每秒向客户端发送示例消息的示例,就像 WebSocket 的演示一样。在实际应用程序中,您根本不需要服务器代码上的 sleep(1)
    • @Basj 哦,你是对的!
    猜你喜欢
    • 2012-08-14
    • 2014-10-09
    • 2023-03-22
    • 2015-10-29
    • 2016-11-16
    • 1970-01-01
    • 1970-01-01
    • 2017-02-13
    相关资源
    最近更新 更多