Docker中的LaravelS

Ⅰ.序

此篇是接着这篇写滴://www.greatytc.com/p/6c9ad29c552b
目的是一个搭小聊天室。

Ⅱ.安装swoole扩展

再上一遍的基础上我先安装了sockets calendar exif pcntl shmop sysvmsg sysvsem sysvshm wddx这些扩展,文件laravel/docker/Dockerfile,在上一步安装php扩展的下方添加,感觉只有sockets是需要的,谁知道呢...都给整上:

RUN set -xe \
    && docker-php-ext-configure calendar --enable-calendar \
    && docker-php-ext-configure exif --enable-exif \
    && docker-php-ext-configure pcntl --enable-pcntl \
    && docker-php-ext-configure shmop --enable-shmop \
    && docker-php-ext-configure sysvmsg --enable-sysvmsg \
    && docker-php-ext-configure sysvsem --enable-sysvsem \
    && docker-php-ext-configure sysvshm --enable-sysvshm \
    && docker-php-ext-configure wddx --enable-wddx \
    && docker-php-ext-configure sockets --enable-sockets \
    && docker-php-ext-install sockets  calendar exif pcntl shmop sysvmsg sysvsem sysvshm wddx

安装swoole扩展

  • 文件laravel/docker/Dockerfile添加:
RUN set -xe \
    && pecl install -o -f swoole \
    && docker-php-ext-enable swoole
  • 报错,大致是这样说的(忘记截图了):错误:加载公共库libstdc++.so.6 文件找不到或者不存在...swoole.so...找不到或者不存在。但其实swoole.so是有的,只是加载出来要libstdc++库。所以:
RUN set -xe \
    && apk add --no-cache --virtual .persistent-deps \
        libstdc++

安装LaravelS

参考

composer require hhxsv5/laravel-s
php artisan laravels publish

配置 .env

LARAVELS_LISTEN_IP=workspace
LARAVELS_DAEMONIZE=true

实现 WebSocket 服务器

参考

<?php

namespace App\Services;

use App\Events\Chat;
use App\User;
use Hhxsv5\LaravelS\Swoole\WebSocketHandlerInterface;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
use Swoole\Http\Request;
use Swoole\WebSocket\Frame;
use Swoole\WebSocket\Server;

class WebSocketService implements WebSocketHandlerInterface
{
    const MSG_TYPE_JOIN = 1;
    const MSG_TYPE_TEXT = 2;
    const MSG_TYPE_LEAVE = 3;

    const AGENT_PC_WEB = 1;
    const AGENT_PHONE_WEB = 2;
    const AGENT_PC_CLIENT = 3;
    const AGENT_PHONE_CLIENT = 4;

    /**
     * @var User
     */
    protected $user;

    protected $room;

    private $agents;

    public function __construct()
    {
        $this->agents = array(
            self::AGENT_PC_WEB,
            self::AGENT_PHONE_WEB,
            self::AGENT_PC_CLIENT,
            self::AGENT_PHONE_CLIENT,
        );
    }

    // 连接建立时触发
    public function onOpen(Server $server, Request $request)
    {
        // 在触发 WebSocket 连接建立事件之前,Laravel 应用初始化的生命周期已经结束,你可以在这里获取 Laravel 请求和会话数据
        // 调用 push 方法向客户端推送数据,fd 是客户端连接标识字段
        Log::info('WebSocket 连接建立');
        // $server->push($request->fd, 'Welcome to WebSocket Server built on LaravelS');
    }

    // 收到消息时触发
    public function onMessage(Server $server, Frame $frame)
    {
        // 调用 push 方法向客户端推送数据
//        $server->push($frame->fd, 'This is a message sent from WebSocket Server at ' . date('Y-m-d H:i:s'));
        echo "received message: {$frame->data}\n";
        $msg = json_decode($frame->data, true);
        if ($msg['msg_type'] == self::MSG_TYPE_JOIN) {
            // TODO:创建房间。参考:https://github.com/nineyang/chat/blob/master/app/Console/Commands/Swoole.php
            Redis::zadd("room:{$msg['room_id']}", intval($msg['msg_from']), $frame->fd);
            Redis::hset('room', $frame->fd, $msg['room_id']);
            $memberInfo = [
                'online' => Redis::zcard("room:{$msg['room_id']}"),
                'all'    => Redis::zrange("room:{$msg['room_id']}", 0, -1)
            ];
            $message = [
                'member_info' => $memberInfo,
                'msg'         => $msg['msg_body']
            ];
            $this->sendAll($server, $msg['room_id'], $msg['msg_from'], $message, self::MSG_TYPE_JOIN);
        } else {
            $this->sendAll($server, $msg['room_id'], $msg['msg_from'], $frame->data, self::MSG_TYPE_TEXT);
            // event(new Chat($msg['msg_from'], $frame->data));
        }
    }

    // 关闭连接时触发
    public function onClose(Server $server, $fd, $reactorId)
    {
        echo "connection close: {$fd}\n";
        $room_id = Redis::hget('room', $fd);
        Redis::hdel('room', $fd);
        $user_id = intval(Redis::zscore("room:{$room_id}", $fd));
        Redis::zrem("room:{$room_id}", $fd);
        $balance = Redis::zcard("room:{$room_id}");
        if ($balance == 0) {
            Redis::del("room:{$room_id}");
            return;
        }
        $memberInfo = [
            'online' => $balance,
            'all'    => Redis::zrange("room:{$room_id}", 0, -1)
        ];
        $message = [
            'member_info' => $memberInfo,
            'msg'         => $user_id . '离开了群聊'
        ];
        $this->sendAll($server, $room_id, $user_id, $message, self::MSG_TYPE_LEAVE);
    }

    /**
     * @param Server $ws
     * @param $room_id
     * @param null $user_id
     * @param null $message
     * @param int $type
     */
    private function sendAll($ws, $room_id, $user_id = null, $message = null, $type = self::MSG_TYPE_TEXT)
    {
//        $user = $this->user->find($user_id, ['id', 'name']);
//        if (!$user) {
//            return;
//        }
        if ($type != self::MSG_TYPE_TEXT)
            $message = json_encode([
                'msg_body' => is_string($message) ? nl2br($message) : $message,
                'msg_from' => $user_id,
                'msg_type' => $type
            ]);
        $members = Redis::zrange("room:{$room_id}", 0, -1);
        foreach ($members as $fd) {
            $ws->push($fd, $message);
        }
    }
}

修改配置文件

  • 配置文件 config/laravels.php
'websocket' => [
    'enable' => true,
    'handler' => \App\Services\WebSocketService::class,
],
...
// 可选
'swoole' => [
    ...
    
    // 每隔 60s 检测一次所有连接,如果某个连接在 600s 内都没有发送任何数据,则关闭该连接
    'heartbeat_idle_time'      => 600,
    'heartbeat_check_interval' => 60,
    
    ...
],
  • 配置.env
LARAVELS_LISTEN_IP=app   // 这里的 IP 需要和 nginx upstream 中配置的监听 IP 保持一致,即php-fpm的服务名
LARAVELS_DAEMONIZE=true
  • 配置Nginx配置文件,注意mapupstream配置与server是平级的
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

upstream laravels{
    # Connect IP:Port
#            server workspace:5200 weight=5 max_fails=3 fail_timeout=30s;
#            keepalive 16;
    server app:5200 weight=5 max_fails=3 fail_timeout=30s;
    keepalive 16;
}

server {
        listen 80;
        server_name .purchase.com;

        set $root_path '/usr/share/nginx/html';
        set $php_path '/var/www/html/public';

        client_max_body_size 100m;

        charset utf-8;

        location ~ (\.html|\.css|\.js|\.jpg|\.jpeg|\.png|\.gif|\.ico|\.ttf|\.woff|\.woff2|\.xls|\.xlsx|\.docx|\.pdf|\.mpga|\.mp3|\.txt) {
             root $root_path;
       }

        location / {
             index   index.php index.html index.htm;
             try_files $uri $uri/ /index.php?$args;
        }

        # WebSocket 通信
        location =/ws {
            proxy_connect_timeout 3600;
            proxy_send_timeout   3600;
            proxy_read_timeout   3600;
            proxy_http_version 1.1;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Real-PORT $remote_port;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $http_host;
            proxy_set_header Scheme $scheme;
            proxy_set_header Server-Protocol $server_protocol;
            proxy_set_header Server-Name $server_name;
            proxy_set_header Server-Addr $server_addr;
            proxy_set_header Server-Port $server_port;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection $connection_upgrade;
            proxy_pass http://laravels;
        }

        location ~ \.php(.*)$ {
            root $php_path;
            fastcgi_pass app:9000;
            fastcgi_index index.php;
            fastcgi_buffering off;
            fastcgi_split_path_info ^((?U).+\.php)(/?.+)$;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            fastcgi_param PATH_INFO $fastcgi_path_info;
            fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
            include fastcgi_params;
            fastcgi_intercept_errors on;
        }

        location ~ /\.ht {
            deny all;
        }
}

Websocket的前端实现

直接上代码吧,简陋滴很。(要一个jquery文件)

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>Laravel</title>
    <!-- Fonts -->
    <link href="https://fonts.googleapis.com/css2?family=Nunito:wght@200;600&display=swap" rel="stylesheet">
    <script src="{{asset('assets/js/jquery-3.0.0.min.js')}}" type="text/javascript" charset="utf-8"></script>
    <!-- Styles -->
    <style>
        html, body {
            background-color: #fff;
            color: #636b6f;
            font-family: 'Nunito', sans-serif;
            font-weight: 200;
            height: 100vh;
            margin: 0;
        }

        .full-height {
            height: 100vh;
        }

        .flex-center {
            align-items: center;
            display: flex;
            justify-content: center;
        }

        .position-ref {
            position: relative;
        }

        .top-right {
            position: absolute;
            right: 10px;
            top: 18px;
        }

        .content {
            text-align: center;
        }

        .title {
            font-size: 84px;
        }

        .links > a {
            color: #636b6f;
            padding: 0 25px;
            font-size: 13px;
            font-weight: 600;
            letter-spacing: .1rem;
            text-decoration: none;
            text-transform: uppercase;
        }

        .m-b-md {
            margin-bottom: 30px;
        }
    </style>
</head>
<body style="position: relative">
<div class="flex-center">
    <textarea style="height: 100px;width: 500px;border: 1px solid red;" id="msgBody"></textarea>
</div>
<div class="flex-center">
    <input type="hidden" name="roomId" id="roomId">
    <input type="button" id="btnConnection" value="复制房间连接"/>
    <input type="button" id="btnClose" value="关闭"/>
    <input type="button" id="btnSend" value="发送"/>
</div>
<textarea id="copy" style="display:none;height: 0;"></textarea>
<div
    style="display: none;border: 1px solid red;width:500px;height: 500px; position:absolute;left:50%;top:50%;transform: translate(-50%,-50%);"
    id="chatBox">

</div>
</body>
<script>
    var uid = Math.ceil(Math.random() * 10) + 1;
    var websocket;
    var roomId = "{{$roomId}}";
    var createWebsocket = function () {
        if (typeof (WebSocket) == "undefined") {
            alert("您的浏览器不支持WebSocket");
            return;
        }
        var wsUri = (location.protocol == 'https:' ? 'wss://' : 'ws://') + location.host +'/ws';
        websocket = new WebSocket(wsUri);
        websocket.onopen = function (evt) {
            console.log("Connected to WebSocket server.");
            $("#chatBox").show();
            if (websocket.readyState === 1){
                websocket.send('{"msg_type":"1","agent_type":"1","room_id":"' + roomId + '","msg_time":"' + Math.floor(((new Date()).valueOf()) / 1000) + '","msg_from":"' + uid + '","msg_body":"' + uid + '加入了群聊"}');
            }
        };

        websocket.onclose = function (evt) {
            console.log("Disconnected");
            $("#chatBox").css("display", "none");
        };

        websocket.onmessage = function (evt) {
            var data = JSON.parse(evt.data);
            var h;
            var s = 'left';
            if (data.msg_from == uid) {
                s = 'right';
            }
            h = '<div>\n' +
                '        <div style="text-align: ' + s + ';">' + data.msg_from + ':</div>\n' +
                '        <div style="text-align: ' + s + ';">' + data.msg_body + '</div>\n' +
                '    </div>';

            if (data.msg_type == 1 || data.msg_type == 3) {
                h = '<div style="text-align: center">' + data.msg_body.msg + '</div>';
            }

            $("#chatBox").append(h);
            console.log('Retrieved data from server: ' + evt.data);
        };

        websocket.onerror = function (evt, e) {
            console.log('Error occured: ' + evt.data);
        };
    };

    createWebsocket();

    function copyText(str) {
        $('#copy').text(str).show();
        var ele = document.getElementById("copy");
        ele.select();
        document.execCommand('copy', false, null);
        $('#copy').hide();
        alert('复制成功!');
    }

    $("#btnConnection").click(function () {
        copyText(window.location.href);
    });

    //发送消息
    $("#btnSend").click(function () {
        websocket.send('{"msg_type":"2","agent_type":"1 ","room_id":"' + roomId + '","msg_time":"' + Math.floor(((new Date()).valueOf()) / 1000) + '","msg_from":"' + uid + '","msg_body":"' + $('#msgBody').val() + '"}');
    });

    //关闭
    $("#btnClose").click(function () {
        websocket.close();
    });
</script>
</html>

启动

在php源代码容器中,项目根目录下执行:

php bin/laravels start

访问下:http://purchase.com/test/chatRoom/91609929b9699631e84fdef3385f3721(本地的host文件配置好域名)

  • 出现问题,这个问题参考的网站下的评论里也出现了,但是没有解决:
    php源代码容器中,[ERROR] worker[5] error: exitCode=255, signal=0

解决问题

在nginx容器中查看app:5200是否能连上:

  • 安装telnet,参考,下面这样是正常的
/usr/share/nginx/html # apk update
/usr/share/nginx/html # apk add busybox-extras
/usr/share/nginx/html # busybox-extras telnet app:5200
Connected to app:5200
  • 如果连不上,可能是5200端口没有给到位,那么,文件docker/docker-compose.override.yml,暴露或者映射下端口
    app:
        build:
            context: ../laravel/
            dockerfile: docker/Dockerfile
        image: "laravel-php:latest"
        ports:
            - "5200:5200"
        environment:
            ## Application Configuration ##
            APP_ENV: "dev"
            APP_DEBUG: "true"
        volumes:
            - ../laravel:/var/www/html

php源代码容器中,[ERROR] worker[5] error: exitCode=255, signal=0

  • 参考,composer.json文件加入 "app/Services",
"autoload": {
"psr-4": {
"App\": "app/"
},
"classmap": [
"database/seeds",
"app/Services",
"database/factories"
]
},
  • 项目根目录下运行命令
composer dump-autoload

至此,模子就差不多了

image.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容