Ratchet Websockets - 检测已断开连接的客户端

3
我正在设计一个在ratchet websockets中尽可能响应的聊天室。它知道用户何时离开页面等一切情况。但是,如果用户/客户端例如与服务器失去连接,问题在于客户端无法让服务器知道它已断开连接,因为它已经断开连接并且无法向服务器发送消息。那么我该如何跟踪聊天客户端何时失去其互联网连接并不再在线呢?
我可以想到两种可能的解决方案:
1. 服务器每隔15分钟至半个小时轮询客户端以检查谁在线。未响应的客户端将被断开连接。这是否可以在php中进行而不会中断其他所有任务?如果可以的话,应该放置在哪里?我看到了关于LoopInterface的addPeriodicTimer(),但我不确定它是否能够胜任此工作或者该函数应放在我的代码的哪个位置。此外,它是否调用sleep()函数,因为那样做就不好了。我仍希望在此函数定时器上发生其他后台任务(如果可能在php中)。
2. 在php中使用onClose()方法。这个方法能否在任何情况下检测到用户真正的断开连接?如果可以,当此事件触发时,我如何找出哪个用户已断开连接?它只传递ConnectionInterface而没有消息。
对不起,我还是ratchet库的新手,仍在努力解决这个问题。
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();
?>

应用程序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();


};

聊天室的 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()方法不会触发。

是否有一种方法可以仍然使用第一个解决方案?

1个回答

0

检测断开连接客户端的最佳解决方案是基于事件的,完全不需要轮询客户端。这种方法将是您的第二个解决方案,并且也很好地模拟了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进行索引,您应该能够使用此实现来确定所有源自客户端的消息的身份,在它们发送初始注册消息后。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接