我们将 Laravel 4 与 Ratchet 一起使用来创建一个聊天应用程序。一切正常运行约14-20小时。一段时间后,聊天应用程序停止运行。建立从客户端到服务器的连接,但在此之后服务器关闭连接。
我们的日志文件中没有报告任何错误,而且我们无法在我们的开发环境中复制问题的事实也无济于事。
重新启动服务器上的聊天应用程序可以再解决 14-20 小时的问题。
主管配置:
[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 设置聊天命令):
<?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();
}
}
服务器.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();
}
}
服务.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();
}
}
}
客户端.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');
}
}
我倾向于认为它与操作系统有关,或者没有被正确清理或重用......再次,没有记录错误并且资源消耗是正常的(所以脚本不会用完 RAM或杀死 CPU)。
非常感谢任何提示或想法!
谢谢,亚历克斯