如何在PHP中创建WebSocket服务器


88

是否有任何教程或指南显示如何用PHP编写一个简单的Websockets服务器?我曾尝试在Google上寻找它,但没有找到很多。我找到了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 can't establish a connection to the server at ws://localhost:12345/.

编辑
由于这个问题引起了极大的兴趣,我决定为您提供我最终想到的东西。这是我的完整代码。


1
此页面列出,他们也有与当前phpwebsockets问题,包括他们的例子SRC代码所做的更改:net.tutsplus.com/tutorials/javascript-ajax/...
scrappedcola

1
一个有用的库,可用于WebSockets应用程序。包括客户端和PHP端。techzonemind.com/…–
Jithin Jose

1
我认为最好在C ++中实现它。
Michael Chourdakis

Answers:


113

我和您最近在同一条船上,这是我的工作:

1)我使用phpwebsockets代码作为如何构建服务器端代码的参考。(您似乎已经在执行此操作,并且正如您所指出的,由于多种原因,该代码实际上并不起作用。)

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

3)我阅读了实际的WebSocket草案(请对其进行网络搜索,因为每个帖子不能发布两个以上的链接)。在开始深入研究之前,我不得不阅读了很多次。在整个过程中,您可能不得不一次又一次地回到本文档,因为它是拥有正确,最新信息的权威资源。有关WebSocket API的信息。

4)我根据#3草案中的说明对正确的握手过程进行了编码。这还不错。

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

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

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

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

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

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进行其他操作要容易得多。并且在无法安装/使用Node.js for WebSocket的共享托管环境中,可以使用Heroku等免费服务来设置Node.js WebSocket服务器并进行跨域从您的服务器请求它。


谢谢,我会尽力做到的。您如何评价此PHP服务器的性能?
Dharman 2013年

@Dharman,很难说,因为我只能在本地主机上运行它。当然,它在那里可以正常工作,但我不知道在负载很重的真实服务器上。我想它会运行得很好,因为我的代码没有膨胀。
哈特利

1
我目前还没有,但是在不久的将来,我打算编写一个包含所有代码的整个过程的教程。完成后,我将发布链接。
哈特利

1
@HartleySan:你好!我会对查看您的代码非常感兴趣。您可以将其放在网上还是亲自发送给我?
2013年

是的,它将很快上线。对所有要求的人表示抱歉。我最近很忙。我很快会上。
哈特利圣


8

为什么不使用套接字http://uk1.php.net/manual/en/book.sockets.php?它有充分的文档记录(不仅在PHP上下文中),而且有很好的示例http://uk1.php.net/manual/en/sockets.examples.php


2
是的,您可以在纯PHP套接字和网页之间建立连续连接,我已经对其进行了多次测试。
WiMantis 2013年

@WiMantis:您好!您能在网上放置一个代码示例,还是可以选择亲自发送给我?
2013年

您可以将其与常规HTTP连接一起使用吗?我正在构建一个DDD框架,我想在此基础上创建一个包装类,并提供套接字功能,最好使用核心扩展在香草php中

1

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

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

让我知道是否有帮助。


1

我呆了一段时间,最终使用了node.js,因为它可以实现混合解决方案,例如将Web和套接字服务器集成在其中。因此,php后端可以通过http向节点Web服务器提交请求,然后使用websocket广播它。非常有效的方法。


所以我们必须使用http客户端来从php向节点服务器发出http请求,对不对?
Kiren Siva

Kiren Siva,对。卷曲或类似,然后节点通过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);
    }
}

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

从另一个终端运行:echo“ hello woerld” | nc 127.0.0.1 8002


1
那是什么意思?
Dharman

8
这是套接字,而不是WebSocket。有很大的不同。
克里斯,

@techexpander,根据每个问题,您必须从头开始解释而不是从前解释。这是行不通的。
杰明(Jaymin),
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.