通过附加信息在SplObjectStorage中查找对象

时间:2015-03-01 09:38:54

标签: php

我使用PHP Ratchet构建了一个聊天应用程序。

我将所有连接存储在SplObjectStorage中。

每个连接都有用户ID,我将通过此附加他:

   public function __construct() {
        $this->clients = new \SplObjectStorage;
    }

   public function onOpen(ConnectionInterface $conn)
    {
        // Store the new connection to send messages to later
        $querystring = $conn->WebSocket->request->getQuery();

        foreach ($querystring as $value)
        {
            if($key == "senderId")
                $senderId = $value;
        } 

        $this->clients->attach($conn, $senderId);

        echo "New connection! ({$conn->resourceId}) senderId({$senderId})\n";
    }

当邮件到达时,我希望以最快的方式获取与特定用户ID相关的$conn对象。 我可以像这样使用琐碎的foreach:

public function onMessage(ConnectionInterface $from, $msg) {
        $numRecv = count($this->clients) - 1;
        echo sprintf('Connection %d sending message "%s" to %d other connection%s' . "\n"
            , $from->resourceId, $msg, $numRecv, $numRecv == 1 ? '' : 's');

     foreach ($this->clients as $client)
     {
                if ($from->getInfo() !== $client->getInfo()) {

                   // do stuff
                }
    }

我想知道是否有更快的方法。也许使用这样的函数:

$conn = $this->clients->getClientWithInfo("WANTED-INFO");

想要的方法是在我的所有连接上取消循环,以便向特定用户发送消息。 我想获得与用户ID相关联的连接。

2 个答案:

答案 0 :(得分:3)

在我看来,只有一种解决方案可以让它发挥作用,就像你期望的那样=>扩展SplObjectStorage类。但是你有两个选择。

首先,你可以懒惰,并为找到对象的类添加一个getClientWithInfo方法:

class ConnectionStorageSimple extends SplObjectStorage
{
    public function getClientWithInfo($info)
    {
        $this->rewind();
        while ($this->valid()) {
            $object = $this->current(); // similar to current($s)
            $data = $this->getInfo();
            if ($info === $data) {
                $this->rewind();

                return $object;
            }
            $this->next();
        }

        return null;
    }
}

$conStorage = new ConnectionStorageSimple();

$con1 = new \stdClass();
$con1->id = 1;
$con2 = new \stdClass();
$con2->id = 2;


$conStorage->attach($con1, 1);
$conStorage->attach($con2, 2);

var_dump($conStorage->getClientWithInfo(1));

var_dump($conStorage->getClientWithInfo(2));

/**
 This will output something like that:
 class stdClass#2 (1) {
   public $id =>
   int(1)
 }
 class stdClass#3 (1) {
   public $id =>
   int(2)
 }
*/

另一个选择是,您基于父函数构建一个信息对象映射。这有点复杂:

<?php

class ConnectionStorage extends SplObjectStorage
{

    private $objInfoMapping = array();

    public function attach($object, $data = null)
    {
        if (null !== $data) {
            $this->objInfoMapping[$data] = $object;
        }
        parent::attach($object, $data);
    }

    public function detach($object)
    {
        $this->detach($object);
        parent::detach($object);
    }

    public function addAll($storage)
    {
        $this->addStorage($storage);

        parent::addAll($storage);
    }

    public function removeAll($storage)
    {
        $this->objInfoMapping = array();
        parent::removeAll($storage);
    }

    public function removeAllExcept($storage)
    {
        $this->objInfoMapping = array();
        $this->addStorage($storage);
        parent::removeAllExcept($storage);
    }

    public function unserialize($serialized)
    {
        parent::unserialize($serialized);
        $this->addStorage($this);
    }

    public function offsetUnset($object)
    {
        $this->detach($object);
        parent::offsetUnset($object);
    }

    protected function detachObject($obj)
    {
        $info = $this[$obj];
        if (array_key_exists($info, $this->objInfoMapping)) {
            unset($this->objInfoMapping[$info]);
        }
    }

    protected function addStorage(SplObjectStorage $storage)
    {
        $storage->rewind();
        while ($storage->valid()) {
            $object = $storage->current(); // similar to current($s)
            $data = $storage->getInfo();
            $this->objInfoMapping[$data] = $object;
            $storage->next();
        }
    }

    public function getClientWithInfo($info)
    {
        if (array_key_exists($info, $this->objInfoMapping)) {
            return $this->objInfoMapping[$info];
        }
    }

}

$conStorage = new ConnectionStorage();

$con1 = new \stdClass();
$con1->id = 1;
$con2 = new \stdClass();
$con2->id = 2;


$conStorage->attach($con1, 1);
$conStorage->attach($con2, 2);

var_dump($conStorage->getClientWithInfo(1));

var_dump($conStorage->getClientWithInfo(2));

/**
 This will also output something like that:
 class stdClass#2 (1) {
   public $id =>
   int(1)
 }
 class stdClass#3 (1) {
   public $id =>
   int(2)
 }
*/

两个类之间的主要区别在于,第二个示例在大数据集上的性能会更好,因为您不必遍历存储的所有对象。 而且因为你只存储对自己数组的对象引用,所以额外的内存消耗不应该那么大。

免责声明:这些课程只是为了说明可能性。第一个应该保存使用,但第二个应该进行更多测试

希望这有帮助。

答案 1 :(得分:0)

这就是我所做的,看到它更简单快速。

namespace mine;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;

class ws implements MessageComponentInterface {
    protected $clients;
    protected $clientids;

    public function __construct() {
        $this->clients = new \SplObjectStorage; 
        $this->clientids = array();
    }

    public function multicast($msg) {
        foreach ($this->clients as $client) $client->send($msg);
    }

    public function send_to($to,$msg) {
        if (array_key_exists($to, $this->clientids)) $this->clientids[$to]->send($msg);
    }

    public function onOpen(ConnectionInterface $conn) {
        $socket_name = "{$conn->resourceId}@{$conn->WebSocket->request->getHeader('X-Forwarded-For')}";
        $this->clients->attach($conn,$socket_name);
        $this->clientids[$socket_name] = $conn;
    }

    public function onMessage(ConnectionInterface $from, $msg) {

    }

    public function onClose(ConnectionInterface $conn) {
        unset($this->clientids[$this->clients[$conn]]);
        $this->clients->detach($conn);
    }

    public function onError(ConnectionInterface $conn, \Exception $e) {
        $conn->close();
    }
}

这增加了2个函数,一个用于多播,另一个用于通过socket_name向客户端发送消息,这是一个字符串ID(我选择了套接字ID和ip的组合来阻止可能的冲突)。

以便发送给客户:

$ws->send_to(socket_name,message);

显然$ ws是在initization创建的websocket:

$ws = new mine\ws();
$ws_server = new Ratchet\Server\IoServer( new Ratchet\Http\HttpServer( new Ratchet\WebSocket\WsServer( $ws ) ), $socket );