如何在 PHP 中创建网络套接字服务器

我正在寻找一个简单的代码来创建一个 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 无法在 ws://localhost: 12345/建立到服务器的连接。

260635 次浏览

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

  2. 我使用 PHP.net 来阅读在 phpwebsockets 代码中使用的 每个插座功能的详细信息。通过这样做,我终于能够理解整个系统在概念上是如何工作的。这是个很大的障碍。

  3. 我读了真正的 WebSocket 草稿。我不得不读了很多遍才终于明白。您可能必须在整个过程中反复查看这个文档,因为它是有关 WebSocket API 的正确、最新信息的唯一权威资源。

  4. 我根据草案3中的说明编写了正确的握手程序,这还不算太糟。

  5. 握手之后,我不断收到客户发送到服务器的一堆混乱的文本,我不知道为什么,直到我意识到数据是加密的,必须揭开。下面的链接帮了我很多: (原创链接中断) 存档拷贝

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

  6. 然后,我遇到了下面的 SO 线程,它清楚地解释了如何正确地编码和解码来回发送的消息: < a href = “ https://stackoverflow. com/questions/8125507/How-can-i-send-and-access-WebSocket-message-on-the-server-side”> 如何在服务器端发送和接收 WebSocket 消息?

    这个链接真的很有用。我建议在查看 WebSocket 草案时参考它。这将有助于更好地理解草案的内容。

  7. 在这一点上,我几乎完成了,但有一些问题与 WebRTC 应用程序,我正在使用 WebSocket,所以我最终问自己的问题,关于 SO,我最终解决了: < a href = “ https://stackoverflow.com/q/14425382/8112776”> 这是什么数据在 WebRTC 候选人信息的结尾?

  8. 此时此刻,我基本上都搞定了。我只需要添加一些额外的逻辑来处理连接的关闭,然后就完成了。

这个过程总共花了我两周的时间。好消息是,我现在非常了解 WebSocket,我能够从头开始创建自己的客户端和服务器脚本,它们工作得非常好。 Hopefully the culmination of all that information will give you enough guidance and information to code your own WebSocket PHP script.

祝你好运!


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,你可以使用像 你好这样的免费服务来设置一个 Node.js WebSocket 服务器,并从你的服务器向它发出跨域请求。只要确保这样做是为了将 WebSocket 服务器设置为能够处理跨源请求。

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

需要先将密钥从十六进制转换为十二进制,然后再将其发送给 base64 _ coding 进行握手。

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

我在你的位置上呆了一段时间,最后终于用上了 node.js,因为它可以做一些混合的解决方案,比如将 web 和 socket 服务器集成在一起。所以 php 后端可以通过 http 提交请求到节点 Web 服务器,然后通过 websocket 广播请求。非常有效率的死法。

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

超级简单的 PHP WebSocket 示例

它不需要任何第三方库。

下面是如何做到这一点: 创建一个包含以下内容的 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文件。