我正在棘轮网络套接字中设计一个聊天室,以尽可能地响应。它知道用户何时离开页面,以及诸如此类的事情。但是,例如,如果用户/客户端失去了与服务器的连接,问题是客户端不能让服务器知道它已经断开了连接,因为它已经断了连接,并且不能向服务器发送消息。那么,当聊天客户端失去互联网连接并且不再在线时,我该如何跟踪?
我能想到的两种可能的解决方案:
-
服务器每15分钟到半小时轮询客户端一次,以查看谁在线。没有响应的客户端将断开连接。这是否可以在不中断php中其他一切的情况下完成?如果是,如何?我该把代码放在哪里?我从
LoopInterface
中看到了一些关于addPeriodicTimer()
的内容,但我不确定这是否能完成任务,也不确定函数在我的代码中的位置。它也会调用sleep()
函数,因为那样做不好。我仍然希望其他任务发生在后台,而这个功能是在计时器上(如果可能的话,在php中) -
php中的
onClose()
方法这能检测到用户在任何情况下何时真正断开连接吗?如果是这样的话,当这个事件触发时,我如何找出哪个用户断开了连接?它只传递ConnectionInterface而不传递消息。
对不起,我还是棘轮库的新手,仍在努力解决如何完成这项任务。
我的server.php代码:
<?php
require($_SERVER['DOCUMENT_ROOT'].'/var/www/html/vendor/autoload.php');
use Ratchet'Server'IoServer;
use Ratchet'Http'HttpServer;
use Ratchet'WebSocket'WsServer;
$server = IoServer::factory(new HttpServer(new WsServer(new Chat)), 8080);
$server->run();
?>
app.js 的代码
// JavaScript Document
var chat = document.getElementById("chatwindow");
var msg = document.getElementById("messagebox");
var refInterval = 0;
var timeup = false;
var awaytimer;
var socket = new WebSocket("ws://52.39.48.172:8080");
var openn = false;
function addMessage(msg){
"use strict";
chat.innerHTML += "<p>" + msg + "</p>";
}
msg.addEventListener('keypress', function(evt){
"use strict";
if(evt.charCode != 13)
return;
evt.preventDefault();
if(msg.value == "" || !openn)
return;
socket.send(JSON.stringify({
msg: msg.value,
uname: nme,
uid: id,
tag: "[msgsend]"
}));
msg.value = "";
});
socket.onopen = function(evt) {
openn = true;
socket.send(JSON.stringify({
uname: nme,
uid: id,
tag: "[connected]"
}));
};
socket.onmessage = function(evt) {
var data = JSON.parse(evt.data);
if(data.tag == "[connected]")
{
addMessage(data.uname + " has connected...");
}
else if(data.tag == "[bye]")
{
addMessage(data.uname + " has left the room...");
if(data.uname == nme)
socket.close();
}
else if(data.tag == "[msgsend]")
{
addMessage(data.uname + ": " + data.msg);
}
};
window.onfocus = refPage;
function refPage()
{
if(timeup == true)
{
if(refInterval == 1)
{
refInterval = 0;
location.reload();
}
}
else
{
clearTimeout(awaytimer);
}
timeup = false;
}
window.onblur = timelyExit;
function timelyExit()
{
refInterval = 1;
// change this to trigger some kind of inactivity timer
awaytimer = setTimeout(function(){socket.send(JSON.stringify({
uname: nme,
uid: id,
tag: "[bye]"
})); timeup=true; }, 900000);
}
window.onoffline = window.onunload = window.onbeforeunload = confirmExit;
function confirmExit()
{
socket.send(JSON.stringify({
uname: nme,
uid: id,
tag: "[bye]"
}));
socket.close();
}
socket.onclose = function() {
openn = false;
//cant send server this message because already closed.
/*
socket.send(JSON.stringify({
uname: nme,
uid: id,
tag: "[bye]"
}));
*/
socket.close();
};
chat.php 的代码
<?php
error_reporting(E_ALL ^ E_NOTICE);
session_id($_GET['sessid']);
if(!session_id)
session_start();
$userid = $_SESSION["userid"];
$username = $_SESSION["username"];
$isadmin = $_SESSION["isadmin"];
use Ratchet'MessageComponentInterface;
use Ratchet'ConnectionInterface;
class Chat implements MessageComponentInterface
{
protected $clients;
public function __construct()
{
$this->clients = new 'SplObjectStorage;
}
public function onOpen(ConnectionInterface $conn)
{
$this->clients->attach($conn);
}
public function onClose(ConnectionInterface $conn)
{
$this->clients->detach($conn);
}
public function onMessage(ConnectionInterface $conn, $msg)
{
$msgjson = json_decode($msg);
$tag = $msgjson->tag;
if($tag == "[msgsend]")
{
foreach($this->clients as $client)
{
$client->send($msg);
}
}
else if($tag == "[bye]")
{
foreach($this->clients as $client)
{
$client->send($msg);
}
onClose($conn);
}
else if($tag == "[connected]")
{
//store client information
//send out messages
foreach($this->clients as $client)
{
$client->send($msg);
}
}
}
public function onError(ConnectionInterface $conn, Exception $e)
{
echo "Error: " . $e->getMessage();
$conn -> close();
}
}
?>
编辑:刚刚测试并确认onClose()方法在终止互联网连接时不会启动。
对于第一个解决方案,我还有办法吗?
检测断开连接的客户端的最佳解决方案是基于事件的,并且根本不轮询客户端。这种方法将是您的第二个解决方案,并且还可以很好地围绕WebSocket消息传递的异步特性进行建模。
然而,正如你所说,在某些情况下,一些顽皮的客户端可能不会通知套接字服务器他们的断开连接,并让它"挂起"。在这种情况下,我建议不要尝试在套接字服务器本身中实现轮询触发器,而是通过单独的服务器端客户端启动轮询,该客户端通过cron或其他任务调度程序触发,并指示套接字服务器启动轮询所有连接客户端的请求。
有关构建服务器端客户端的更多信息,请参阅我的这个问题,我也能够找到一些解决方案。
为了确定是谁发送了断开连接消息,我建议不要在现有的Ratchet'MessageComponentInterface
实现中只使用SplObjectStorage
,而是将一个简单的数组封装在另一个类中,这样:
class MyClientList
{
protected $clients = [];
public function addClient(Connection $conn)
{
$this->clients[$conn->resourceId] = [
'connection' => $conn,
];
return $this;
}
public function removeClient(Connection $conn)
{
if(isset($this->clients[$conn->resourceId])) {
unset($this->clients[$conn->resourceId]);
}
return $this;
}
public function registerClient(ConnectionInterface $conn, array $userData)
{
if(isset($this->clients[$conn->resourceId])) {
$this->clients[$conn->resourceId] = array_merge(
$this->clients[$conn->resourceId],
$userData
);
}
return $this;
}
public function getClientData(ConnectionInterface $conn)
{
return isset($this->clients[$conn->resourceId]) ?
$this->clients[$conn->resourceId] :
null
;
}
}
在用户首次连接后不久的某个时刻,您的客户端应该向服务器发送一条套接字消息,并指示服务器现在使用附加信息注册客户端的身份(在您的情况下,您正试图标识uname
和uid
属性)。通过根据连接id进行索引,您应该能够使用此实现来推测在客户端发送初始注册消息后源自客户端的所有消息的身份。