Кто из вас не мечтал о собственном уютном чатике с преферансом и куртизанками? Фреймворк Laravel, позволяет создать его, не сильно напрягаясь, используя Web Socket и библиотеку Ratchet. На установке Ratchet особо останавливаться не буду. По этому поводу много чего написано и есть например замечательные уроки от Дмитрия Афанасьева
В принципе всё что нужно для работы Web Socket сервера можно смело взять от туда.
/app/Classes/Socket/Base/BasePusher.php Базовый класс нашего пушера
<?php namespace App\Classes\Socket\Base; use Ratchet\ConnectionInterface; use Ratchet\Wamp\WampServerInterface; class BasePusher implements WampServerInterface { protected $subcribed_topics = []; /** * @return array */ public function getSubcribedTopics() { return $this->subcribed_topics; } public function addSubscribedTopic($topic){ $this->subcribed_topics[$topic->getId()] = $topic; } public function onSubscribe(ConnectionInterface $conn, $topic) { $this->addSubscribedTopic($topic); } public function onUnSubscribe(ConnectionInterface $conn, $topic) { // TODO: Implement onUnSubscribe() method. } public function onOpen(ConnectionInterface $conn) { echo ('New connection ('.$conn->resourceId.')'.PHP_EOL); } public function onClose(ConnectionInterface $conn) { echo ('Connection ('.$conn->resourceId.') has disconnected'.PHP_EOL); } public function onCall(ConnectionInterface $conn, $id, $topic, array $params) { $conn->callError($id, $topic, 'You are not allowed to make calls')->close(); } public function onPublish(ConnectionInterface $conn, $topic, $event, array $exclude, array $eligible) { $conn->close(); } public function onError(ConnectionInterface $conn, \Exception $e) { echo ('Error: '.$e->getMessage().PHP_EOL); $conn->close(); } }
/app/Classes/Socket/Pusher.php — собственно пушер
<?php namespace App\Classes\Socket; use App\Classes\Socket\Base\BasePusher; use ZMQContext; use Illuminate\Support\Facades\Config; class Pusher extends BasePusher { static function sendDataToServer(array $data) { $context = new ZMQContext; $socket = $context->getSocket(\ZMQ::SOCKET_PUSH, 'my_pusher'); $ip = Config::get('app.ip'); $socket->connect('tcp://' . $ip . ':5555'); $data = json_encode($data); $socket->send($data); } public function broadcast($json_data_to_send) { $data_to_send = json_decode($json_data_to_send); $subscribed_topics = $this->getSubcribedTopics(); if(is_object($data_to_send)){ if (isset($subscribed_topics[$data_to_send->topic_id])) { $topic = $subscribed_topics[$data_to_send->topic_id]; $topic->broadcast($data_to_send); } } } }
в /config/app.php прописываем ip адрес для нашего соединения
'ip' => env('APP_IP', '192.168.10.10'), //стандартный IP адрес для Homestead
ну и в /.env можно прописать например
APP_IP='192.168.10.10'
Далее напишем наш Пуш сервер. /app/Console/Commands/PushServer.php
<?php namespace App\Console\Commands; use Illuminate\Console\Command; use Ratchet\Server\IoServer; use Ratchet\Http\HttpServer; use Ratchet\WebSocket\WsServer; use App\Classes\Socket\Pusher; use React\EventLoop\Factory as ReactLoop; use React\ZMQ\Context as ReactContext; use React\Socket\Server as ReactServer; use Ratchet\Wamp\WampServer; use Illuminate\Support\Facades\Config; class PushServer extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'push_server:serve'; /** * The console command description. * * @var string */ protected $description = 'Push server started'; /** * Create a new command instance. * * @return void */ public function __construct() { parent::__construct(); } /** * Execute the console command. * * @return mixed */ public function handle() { $loop = ReactLoop::create(); $pusher = new Pusher; $context = new ReactContext($loop); $ip = Config::get('app.ip'); echo($ip); $pull = $context->getSocket(\ZMQ::SOCKET_PULL); $pull->bind('tcp://' . $ip . ':5555'); $pull->on('message', [$pusher, 'broadcast']); $web_sock = new ReactServer('tcp://0.0.0.0:8080', $loop); //обратите внимание! Здесь синтаксис изменился относительно описанного в видео для Laravel 5.4 $web_server = new IoServer( new HttpServer( new WsServer( new WampServer($pusher) ) ), $web_sock ); $this->info('Push server is running'); $loop->run(); } }
Веб сервер готов! Можете запустить его командой
php artisan push_server:serve
Теперь создадим три таблицы для нашего чата:
1) Таблица «Беседы» (комнаты, обсуждения). Миграция:
<?php use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateConversationsTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('conversations', function (Blueprint $table) { $table->increments('id'); $table->string('title')->nullable(); $table->integer('owner_id')->nullable(); $table->integer('is_visible')->nullable()->default(1); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('conversations'); } }
2) Сообщения:
<?php use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateMessagesTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('messages', function (Blueprint $table) { $table->increments('id'); $table->text('text')->nullable(); $table->integer('conversation_id')->nullable(); $table->integer('is_visible')->default(1)->nullable(); $table->integer('attachment_id')->nullable(); $table->integer('author_id')->nullable(); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('messages'); } }
3) Подписчики:
<?php use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateFollowersTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('followers', function (Blueprint $table) { $table->increments('id'); $table->string('type')->nullable(); $table->integer('follower_id')->nullable(); $table->integer('post_id')->nullable(); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('followers'); } }
Так как функционал подписчиков можно использовать не только для чата, зададим ему поле 'type', на всякий случай.
Что-то получается слишком длинно. Так что ждите разобью пожалуй статью на несколько частей.
Выдает ошибку « There are no commands defined in the „push_server“ namespace», хелп плез
да. забыл один момент. в app/Console/Kernel.php надо прописать
class Kernel extends ConsoleKernel
{
/**
* The Artisan commands provided by your application.
*
* @var array
*/
protected $commands = [
\App\Console\Commands\PushServer::class,
];
теоретически должно заработать
Добрый день, подскажите пожалуйста.
php artisan push_server:serve
UnexpectedValueException : The stream or file «/home/mdma/public_html/test3/storage/logs/laravel.log» could not be opened: failed to open stream: Permission denied
at /home/mdma/public_html/test3/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php:107
103| }
104| restore_error_handler ();
105| if (!is_resource ($this->stream)) {
106| $this->stream = null;
> 107| throw new \UnexpectedValueException (sprintf ('The stream or file «%s» could not be opened: '.$this->errorMessage, $this->url));
108| }
109| }
110|
111| if ($this->useLocking) {
Exception trace:
1 Monolog\Handler\StreamHandler::write ()
/home/mdma/public_html/test3/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php:37
2 Monolog\Handler\AbstractProcessingHandler::handle ()
/home/mdma/public_html/test3/vendor/monolog/monolog/src/Monolog/Logger.php:337
Please use the argument -v to see more details.
С правами разобрался, очень странно они назначились, непонятно как. Дальше — хуже) php artisan push_server:serve
Symfony\Component\Debug\Exception\FatalThrowableError : Class 'React\ZMQ\Context' not found
at /home/mdma/public_html/test3/app/Console/Commands/PushServer.php:51
47| public function handle ()
48| {
49| $loop = ReactLoop::create ();
50| $pusher = new Pusher;
> 51| $context = new ReactContext ($loop);
52| $ip = Config::get ('app.ip');
53| echo ($ip);
54|
55| $pull = $context->getSocket (\ZMQ::SOCKET_PULL);
Exception trace:
1 App\Console\Commands\PushServer::handle ()
/home/mdma/public_html/test3/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:29
2 call_user_func_array ([])
/home/mdma/public_html/test3/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:29
Please use the argument -v to see more details.
вы не забыли указать use React\ZMQ\Context as ReactContext; в PushServer.php?
I blog quite often and I truly thank you for your content.
Your article has really peaked my interest. I will bookmark your
website and keep checking for new information about once per week.
I opted in for your RSS feed too.