dongxi7609 2014-08-12 20:27 采纳率: 0%
浏览 35

棘轮聊天应用程序 - 应用程序运行一段时间后立即关闭连接

We're using Laravel 4 together with Ratchet to create a chat application. Everything runs normally for about 14-20 hours. After a period of time the chat app stops to run. The connection gets established from the client to the server but right after that the server closes the connection.

No errors get reported in our log files and the fact that we haven't been able to replicate the problem in our development environment doesn't help.

Restarting the chat application on the server fixes the issue for another 14-20 hours.

Supervisor configuration:

[program:chat]
command         = bash -c "ulimit -n 10000 && /usr/bin/php /var/www/artisan setup:chat --env=staging"
process_name    = chat
numprocs        = 1
autostart       = true
autorestart     = true
user            = root
stdout_logfile      = /var/www/app/storage/logs/chat_info.log
stdout_logfile_maxbytes = 1MB
stderr_logfile      = /var/www/app/storage/logs/chat_error.log
stderr_logfile_maxbytes = 1MB

SetupChatCommand.php (Laravel Setup Chat Command):

<?php

use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;

use Application\Chat\Server;

class SetupChatCommand extends Command {

    /**
     * The console command name.
     *
     * @var string
     */

    protected $name = 'setup:chat';

    /**
     * The console command description.
     *
     * @var string
     */

    protected $description = 'Setup the chat server';

    /**
     * Create a new command instance.
     *
     * @return void
     */

    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */

    public function fire()
    {
        $server = new Server();
        $server->run();
    }

}

Server.php:

<?php namespace Application\Chat;

use React\EventLoop\Factory;
use React\Socket\Server as Reactor;
use Ratchet\Server\IoServer;
use Ratchet\Server\FlashPolicy;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;

class Server {

    const CHAT_PORT = 7778;
    const FLASH_PORT = 843;

    public function __construct()
    {
        $this->_setup();
    }

    private function _setup()
    {
        $loop = Factory::create();

        $web_socket = new Reactor($loop);
        $web_socket->listen(self::CHAT_PORT, '0.0.0.0');

        $this->_server = new IoServer(
            new HttpServer(
                new WsServer(
                    new Service()
                )
            )
          , $web_socket
          , $loop
        );

        $flash_policy = new FlashPolicy();
        $flash_policy->addAllowedAccess('*', self::CHAT_PORT);

        $flash_socket = new Reactor($loop);
        $flash_socket->listen(self::FLASH_PORT, '0.0.0.0');

        $flash_server = new IoServer($flash_policy, $flash_socket);
    }

    public function run()
    {
        $this->_server->run();
    }

}

Service.php:

<?php namespace Application\Chat;

use SplObjectStorage;

use Ratchet\ConnectionInterface;
use Ratchet\MessageComponentInterface;

class Service implements MessageComponentInterface {

    public function __construct()
    {
        $this->_setupClients();
    }

    /**
     * Clients
     */

    private $_clients = null;

    private function _setupClients()
    {
        $this->_clients = new SplObjectStorage();
    }

    public function getClientByConnection($connection)
    {
        foreach ($this->_clients as $client)
        {
            if($client->getConnection() === $connection)
            {
                return $client;
            }
        }

        return null;
    }

    public function getClientsByRoom($room)
    {
        $clients = array();

        foreach ($this->_clients as $client)
        {
            if($client->getRoom()->id === $room->id)
            {
                array_push($clients, $client);
            }
        }

        return $clients;
    }

    /**
     * Input
     */

    private function _handleInput($client, $input)
    {
        if(empty($input) || empty($input['type']) || empty($input['content'])) return;

        switch ($input['type'])
        {
            case 'session.set':
                $this->_handleInputSetSession($client, $input['content']);
                break;

            case 'room.set':
                $this->_handleInputSetRoom($client, $input['content']);
                break;

            case 'message':
                $this->_handleInputMessage($client, $input['content']);
                break;
        }
    }

    private function _handleInputSetSession($client, $input)
    {
        $client->setSession($input);
    }

    private function _handleInputSetRoom($client, $input)
    {
        $client->setRoom($input);
    }

    private function _handleInputMessage($client, $input)
    {
        $message = $client->message($input);

        if($client->hasRoom() && $message)
        {
            $clients = $this->getClientsByRoom($client->getRoom());

            foreach ($clients as $other)
            {
                if($other !== $client)
                {
                    $other->getConnection()->send(json_encode(array(
                        'type'    => 'message.get'
                      , 'content' => $message->toArray()
                    )));
                }
            }
        }
    }

    /**
     * Callbacks
     */

    public function onOpen(ConnectionInterface $connection)
    {
        $client = new Client($connection);
        $this->_clients->attach($client);
    }

    public function onMessage(ConnectionInterface $connection, $input)
    {
        $client = $this->getClientByConnection($connection);
        $input    = json_decode($input, true);

        $this->_handleInput($client, $input);
    }

    public function onClose(ConnectionInterface $connection)
    {
        $client = $this->getClientByConnection($connection);

        if($client)
        {
            $this->_clients->detach($client);
        }
    }

    public function onError(ConnectionInterface $connection, \Exception $e)
    {
        $client = $this->getClientByConnection($connection);

        if($client)
        {
            $client->getConnection()->close();
        }
    }

}

Client.php:

<?php namespace Application\Chat;

use App;
use Config;

use Application\Models\ChatRoom;
use Application\Models\ChatRoomUser;
use Application\Models\ChatRoomMessage;
use Application\Models\File;

class Client {

    /**
     * Constructor & Destructor
     */

    public function __construct($connection = null)
    {
        if($connection)
        {
            $this->setConnection($connection);
        }
    }

    public function __destruct()
    {
        if($this->hasRoom())
        {
            $this->takenUserOfflineForRoomId($this->getRoom()->id);
        }
    }

    /**
     * Connection
     */

    protected $_connection = null;

    public function getConnection()
    {
        return $this->_connection;
    }

    public function setConnection($connection)
    {
        $this->_connection = $connection;
    }

    /**
     * Session
     */

    public function setSession($input)
    {
        Config::set('session.driver', 'database');

        $session_id = $input; 

        $session = App::make('session');
        $session->setDefaultDriver(Config::get('session.driver'));

        $session->driver()->setId($session_id);
        $session->driver()->start();

        $cartalyst_session = $session->driver()->get(
            Config::get('cartalyst/sentry::cookie.key')
        );

        if(!empty($cartalyst_session))
        {
            $this->setUserId($cartalyst_session[0]);
        }
        else
        {
            throw new \Exception('User not recognized.');
        }
    }

    /**
     * User id
     */

    private $_user_id = null;

    private function setUserId($id)
    {
        $this->_user_id = $id;
    }

    public function getUserId()
    {
        return $this->_user_id;
    }

    /**
     * Room
     */

    private $_room = null;

    public function getRoom()
    {
        return $this->_room;
    }

    public function setRoom($input)
    {
        if(empty($input) || empty($input['id']))
        {
            throw new \Exception('Invalid chat room.');
        }

        $this->_room = ChatRoom::find($input['id']);

        $this->takeUserOnlineForRoomId($this->getRoom()->id);
    }

    public function hasRoom()
    {
        if($this->_room)
        {
            return true;
        }

        return false;
    }

    /**
     * User room status
     */

    public function takeUserOnlineForRoomId($room_id)
    {
        $chat_room_user = ChatRoomUser::where('chat_room_id', '=', $room_id)
                                      ->where('user_id', '=', $this->getUserId())
                                      ->first();

        if($chat_room_user)
        {
            $chat_room_user->status = ChatRoomUser::STATUS_ONLINE;
            $chat_room_user->save();
        }
    }

    public function takenUserOfflineForRoomId($room_id)
    {
        $chat_room_user = ChatRoomUser::where('chat_room_id', '=', $room_id)
                                      ->where('user_id', '=', $this->getUserId())
                                      ->first();

        if($chat_room_user)
        {
            $chat_room_user->status = ChatRoomUser::STATUS_OFFLINE;
            $chat_room_user->save();
        }
    }

    /**
     * Message
     */

    public function message($input)
    {
        $message = new ChatRoomMessage();
        $message->user_id = $this->getUserId();
        $message->status  = ChatRoomMessage::STATUS_NEW;
        $message->content = $input['content'];

        $chat_room = $this->getRoom();
        $chat_room->messages()->save($message);

        $this->_attachInputFile($input, $message);

        $message->load('user', 'user.profile', 'user.profile.picture');

        return $message;
    }

    private function _attachInputFile($input, $message)
    {
        if(empty($input['file']) || empty($input['file']['id'])) return;

        $file = File::where('user_id', '=', $this->getUserId())
                    ->where('id', '=', $input['file']['id'])
                    ->first();

        if(!$file) return;

        $message->file()->save($file);
        $message->load('file');
    }

}

I tend to think that its something related to the operating system or something that doesn't get cleaned correctly or reused... Again, no errors are being logged and the resource consumption is normal (so the script doesn't run out of RAM or kill the CPU).

Would greatly appreciate any hints or ideas!

Thanks, Alex

  • 写回答

0条回答 默认 最新

    报告相同问题?

    悬赏问题

    • ¥15 如何在炒股软件中,爬到我想看的日k线
    • ¥15 51单片机中C语言怎么做到下面类似的功能的函数(相关搜索:c语言)
    • ¥15 seatunnel 怎么配置Elasticsearch
    • ¥15 PSCAD安装问题 ERROR: Visual Studio 2013, 2015, 2017 or 2019 is not found in the system.
    • ¥15 (标签-MATLAB|关键词-多址)
    • ¥15 关于#MATLAB#的问题,如何解决?(相关搜索:信噪比,系统容量)
    • ¥500 52810做蓝牙接受端
    • ¥15 基于PLC的三轴机械手程序
    • ¥15 多址通信方式的抗噪声性能和系统容量对比
    • ¥15 winform的chart曲线生成时有凸起