使用Docker、Laravel、Redis和Laravel Echo实现实时通信

首先

在Google上搜索时,我找到了很多关于使用Laravel和Redis进行WebSocket通信的官方文档和其他文章,但是我尝试了很多方法却一直不太成功,所以我决定将这些过程记录在这篇文章中作为备忘。由于篇幅原因,我将省略各个工具的说明。我认为搜索会有很多更易理解的文章出现…。

环境

Docker Desktop for Mac 3.6.0
PHP 8.0.9版本
Laravel 8.40版本
redis-server 6.2.5版本
Laravel-echo-server 1.6.2版本
node 14.16.0版本
npm 7.8.0版本
composer 1.10.19版本

使用Docker构建容器。

这次我们在以下文章的基础上添加了所需内容的Docker环境。非常感谢 @ucan-lab 先生。
【超入门】使用Docker快速构建20分钟内的Laravel开发环境 Hands-on教程。

项目组成

.
├── infra
│   ├── echo-server
│   │   └──Dockerfile
│   ├── mysql
│   │   ├── Dockerfile
│   │   └── my.cnf
│   ├── nginx 
│   │   └── default.conf
│   ├── php
│   │   ├── Dockerfile (この名前にするとファイル名の指定を省略できる)
│   │   └── php.ini
│   └── redis
│       └── data(ディレクトリです)
├── docker-compose.yml (この名前にするとファイル名の指定を省略できる)
└── backend
    └── Laravelをインストールするディレクトリ

各个文件的内容

version: "3.9"
services:
  app:
    build: ./infra/php
    volumes:
      - ./backend:/work
  web:
    image: nginx:1.20-alpine
    ports:
      - 8080:80
    volumes:
      - ./backend:/work
      - ./infra/nginx/default.conf:/etc/nginx/conf.d/default.conf
    working_dir: /work
  db:
    build: ./infra/mysql
    ports:
      - 33060:3306
    volumes:
      - db-store:/var/lib/mysql
  echo-server:
    image: broadcast-echo-server
    build: ./infra/echo-server
    ports:
    - "6001:6001"
    command: laravel-echo-server start
    volumes:
    - ./backend:/work
    working_dir: /work
  redis:
    image: redis:latest
    ports:
      - 6379:6379
    volumes:
      - "./infra/redis/data:/data"
volumes:
  db-store:
FROM node:13.8-alpine
RUN npm install -g laravel-echo-server
WORKDIR /work
CMD ["laravel-echo-server", "start"]
FROM mysql/mysql-server:8.0
ENV MYSQL_DATABASE=laravel_local \
  MYSQL_USER=phper \
  MYSQL_PASSWORD=secret \
  MYSQL_ROOT_PASSWORD=secret \
  TZ=Asia/Tokyo
COPY ./my.cnf /etc/my.cnf
RUN chmod 644 /etc/my.cnf
[mysqld]
# default
skip-host-cache
skip-name-resolve
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
secure-file-priv=/var/lib/mysql-files
user=mysql
pid-file=/var/run/mysqld/mysqld.pid
# character set / collation
character_set_server = utf8mb4
collation_server = utf8mb4_ja_0900_as_cs_ks
# timezone
default-time-zone = SYSTEM
log_timestamps = SYSTEM
# Error Log
log-error = mysql-error.log
# Slow Query Log
slow_query_log = 1
slow_query_log_file = mysql-slow.log
long_query_time = 1.0
log_queries_not_using_indexes = 0
# General Log
general_log = 1
general_log_file = mysql-general.log
[mysql]
default-character-set = utf8mb4
[client]
default-character-set = utf8mb4
server {
    listen 80;
    server_name example.com;
    root /work/public;
    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Content-Type-Options "nosniff";
    index index.php;
    charset utf-8;
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }
    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }
    error_page 404 /index.php;

    location ~ \.php$ {
        fastcgi_pass app:9000;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
    }
    location ~ /\.(?!well-known).* {
        deny all;
    }
}
FROM php:8.0-fpm-buster
SHELL ["/bin/bash", "-oeux", "pipefail", "-c"]
ENV COMPOSER_ALLOW_SUPERUSER=1 \
  COMPOSER_HOME=/composer
COPY --from=composer:2.0 /usr/bin/composer /usr/bin/composer
RUN apt-get update && \
  apt-get -y install git unzip libzip-dev libicu-dev libonig-dev && \
  apt-get clean && \
  rm -rf /var/lib/apt/lists/* && \
  docker-php-ext-install intl pdo_mysql zip bcmath
COPY ./php.ini /usr/local/etc/php/php.ini
RUN curl -sL https://deb.nodesource.com/setup_14.x | bash -
RUN apt-get install -y nodejs
RUN npm install npm@latest -g
WORKDIR /work
zend.exception_ignore_args = off
expose_php = on
max_execution_time = 30
max_input_vars = 1000
upload_max_filesize = 64M
post_max_size = 128M
memory_limit = 256M
error_reporting = E_ALL
display_errors = on
display_startup_errors = on
log_errors = on
error_log = /dev/stderr
default_charset = UTF-8
[Date]
date.timezone = Asia/Tokyo
[mysqlnd]
mysqlnd.collect_memory_statistics = on
[Assertion]
zend.assertions = 1
[mbstring]
mbstring.language = Japanese

安装Laravel

在启动Docker容器之后,安装Laravel。从Laravel的安装到环境配置,可以参考下方链接的文章,采用相同的步骤进行操作。
请参考【超入门】Docker手动教程:在20分钟内快速构建Laravel开发环境。

Laravel-echo-server的配置

在初次启动容器时,由于Laravel-echo-server的初始设置尚未完成,会导致错误发生,因此需要进行初始设置。

$ docker compose run echo-server laravel-echo-server init
? Do you want to run this server in development mode? Yes
? Which port would you like to serve from? 6001
? Which database would you like to use to store presence channel members? redis
? Enter the host of your Laravel authentication server. https://localhost
? Will you be serving on http or https? http
? Do you want to generate a client ID/Key for HTTP API? No
? Do you want to setup cross domain access to the API? No
? What do you want this config to be saved as? laravel-echo-server.json
Configuration file saved. Run laravel-echo-server start to run server.

当执行上述操作后,会在Laravel 项目根目录下创建一个名为 laravel-echo-server.json 的文件,随后按照以下方式进行修改。

{
    "authHost": "http://localhost",
    "authEndpoint": "/broadcasting/auth",
    "clients": [],
    "database": "redis",
    "databaseConfig": {
        "redis": {
            "host": "redis",
            "port": 6379
        },
        "sqlite": {
            "databasePath": "/database/laravel-echo-server.sqlite"
        }
    },
    "devMode": true,
    "host": null,
    "port": "6001",
    "protocol": "http",
    "socketio": {},
    "secureOptions": 67108864,
    "sslCertPath": "",
    "sslKeyPath": "",
    "sslCertChainPath": "",
    "sslPassphrase": "",
    "subscribers": {
        "http": true,
        "redis": true
    },
    "apiOriginAllow": {
        "allowCors": false,
        "allowOrigin": "",
        "allowMethods": "",
        "allowHeaders": ""
    }
}

添加所需的库

"require": {
    // 他省略
    "predis/predis": "^1.1"
}

"dependencies": {
        // 他省略
    "laravel-echo": "^1.11.1",
    "laravel-echo-server": "^1.6.2",
    "socket.io": "^2.4.0",
    "socket.io-client": "^2.4.0",
}

所有的设置

配置提供商

取消app/config/app.php文件中以下部分的注释。

App\Providers\BroadcastServiceProvider::class,

Redis的配置

'redis' => [
        'client' => env('REDIS_CLIENT', 'predis'),
        'default' => [
            'host' => env('REDIS_HOST', '127.0.0.1'),
            'password' => env('REDIS_PASSWORD', null),
            'port' => env('REDIS_PORT', 6379),
            'database' => 0,
        ],
        'cache' => [
            'host' => env('REDIS_HOST', '127.0.0.1'),
            'password' => env('REDIS_PASSWORD', null),
            'port' => env('REDIS_PORT', 6379),
            'database' => 1,
        ],
        'options' => [
            'prefix' => env('REDIS_PREFIX', ''),
        ],
    ],

更改.env文件

BROADCAST_DRIVER=redis
QUEUE_CONNECTION=redis
REDIS_HOST=redis
REDIS_CLIENT=predis
REDIS_PASSWORD=null
REDIS_PORT=6379
REDIS_PREFIX=""

在这一点上,我们已经能够通过WebSocket进行实时通信。

亲自试试看

使用Laravel的事件功能,可以将数据实时发送到客户端。

服务器端的准备工作

创建活动

php artisan make:event MessageRecieved
<?php

namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class MessageReceived implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;


    /**
     * Get the channels the event should broadcast on.
     *
     * @return \Illuminate\Broadcasting\Channel|array
     */
    public function broadcastOn()
    {
        return new Channel('test');
    }

    public function broadcastWith()
    {
        return [
            'data' => 'test',
        ];
    }
}

活动准备引发

当我们访问该路径时,将触发一个事件。

Route::get('/', function () {
    event(new \App\Events\MessageReceived());
    return view('welcome');
});

工作人员的启动

由于使用Redis进行处理,因此需要启动工作进程来执行任务。

php artisan queue:work

客户端准备工作

准备视图

以下是在路由中显示的视图方式:

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <title>Laravel</title>
    <link rel="stylesheet" href="{{ mix('css/app.css') }}">
</head>
<body>
    <div id="app">
      test
    </div>
    <script src="{{ mix('js/app.js') }}"></script>
    <script src="http://{{ Request::getHost() }}:6001/socket.io/socket.io.js"></script>
</body>
</html>

脚本准备完毕

在 bootstrap.js 中添加以下内容。

import Echo from "laravel-echo";
window.io = require("socket.io-client");
window.Echo = new Echo({
    broadcaster: "socket.io",
    host: window.location.hostname + ":6001",
});

window.Echo.channel("test").listen("MessageReceived", e => {
    console.log("メッセージ受け取り成功!!!");
});

编译

npm install && npm run dev

进行

スクリーンショット 2021-08-18 23.26.36.png

動かないとき

如果重新启动Docker可以解决问题,那么可以尝试这个方法。
如果问题仍然存在,则应检查Docker的日志信息。

docker compose logs

最后

这次为了方便起见我选择直接粘贴代码作为备忘录。顺便一提,如果使用Pusher这个服务,实现实时通信非常简单。这在Laravel的官方文档中也有说明,如果你感兴趣的话一定要查看一下。

广告
将在 10 秒后关闭
bannerAds