棘轮Websockets - 检测断开连接的客户端

时间:2016-07-24 12:24:37

标签: javascript php web-services websocket ratchet

我在棘轮腹板中设计一个聊天室尽可能地响应。 它知道用户何时离开页面,以及类似的一切。 但是,如果用户/客户端例如失去与服务器的连接,则问题是客户端无法让服务器知道它已断开连接,因为它已经断开连接并且无法向服务器发送消息。那么如何跟踪聊天客户端何时丢失了互联网连接并且不再在线?

我能想到的两种可能的解决方案:

  • 服务器每隔15分钟到半小时轮询一次客户端,以查看谁在线。没有响应的客户端会断开连接。这可能不会打断php中的其他所有内容吗?如果是这样的话?我在哪里放代码?我从addPeriodicTimer()看到了关于LoopInterface的内容,但我不确定这是否可以完成工作或函数适合我的代码。 它也会调用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()方法在互联网连接终止时不会触发。

有没有办法可以解决第一个问题?

2 个答案:

答案 0 :(得分:0)

这种检测断开连接的客户端的最佳解决方案是基于事件,而根本不会轮询客户端。这种方法将是您的第二个解决方案,并且还可以很好地模拟WebSocket消息传递的异步性质。

然而,你可以说某些顽皮的客户在某些情况下可能不会通知套接字服务器他们断开连接并让它“悬挂”,可以这么说。在这种情况下,我建议尝试在Socket服务器本身内实现轮询触发器,而是通过触发的单独的服务器端客户端启动轮询通过cron或其他任务调度程序,并指示套接字服务器发起轮询所有连接客户端的请求。

有关构建服务器端客户端的更多信息,请参阅this question of mine,我也能找到一些解决方案。

为了确定发送了断开连接消息,我建议不要在SplObjectStorage实现中使用Ratchet\MessageComponentInterface,而是包装一个简单的数组在另一个类里面,所以像这样:

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
        ;
    }
}

在用户首次连接后的某个时刻,您的客户端应该向服务器发送套接字消息,并指示服务器现在使用其他信息注册客户端的身份(在您尝试识别{{的情况下) 1}}和uname属性)。通过索引连接ID,您应该能够使用此实现来在客户端发送初始注册消息后对来自客户端的所有消息的身份进行划分。

答案 1 :(得分:-3)

免责声明:我是Ably的联合创始人,只是更好的实时

这正是实时平台为您解决的问题类型。首先,您可以使用presence来检测用户何时在场。使用Ably,如果用户突然断开连接,您将在15秒内收到通知,如果明确断开连接(由客户端),您将立即收到通知。

此外,Ably解决了您需要考虑的其他一些问题,例如如何处理message continuity when clients become disconnected for short periods

我很感激我的回答并没有特别回答你关于如何使用Ratchet做到这一点的问题,但实时平台已经建成,可以大规模可靠地解决这些问题,所以我建议你考虑利用它,如果可以。

相关问题