Rachet chat application - connection closed immediately after established when the app runs for a period of time

偶尔善良 提交于 2019-12-08 04:12:54

问题


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

来源:https://stackoverflow.com/questions/25273500/rachet-chat-application-connection-closed-immediately-after-established-when-t

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!