12/08/2018, 13:23

Ứng dụng Chat với Laravel, Redis và Socket.IO

Nếu bạn muốn làm một ứng dụng web có thể cập nhật theo thời gian thực, Web socket và Socket.IO có thể giúp bạn. Tuy nhiên, Laravel thì không trực tiếp hỗ trợ http socket, vì vậy chúng ta cần sử dụng Redis. Redis là một cơ sở dữ liệu dạng key/value với một tính năng rất thú vị là publish/subscriber. ...

Nếu bạn muốn làm một ứng dụng web có thể cập nhật theo thời gian thực, Web socket và Socket.IO có thể giúp bạn. Tuy nhiên, Laravel thì không trực tiếp hỗ trợ http socket, vì vậy chúng ta cần sử dụng Redis. Redis là một cơ sở dữ liệu dạng key/value với một tính năng rất thú vị là publish/subscriber. Về cơ bản thì mọi message được publish vào một queue nào đó, sẽ được nhận bởi mọi subscriber, trong trường hợp này đó chính là NodeJS server.

Cài đặt môi trường

Trước hết, chúng ta cần cài đặt redis. Cách làm rất đơn giản, với Ubuntu bạn chỉ cần chạy lệnh sau:

sudo apt-get install redis-server

Node Server với Socket.IO sẽ giúp chúng ta duy trì một kết nối web socket với browser. Điều đáng chú ý ở đây là chúng ta sẽ sử dụng tính năng subscribe của Redis. Node server sẽ subscribe một channel của Redis, khi có tin nhắn mới gửi đến, Laravel sẽ publish message vào channel này, và Redis sẽ thông báo cho các subscriber (tức là Node Server) biết về tin nhắn đó. Và nhiệm vụ của Node là gửi lại tin nhắn này đến browser.

Trong Laravel, chúng ta hãy tao một subfolder tên là NodeJS, và trong folder đó chạy lệnh sau để cài các gói cần thiết

npm install express redis socket.io --save

NodeJS Server

Bây giờ chúng ta hãy tạo file server.js có nội dung như sau

var app = require('express')();
var server = require('http').Server(app);
var io = require('socket.io')(server);
var redis = require('redis');

server.listen(8890);
io.on('connection', function (socket) {

  console.log("new client connected");
  var redisClient = redis.createClient();
  redisClient.subscribe('message');

  redisClient.on("message", function(channel, message) {
    console.log("mew message in queue "+ message + "channel");
    socket.emit(channel, message);
  });

  socket.on('disconnect', function() {
    redisClient.quit();
  });

});

Những dòng code này bản thân nó đã nói lên tất cả. Chúng ta khởi động một http server ở cổng 8090 và gắn nó với Socket.IO server. Sau đó chúng ta tạo ra một Redis client, và mỗi khi có một socket client kết nối thì chúng ta sẽ subscribe một channel trong Redis.

Khi có tin nhắn được push vào kênh này, thì redisClient sẽ xảy ra sự kiện "message", và socket sẽ emit nó đến với các client đang kết nối.

Để khởi động server node, từ trong cửa sổ lệnh, chúng ta gõ lệnh sau:

node server.js

Laravel và Redis

Trong Laravel 5, việc config sử dụng Redis rất dễ dàng. Đầu tiên, bạn cần cài đặt predis, một package hỗ trợ redis cho PHP. Nếu bạn dùng composer thì có thể dùng lệnh sau để cài:

composer require predis/predis

Để tránh conflict với Redis trong môi trường PHP, chúng ta cần sửa một chút về alias cho Redis module của Laravel. Trong file config app.php, hãy tìm dòng sau:

'Redis'    => 'IlluminateSupportFacadesRedis',

và đổi thành

'LRedis'    => 'IlluminateSupportFacadesRedis',

Bây giờ chúng ta đã có thể dùng Redis trong Laravel mà không gặp phải bất kì lỗi nào.

Laravel Application

Từ phía Laravel, chúng ta sẽ làm một ứng dụng rất cơ bản, chỉ có 2 trang, một dùng để gửi message, và một dùng để nhận message theo thời gian thực mà không cần reload. Tất cả mọi logic sẽ được đặt trong socketController.

php artisan make:controller socketController

Trong controller này, 3 actions sẽ được thực hiện. 2 actions đầu sẽ hiển thị giao diện tin nhắn và soạn tin. Và action cuối cùng sẽ dùng để push tin nhắn vào channel của Redis.

Code của controller sẽ như sau:

<?php namespace AppHttpControllers;
use AppHttpRequests;
use AppHttpControllersController;
use Request;
use LRedis;

class SocketController extends Controller {
    public function __construct()
    {
        $this->middleware('guest');
    }

    public function index()
    {
        return view('socket');
    }

    public function writemessage()
    {
        return view('writemessage');
    }

    public function sendMessage(){
        $redis = LRedis::connection();
        $redis->publish('message', Request::input('message'));
        return redirect('writemessage');
    }
}

Action index và writemessage rất đơn giản, chỉ render ra view. Action quan trọng là sendMessage, chúng ta sử dụng Redis để đẩy dữ liệu vào channel và từ đó sẽ được Node Server đẩy trở lại browser của người dùng.

Form writemessage.blade.php sẽ như sau, không có gì đặc biệt cả:

@extends('app')

@section('content')
    <div class="container">
        <div class="row">
            <div class="col-md-10 col-md-offset-1">
                <div class="panel panel-default">
                    <div class="panel-heading">Send message</div>
                    <form action="sendmessage" method="POST">
                        <input type="text" name="message" >
                        <input type="submit" value="send">
                    </form>
                </div>
            </div>
        </div>
    </div>

@endsection

Trang mà message được nhận, socket.blade.php sẽ cần import một vài dòng javascript để socket có thể hoạt động. Và chúng ta sẽ sử dụng hàm append của jQuery để update lại DOM. Sau đây là mã nguồn:

@extends('app')

@section('content')
    <script src="//code.jquery.com/jquery-1.11.2.min.js"></script>
    <script src="//code.jquery.com/jquery-migrate-1.2.1.min.js"></script>
    <script src="https://cdn.socket.io/socket.io-1.3.4.js"></script>

    <div class="container">
        <div class="row">
            <div class="col-lg-8 col-lg-offset-2" >
              <div id="messages" ></div>
            </div>
        </div>
    </div>
    <script>
        var socket = io.connect('http://localhost:8890');
        socket.on('message', function (data) {
            $( "#messages" ).append( "<p>"+data+"</p>" );
          });
    </script>

@endsection

Điều cuối cùng chúng ta cần làm là routing cho Laravel app. Trong file routes.php chúng ta thêm những dòng sau:

Route::get('socket', 'SocketController@index');
Route::post('sendmessage', 'SocketController@sendMessage');
Route::get('writemessage', 'SocketController@writemessage');

Vậy là chúng ta đã xong. Để test chứng trình, bạn có thể chạy Redis và Node Server. Sau đó mở 2 trình duyệt, một bên mở sendmessage và một bên mở trang socket. Bây giờ bạn hãy submit một message và message đó sẽ được push vào DOM bằng jQuery ngay lập tức. Cách thực hiện Socket.IO trong Laravel này rất dễ dàng và linh hoạt nhưng có một nhược điểm. Đầu tiên, bạn phải cần 2 cổng HTTP, một cho PHP server, và một cho Node server. Thứ hai là về mặt logic. Nếu bạn muốn sử dụng nhiều hơn một channel, bạn cần phải sửa lại cả ở Node server và Laravel controller.

Việc nghiên cứu về Socket.IO trong Laravel rất thú vị và nếu có thời gian tôi sẽ viết thêm nhiều bài viết khác chuyên sâu hơn về chủ đề này.

Tham khảo

https://www.codetutorial.io/laravel-5-and-socket-io-tutorial/

0