美文网首页
Docker中的LaravelS

Docker中的LaravelS

作者: 段义纬 | 来源:发表于2021-07-17 19:15 被阅读0次

    Ⅰ.序

    此篇是接着这篇写滴:https://www.jianshu.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

    相关文章

      网友评论

          本文标题:Docker中的LaravelS

          本文链接:https://www.haomeiwen.com/subject/dfiwpltx.html