2016-07-24 132 views
2

我正在設計棘輪websockets中的聊天室,以儘可能快地響應。 它知道用戶什麼時候離開頁面,以及類似的東西。 但是,如果一個用戶/客戶端失去了與服務器的連接,問題是客戶端不能讓服務器知道它已經斷開連接,因爲它已經斷開連接並且不能向服務器發送消息。那麼,如何跟蹤聊天客戶端何時失去互聯網連接並且不再上網?棘輪Websockets - 檢測斷開連接的客戶端

兩個可能的解決方案,我能想到的:

  • 服務器輪詢客戶端每15分鐘到半小時檢查,看看誰在線上。沒有迴應的客戶會斷開連接。這可能不會中斷一切在PHP中發生?如果是的話如何?我在哪裏放置代碼?我從LoopInterface看到addPeriodicTimer()的一些信息,但我不確定這是否能完成這項工作,或者函數適合我的代碼。 它也叫它sleep()函數,因爲那樣做不好。我還是想其他任務在後臺發生的事情,而這個功能是一個計時器(如果可能的話在PHP)

  • onClose()方法在PHP 這可檢測出用戶在任何環境真的斷開連接?如果是這樣,當這個事件發生時,我怎麼知道哪個用戶被斷開了?它只傳遞一個ConnectionInterface並且沒有消息。

對不起,即時通訊仍然是新的棘輪庫,仍然試圖找出如何實現這一任務。

我的server.php代碼:爲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 


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(); 
?> 

代碼

<?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( )方法在互聯網連接終止時不會觸發。

有沒有辦法可以解決第一個問題?

回答

-3

免責聲明:我是幹練的創始人之一,只是更好的實時

這是一份實時的平臺爲你解決問題的確切類型。首先,您可以使用presence來檢測用戶是否在場。使用Ably,如果用戶突然斷開連接,您將在15秒內收到通知,如果明確斷開連接(由客戶端),您將立即得到通知。

另外,Ably解決了一些其他問題,您需要考慮如何處理message continuity when clients become disconnected for short periods

我很欣賞我的答案並不是專門回答你如何用Ratchet做到這一點的問題,但是實時平臺的構建是爲了在規模和可靠性上解決這些問題,所以我建議你考慮如果可以的話。

+1

聽起來不錯,但我目前不想爲API支付費用,也不希望每天僅限於100個連接。 – emjay

+0

aww cmon。這裏沒有一個人使用Ratchet API來告訴我如何定期向所有客戶端發送消息? – emjay

0

檢測斷開的客戶端本最好的解決辦法是基於事件的不會輪詢客戶都沒有。這種方法將成爲您的第二個解決方案,並且還可以很好地模擬WebSocket消息傳遞的異步特性。

,你說,在某些情況下,有些調皮的客戶端可能無法通知其斷開插座服務器並使其保持「掛」,可以這麼說然而可能。在這種情況下,我會建議嘗試socket服務器內實現輪詢觸發本身,而是通過單獨的服務器端客戶是通過cron或者其他任務調度觸發並指示插座發起投票服務器發起請求以輪詢所有連接的客戶端。

有關構建服務器端客戶端的更多信息,請參閱this question of mine,我也能找到一些解決方案。


爲了確定發送斷開消息,我會建議使用只是SplObjectStorageRatchet\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 
     ; 
    } 
} 

在某些時候,用戶首次連接後不久,您的客戶端應該發送一個插座信息到服務器,並通知服務器現在就註冊附加信息的客戶端的身份(在你的情況你attemptin g以識別unameuid屬性)。通過對連接ID進行索引,您應該能夠使用此實現來在發送初始註冊消息後對來自客戶端的所有消息進行標識。

相關問題