您现在的位置是:首页> 编程文章 个人博客模板

LaravelS - 基于 Swoole 加速 Laravel/Lumen - 带你飞

无痕小Q个人博客 2020-02-28 00:39:09主页 1071人已围观

简介LaravelS 是一个胶水项目,用于快速集成 Swoole 到 Laravel 或 Lumen,然后赋予它们更好的性能、更多可能性。

特性


内置 Http/WebSocket 服务器


多端口混合协议


协程


自定义进程


常驻内存


异步的事件监听


异步的任务队列


毫秒级定时任务


平滑 Reload


修改代码后自动 Reload


同时支持 Laravel Lumen,兼容主流版本


简单,开箱即用


要求


依赖 说明

PHP >= 5.5.9 推荐PHP7+

Swoole >= 1.7.19 2.0.12开始不再支持PHP5 推荐4.2.3+

Laravel/Lumen >= 5.1 推荐5.6+

安装


1. 通过 Composer 安装 (packagist)。有可能找不到 3.0 版本,解决方案移步#81


composer require "hhxsv5/laravel-s:~3.6.0" -vvv

# 确保你的composer.lock文件是在版本控制中

2. 注册 Service Provider(以下两步二选一)。


Laravel: 修改文件 config/app.phpLaravel 5.5+支持包自动发现,你应该跳过这步


'providers' => [

    //...

    Hhxsv5\LaravelS\Illuminate\LaravelSServiceProvider::class,

],

Lumen: 修改文件 bootstrap/app.php


$app->register(Hhxsv5\LaravelS\Illuminate\LaravelSServiceProvider::class);

3. 发布配置和二进制文件。


每次升级 LaravelS 后,需重新 publish;点击 Release 去了解各个版本的变更记录。


php artisan laravels publish

# 配置文件:config/laravels.php

# 二进制文件:bin/laravels bin/fswatch bin/inotify

4. 修改配置 config/laravels.php:监听的 IP、端口等,请参考配置项。


运行


在运行之前,请先仔细阅读:注意事项 (这非常重要)

操作命令:php bin/laravels {start|stop|restart|reload|info|help}

命令 说明

start 启动 LaravelS,展示已启动的进程列表 "ps -ef|grep laravels"。支持选项"-d|--daemonize" 以守护进程的方式运行,此选项将覆盖 laravels.php swoole.daemonize 设置;支持选项 "-e|--env" 用来指定运行的环境,如 --env=testing 将会优先使用配置文件.env.testing,这个特性要求 Laravel 5.2+

stop 停止 LaravelS

restart 重启 LaravelS,支持选项 "-d|--daemonize""-e|--env"

reload 平滑重启所有 Task/Worker/Timer 进程 (这些进程内包含了你的业务代码),并触发自定义进程的 onReload 方法,不会重启 Master/Manger 进程;修改 config/laravels.php 后,你只能调用 restart 来实现重启

info 显示组件的版本信息

help 显示帮助信息

运行时文件:start 时会自动执行 artisan laravels config 并生成这些文件,开发者一般不需要关注它们,建议将它们加到.gitignore 中。

文件 说明

storage/laravels.json LaravelS 的运行时配置文件

storage/laravels.pid Master 进程的 PID 文件

storage/laravels-timer-process.pid 定时器 Timer 进程的 PID 文件

storage/laravels-custom-processes.pid 所有自定义进程的 PID 文件

部署


建议通过 Supervisord 监管主进程,前提是不能加 -d 选项并且设置 swoole.daemonize false

[program:laravel-s-test]

directory=/var/wwww/laravel-s-test

command=/usr/local/bin/php bin/laravels start -i

numprocs=1

autostart=true

autorestart=true

startretries=3

user=www-data

redirect_stderr=true

stdout_logfile=/var/log/supervisor/%(program_name)s.log

Nginx 配合使用(推荐)


示例。

gzip on;

gzip_min_length 1024;

gzip_comp_level 2;

gzip_types text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml application/x-httpd-php image/jpeg image/gif image/png font/ttf font/otf image/svg+xml;

gzip_vary on;

gzip_disable "msie6";

upstream swoole {

    # 通过 IP:Port 连接

    server 127.0.0.1:5200 weight=5 max_fails=3 fail_timeout=30s;

    # 通过 UnixSocket Stream 连接,小诀窍:将socket文件放在/dev/shm目录下,可获得更好的性能

    #server unix:/xxxpath/laravel-s-test/storage/laravels.sock weight=5 max_fails=3 fail_timeout=30s;

    #server 192.168.1.1:5200 weight=3 max_fails=3 fail_timeout=30s;

    #server 192.168.1.2:5200 backup;

    keepalive 16;

}

server {

    listen 80;

    # 别忘了绑Host

    server_name laravels.com;

    root /xxxpath/laravel-s-test/public;

    access_log /yyypath/log/nginx/$server_name.access.log  main;

    autoindex off;

    index index.html index.htm;

    # Nginx处理静态资源(建议开启gzip)LaravelS处理动态资源。

    location / {

        try_files $uri @laravels;

    }

    # 当请求PHP文件时直接响应404,防止暴露public/*.php

    #location ~* \.php$ {

    #    return 404;

    #}

    location @laravels {

        # proxy_connect_timeout 60s;

        # proxy_send_timeout 60s;

        # proxy_read_timeout 120s;

        proxy_http_version 1.1;

        proxy_set_header Connection "";

        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_pass http://swoole;

    }

}

Apache 配合使用


LoadModule proxy_module /yyypath/modules/mod_deflate.so

    SetOutputFilter DEFLATE

    DeflateCompressionLevel 2

    AddOutputFilterByType DEFLATE text/html text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml application/x-httpd-php image/jpeg image/gif image/png font/ttf font/otf image/svg+xml


    # 别忘了绑Host

    ServerName www.laravels.com

    ServerAdmin hhxsv5@sina.com


    DocumentRoot /xxxpath/laravel-s-test/public;

    DirectoryIndex index.html index.htm

   

        AllowOverride None

        Require all granted

   


    LoadModule proxy_module /yyypath/modules/mod_proxy.so

    LoadModule proxy_module /yyypath/modules/mod_proxy_balancer.so

    LoadModule proxy_module /yyypath/modules/mod_lbmethod_byrequests.so.so

    LoadModule proxy_module /yyypath/modules/mod_proxy_http.so.so

    LoadModule proxy_module /yyypath/modules/mod_slotmem_shm.so

    LoadModule proxy_module /yyypath/modules/mod_rewrite.so


    ProxyRequests Off

    ProxyPreserveHost On

     

        BalancerMember http://192.168.1.1:5200 loadfactor=7

        #BalancerMember http://192.168.1.2:5200 loadfactor=3

        #BalancerMember http://192.168.1.3:5200 loadfactor=1 status=+H

        ProxySet lbmethod=byrequests

   

    #ProxyPass / balancer://laravels/

    #ProxyPassReverse / balancer://laravels/


    # Apache处理静态资源,LaravelS处理动态资源。

    RewriteEngine On

    RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} !-d

    RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} !-f

    RewriteRule ^/(.*)$ balancer://laravels/%{REQUEST_URI} [P,L]


    ErrorLog ${APACHE_LOG_DIR}/www.laravels.com.error.log

    CustomLog ${APACHE_LOG_DIR}/www.laravels.com.access.log combined

启用 WebSocket 服务器


WebSocket 服务器监听的 IP 和端口与 Http 服务器相同。

1. 创建 WebSocket Handler 类,并实现接口 WebSocketHandlerInterfacestart 时会自动实例化,不需要手动创建实例。


namespace App\Services;

use Hhxsv5\LaravelS\Swoole\WebSocketHandlerInterface;

use Swoole\Http\Request;

use Swoole\WebSocket\Frame;

use Swoole\WebSocket\Server;

/**

 * @see https://wiki.swoole.com/wiki/page/400.html

 */

class WebSocketService implements WebSocketHandlerInterface

{

    // 声明没有参数的构造函数

    public function __construct()

    {

    }

    public function onOpen(Server $server, Request $request)

    {

        // 在触发onOpen事件之前,建立WebSocketHTTP请求已经经过了Laravel的路由,

        // 所以LaravelRequestAuth等信息是可读的,Session是可读写的,但仅限在onOpen事件中。

        // \Log::info('New WebSocket connection', [$request->fd, request()->all(), session()->getId(), session('xxx'), session(['yyy' => time()])]);

        $server->push($request->fd, 'Welcome to LaravelS');

        // throw new \Exception('an exception');// 此时抛出的异常上层会忽略,并记录到Swoole日志,需要开发者try/catch捕获处理

    }

    public function onMessage(Server $server, Frame $frame)

    {

        // \Log::info('Received message', [$frame->fd, $frame->data, $frame->opcode, $frame->finish]);

        $server->push($frame->fd, date('Y-m-d H:i:s'));

        // throw new \Exception('an exception');// 此时抛出的异常上层会忽略,并记录到Swoole日志,需要开发者try/catch捕获处理

    }

    public function onClose(Server $server, $fd, $reactorId)

    {

        // throw new \Exception('an exception');// 此时抛出的异常上层会忽略,并记录到Swoole日志,需要开发者try/catch捕获处理

    }

}

2. 更改配置 config/laravels.php


// ...

'websocket'      => [

    'enable'  => true, // 看清楚,这里是true

    'handler' => \App\Services\WebSocketService::class,

],

'swoole'         => [

    //...

    // dispatch_mode只能设置为245https://wiki.swoole.com/wiki/page/277.html

    'dispatch_mode' => 2,

    //...

],

// ...

3. 使用 SwooleTable 绑定 FD UserId,可选的,Swoole Table 示例。也可以用其他全局存储服务,例如 Redis/Memcached/MySQL,但需要注意多个 Swoole Server 实例时 FD 可能冲突。


4. Nginx 配合使用(推荐)


参考 WebSocket 代理

map $http_upgrade $connection_upgrade {

    default upgrade;

    ''      close;

}

upstream swoole {

    # 通过 IP:Port 连接

    server 127.0.0.1:5200 weight=5 max_fails=3 fail_timeout=30s;

    # 通过 UnixSocket Stream 连接,小诀窍:将socket文件放在/dev/shm目录下,可获得更好的性能

    #server unix:/xxxpath/laravel-s-test/storage/laravels.sock weight=5 max_fails=3 fail_timeout=30s;

    #server 192.168.1.1:5200 weight=3 max_fails=3 fail_timeout=30s;

    #server 192.168.1.2:5200 backup;

    keepalive 16;

}

server {

    listen 80;

    # 别忘了绑Host

    server_name laravels.com;

    root /xxxpath/laravel-s-test/public;

    access_log /yyypath/log/nginx/$server_name.access.log  main;

    autoindex off;

    index index.html index.htm;

    # Nginx处理静态资源(建议开启gzip)LaravelS处理动态资源。

    location / {

        try_files $uri @laravels;

    }

    # 当请求PHP文件时直接响应404,防止暴露public/*.php

    #location ~* \.php$ {

    #    return 404;

    #}

    # HttpWebSocket共存,Nginx通过location区分

    # !!! WebSocket连接时路径为/ws

    # Javascript: var ws = new WebSocket("ws://laravels.com/ws");

    location =/ws {

        # proxy_connect_timeout 60s;

        # proxy_send_timeout 60s;

        # proxy_read_timeout:如果60秒内被代理的服务器没有响应数据给Nginx,那么Nginx会关闭当前连接;同时,Swoole的心跳设置也会影响连接的关闭

        # proxy_read_timeout 60s;

        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://swoole;

    }

    location @laravels {

        # proxy_connect_timeout 60s;

        # proxy_send_timeout 60s;

        # proxy_read_timeout 60s;

        proxy_http_version 1.1;

        proxy_set_header Connection "";

        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_pass http://swoole;

    }

}

5. 心跳配置


Swoole 的心跳配置


// config/laravels.php

'swoole' => [

    //...

    // 表示每60秒遍历一次,一个连接如果600秒内未向服务器发送任何数据,此连接将被强制关闭

    'heartbeat_idle_time'      => 600,

    'heartbeat_check_interval' => 60,

    //...

],

Nginx 读取代理服务器超时的配置


# 如果60秒内被代理的服务器没有响应数据给Nginx,那么Nginx会关闭当前连接

proxy_read_timeout 60s;

6. 在控制器中推送数据


namespace App\Http\Controllers;

class TestController extends Controller

{

    public function push()

    {

        $fd = 1; // Find fd by userId from a map [userId=>fd].

        /**@var \Swoole\WebSocket\Server $swoole */

        $swoole = app('swoole');

        $success = $swoole->push($fd, 'Push data to fd#1 in Controller');

        var_dump($success);

    }

}

监听事件


系统事件


通常,你可以在这些事件中重置或销毁一些全局或静态的变量,也可以修改当前的请求和响应。

laravels.received_request Swoole\Http\Request 转成 Illuminate\Http\Request 后,在 Laravel 内核处理请求前。


// 修改`app/Providers/EventServiceProvider.php`, 添加下面监听代码到boot方法中

// 如果变量$events不存在,你也可以通过Facade调用\Event::listen()

$events->listen('laravels.received_request', function (\Illuminate\Http\Request $req, $app) {

    $req->query->set('get_key', 'hhxsv5');// 修改querystring

    $req->request->set('post_key', 'hhxsv5'); // 修改post body

});

laravels.generated_response Laravel 内核处理完请求后,将 Illuminate\Http\Response 转成 Swoole\Http\Response 之前 (下一步将响应给客户端)


// 修改`app/Providers/EventServiceProvider.php`, 添加下面监听代码到boot方法中

// 如果变量$events不存在,你也可以通过Facade调用\Event::listen()

$events->listen('laravels.generated_response', function (\Illuminate\Http\Request $req, \Symfony\Component\HttpFoundation\Response $rsp, $app) {

    $rsp->headers->set('header-key', 'hhxsv5');// 修改header

});

自定义的异步事件


此特性依赖 Swoole AsyncTask,必须先设置 config/laravels.php swoole.task_worker_num。异步事件的处理能力受 Task 进程数影响,需合理设置 task_worker_num

1. 创建事件类。


use Hhxsv5\LaravelS\Swoole\Task\Event;

class TestEvent extends Event

{

    private $data;

    public function __construct($data)

    {

        $this->data = $data;

    }

    public function getData()

    {

        return $this->data;

    }

}

2. 创建监听器类。


use Hhxsv5\LaravelS\Swoole\Task\Task;

use Hhxsv5\LaravelS\Swoole\Task\Event;

use Hhxsv5\LaravelS\Swoole\Task\Listener;

class TestListener1 extends Listener

{

    // 声明没有参数的构造函数

    public function __construct()

    {

    }

    public function handle(Event $event)

    {

        \Log::info(__CLASS__ . ':handle start', [$event->getData()]);

        sleep(2);// 模拟一些慢速的事件处理

        // 监听器中也可以投递Task,但不支持Taskfinish()回调。

        // 注意:config/laravels.php中修改配置task_ipc_mode12,参考 https://wiki.swoole.com/wiki/page/296.html

        $ret = Task::deliver(new TestTask('task data'));

        var_dump($ret);

        // throw new \Exception('an exception');// handle时抛出的异常上层会忽略,并记录到Swoole日志,需要开发者try/catch捕获处理

    }

}

3. 绑定事件与监听器。


// "config/laravels.php"中绑定事件与监听器,一个事件可以有多个监听器,多个监听器按顺序执行

[

    // ...

    'events' => [

        \App\Tasks\TestEvent::class => [

            \App\Tasks\TestListener1::class,

            //\App\Tasks\TestListener2::class,

        ],

    ],

    // ...

];

4. 触发事件。


// 实例化TestEvent并通过fire触发,此操作是异步的,触发后立即返回,由Task进程继续处理监听器中的handle逻辑

use Hhxsv5\LaravelS\Swoole\Task\Event;

$event = new TestEvent('event data');

// $event->delay(10); // 延迟10秒触发

// $event->setTries(3); // 出现异常时,累计尝试3

$success = Event::fire($event);

var_dump($success);// 判断是否触发成功

异步的任务队列


此特性依赖 Swoole AsyncTask,必须先设置 config/laravels.php swoole.task_worker_num。异步任务的处理能力受 Task 进程数影响,需合理设置 task_worker_num

1. 创建任务类。


use Hhxsv5\LaravelS\Swoole\Task\Task;

class TestTask extends Task

{

    private $data;

    private $result;

    public function __construct($data)

    {

        $this->data = $data;

    }

    // 处理任务的逻辑,运行在Task进程中,不能投递任务

    public function handle()

    {

        \Log::info(__CLASS__ . ':handle start', [$this->data]);

        sleep(2);// 模拟一些慢速的事件处理

        // throw new \Exception('an exception');// handle时抛出的异常上层会忽略,并记录到Swoole日志,需要开发者try/catch捕获处理

        $this->result = 'the result of ' . $this->data;

    }

    // 可选的,完成事件,任务处理完后的逻辑,运行在Worker进程中,可以投递任务

    public function finish()

    {

        \Log::info(__CLASS__ . ':finish start', [$this->result]);

        Task::deliver(new TestTask2('task2')); // 投递其他任务

    }

}

2. 投递任务。


// 实例化TestTask并通过deliver投递,此操作是异步的,投递后立即返回,由Task进程继续处理TestTask中的handle逻辑

use Hhxsv5\LaravelS\Swoole\Task\Task;

$task = new TestTask('task data');

// $task->delay(3); // 延迟3秒投递任务

// $task->setTries(3); // 出现异常时,累计尝试3

$ret = Task::deliver($task);

var_dump($ret);// 判断是否投递成功

毫秒级定时任务


基于 Swoole 的毫秒定时器,封装的定时任务,取代 Linux Crontab

1. 创建定时任务类。


namespace App\Jobs\Timer;

use App\Tasks\TestTask;

use Swoole\Coroutine;

use Hhxsv5\LaravelS\Swoole\Task\Task;

use Hhxsv5\LaravelS\Swoole\Timer\CronJob;

class TestCronJob extends CronJob

{

    protected $i = 0;

    // !!! 定时任务的`interval``isImmediate`有两种配置方式(二选一):一是重载对应的方法,二是注册定时任务时传入参数。

    // --- 重载对应的方法来返回配置:开始

    public function interval()

    {

        return 1000;// 1秒运行一次

    }

    public function isImmediate()

    {

        return false;// 是否立即执行第一次,false则等待间隔时间后执行第一次

    }

    // --- 重载对应的方法来返回配置:结束

    public function run()

    {

        \Log::info(__METHOD__, ['start', $this->i, microtime(true)]);

        // do something

        // sleep(1); // Swoole < 2.1

        Coroutine::sleep(1); // Swoole>=2.1 run()方法已自动创建了协程。

        $this->i++;

        \Log::info(__METHOD__, ['end', $this->i, microtime(true)]);


        if ($this->i >= 10) { // 运行10次后不再执行

            \Log::info(__METHOD__, ['stop', $this->i, microtime(true)]);

            $this->stop(); // 终止此任务

            // CronJob中也可以投递Task,但不支持Taskfinish()回调。

            // 注意:修改config/laravels.php,配置task_ipc_mode12,参考 https://wiki.swoole.com/wiki/page/296.html

            $ret = Task::deliver(new TestTask('task data'));

            var_dump($ret);

        }

        // throw new \Exception('an exception');// 此时抛出的异常上层会忽略,并记录到Swoole日志,需要开发者try/catch捕获处理

    }

}

2. 注册定时任务类。


// "config/laravels.php"注册定时任务类

[

    // ...

    'timer'          => [

        'enable' => true, // 启用Timer

        'jobs'   => [ // 注册的定时任务类列表

            // 启用LaravelScheduleJob来执行`php artisan schedule:run`,每分钟一次,替代Linux Crontab

            // \Hhxsv5\LaravelS\Illuminate\LaravelScheduleJob::class,

            // 两种配置参数的方式:

            // [\App\Jobs\Timer\TestCronJob::class, [1000, true]], // 注册时传入参数

            \App\Jobs\Timer\TestCronJob::class, // 重载对应的方法来返回参数

        ],

        'max_wait_time' => 5, // Reload时最大等待时间

    ],

    // ...

];

3. 注意在构建服务器集群时,会启动多个定时器,要确保只启动一个定期器,避免重复执行定时任务。


4.LaravelS v3.4.0 开始支持热重启 [Reload] 定时器进程,LaravelS 在收到 SIGUSR1 信号后会等待 max_wait_time(默认 5) 秒再结束进程,然后 Manager 进程会重新拉起定时器进程。


修改代码后自动 Reload


基于 inotify,仅支持 Linux


1. 安装 inotify 扩展。


2. 开启配置项。


3. 注意:inotify 只有在 Linux 内修改文件才能收到文件变更事件,建议使用最新版 DockerVagrant 解决方案。


基于 fswatch,支持 OS XLinuxWindows


1. 安装 fswatch


2. 在项目根目录下运行命令。


# 监听当前目录

./bin/fswatch

# 监听app目录

./bin/fswatch ./app

基于 inotifywait,仅支持 Linux


1. 安装 inotify-tools


2. 在项目根目录下运行命令。


# 监听当前目录

./bin/inotify

# 监听app目录

./bin/inotify ./app

当以上方法都不行时,终极解决方案:配置 max_request=1,worker_num=1,这样 Worker 进程处理完一个请求就会重启,这种方法的性能非常差,故仅限在开发环境使用。


在你的项目中使用 SwooleServer 实例


/**

 * 如果启用WebSocket server$swoole`Swoole\WebSocket\Server`的实例,否则是是`Swoole\Http\Server`的实例

 * @var \Swoole\WebSocket\Server|\Swoole\Http\Server $swoole

 */

$swoole = app('swoole');

var_dump($swoole->stats());// 单例

使用 SwooleTable


1. 定义 Table,支持定义多个 Table


Swoole 启动之前会创建定义的所有 Table

// "config/laravels.php"配置

[

    // ...

    'swoole_tables'  => [

        // 场景:WebSocketUserIdFD绑定

        'ws' => [// KeyTable名称,使用时会自动添加Table后缀,避免重名。这里定义名为wsTableTable

            'size'   => 102400,//Table的最大行数

            'column' => [// Table的列定义

                ['name' => 'value', 'type' => \Swoole\Table::TYPE_INT, 'size' => 8],

            ],

        ],

        //...继续定义其他Table

    ],

    // ...

];

2. 访问 Table:所有的 Table 实例均绑定在 SwooleServer 上,通过 app('swoole')->xxxTable 访问。


namespace App\Services;

use Hhxsv5\LaravelS\Swoole\WebsocketHandlerInterface;

use Swoole\Http\Request;

use Swoole\WebSocket\Frame;

use Swoole\WebSocket\Server;

class WebSocketService implements WebSocketHandlerInterface

{

    /**@var \Swoole\Table $wsTable */

    private $wsTable;

    public function __construct()

    {

        $this->wsTable = app('swoole')->wsTable;

    }

    // 场景:WebSocketUserIdFD绑定

    public function onOpen(Server $server, Request $request)

    {

        // var_dump(app('swoole') === $server);// 同一实例

        /**

         * 获取当前登录的用户

         * 此特性要求建立WebSocket连接的路径要经过Authenticate之类的中间件。

         * 例如:

         * 浏览器端:var ws = new WebSocket("ws://127.0.0.1:5200/ws");

         * 那么Laravel/ws路由就需要加上类似Authenticate的中间件。

         */

        // $user = Auth::user();

        // $userId = $user ? $user->id : 0; // 0 表示未登录的访客用户

        $userId = mt_rand(1000, 10000);

        $this->wsTable->set('uid:' . $userId, ['value' => $request->fd]);// 绑定uidfd的映射

        $this->wsTable->set('fd:' . $request->fd, ['value' => $userId]);// 绑定fduid的映射

        $server->push($request->fd, "Welcome to LaravelS #{$request->fd}");

    }

    public function onMessage(Server $server, Frame $frame)

    {

        // 广播

        foreach ($this->wsTable as $key => $row) {

            if (strpos($key, 'uid:') === 0 && $server->isEstablished($row['value'])) {

                $content = sprintf('Broadcast: new message "%s" from #%d', $frame->data, $frame->fd);

                $server->push($row['value'], $content);

            }

        }

    }

    public function onClose(Server $server, $fd, $reactorId)

    {

        $uid = $this->wsTable->get('fd:' . $fd);

        if ($uid !== false) {

            $this->wsTable->del('uid:' . $uid['value']); // 解绑uid映射

        }

        $this->wsTable->del('fd:' . $fd);// 解绑fd映射

        $server->push($fd, "Goodbye #{$fd}");

    }

}

多端口混合协议


更多的信息,请参考 Swoole 增加监听的端口与多端口混合协议

为了使我们的主服务器能支持除 HTTP WebSocket 外的更多协议,我们引入了 Swoole 的多端口混合协议特性,在 LaravelS 中称为 Socket。现在,可以很方便地在 Laravel 上构建 TCP/UDP 应用。


创建 Socket 处理类,继承 Hhxsv5\LaravelS\Swoole\Socket\{TcpSocket|UdpSocket|Http|WebSocket}


namespace App\Sockets;

use Hhxsv5\LaravelS\Swoole\Socket\TcpSocket;

use Swoole\Server;

class TestTcpSocket extends TcpSocket

{

    public function onConnect(Server $server, $fd, $reactorId)

    {

        \Log::info('New TCP connection', [$fd]);

        $server->send($fd, 'Welcome to LaravelS.');

    }

    public function onReceive(Server $server, $fd, $reactorId, $data)

    {

        \Log::info('Received data', [$fd, $data]);

        $server->send($fd, 'LaravelS: ' . $data);

        if ($data === "quit\r\n") {

            $server->send($fd, 'LaravelS: bye' . PHP_EOL);

            $server->close($fd);

        }

    }

    public function onClose(Server $server, $fd, $reactorId)

    {

        \Log::info('Close TCP connection', [$fd]);

        $server->send($fd, 'Goodbye');

    }

}

这些连接和主服务器上的 HTTP/WebSocket 连接共享 Worker 进程,因此可以在这些事件操作中使用 LaravelS 提供的异步任务投递、SwooleTableLaravel 提供的组件如 DBEloquent 等。同时,如果需要使用该协议端口的 Swoole\Server\Port 对象,只需要像如下代码一样访问 Socket 类的成员 swoolePort 即可。


public function onReceive(Server $server, $fd, $reactorId, $data)

{

    $port = $this->swoolePort; //获得`Swoole\Server\Port`对象

}

注册套接字。


// 修改文件 config/laravels.php

// ...

'sockets' => [

    [

        'host'     => '127.0.0.1',

        'port'     => 5291,

        'type'     => SWOOLE_SOCK_TCP,// 支持的嵌套字类型:https://wiki.swoole.com/wiki/page/16.html#entry_h2_0

        'settings' => [// Swoole可用的配置项:https://wiki.swoole.com/wiki/page/526.html

            'open_eof_check' => true,

            'package_eof'    => "\r\n",

        ],

        'handler'  => \App\Sockets\TestTcpSocket::class,

    ],

],

关于心跳配置,只能设置在主服务器上,不能配置在套接字上,但套接字会继承主服务器的心跳配置。


对于 TCP 协议,dispatch_mode 选项设为 1/3 时,底层会屏蔽 onConnect/onClose 事件,原因是这两种模式下无法保证 onConnect/onClose/onReceive 的顺序。如果需要用到这两个事件,请将 dispatch_mode 改为 2/4/5,参考。


'swoole' => [

    //...

    'dispatch_mode' => 2,

    //...

];

测试。


TCPtelnet 127.0.0.1 5291


UDPLinux echo "Hello LaravelS" > /dev/udp/127.0.0.1/5292


其他协议的注册示例。


UDP


'sockets' => [

[

    'host'     => '0.0.0.0',

    'port'     => 5292,

    'type'     => SWOOLE_SOCK_UDP,

    'settings' => [

        'open_eof_check' => true,

        'package_eof'    => "\r\n",

    ],

    'handler'  => \App\Sockets\TestUdpSocket::class,

],

],

Http


'sockets' => [

[

    'host'     => '0.0.0.0',

    'port'     => 5293,

    'type'     => SWOOLE_SOCK_TCP,

    'settings' => [

        'open_http_protocol' => true,

    ],

    'handler'  => \App\Sockets\TestHttp::class,

],

],

WebSocket:主服务器必须开启WebSocket,即需要将 websocket.enable 置为 true


'sockets' => [

[

    'host'     => '0.0.0.0',

    'port'     => 5294,

    'type'     => SWOOLE_SOCK_TCP,

阅读量! (1071)

关于本站

昵称:无痕小Q

职业:php-go-web开发工程师

现居:北京

Email:1838638884@qq.com

    苏轼 明月未出群山高,瑞光千丈生白毫。  一杯未尽银阙涌,乱云脱坏如崩涛。  谁为天公洗眸子,应费明河千斛水。  遂令冷看世间人,照我湛然心不起。  西南火星如弹丸,角尾奕奕苍龙蟠。  今宵注眼看不见

网站公告

  • 欢迎来到我的博客

  • 1:欢迎来到我的博客


    2:博客免费api接口现已上线


    3:博客会定期更新文章


    4:欢迎大家来捧场


    其一 天街小雨润如酥,草色遥看近却无。 最是一年春好处,绝胜烟柳满皇都。 其二 莫道官忙身老大,即无年少逐春心。 凭君先到江头看,柳色如今深未深。

站点信息

山川异域,风月同天。  寄诸佛子,共结来缘。 生活是一位睿智的长者,生活是一位博学的老师,它常常春风化雨,润物无声地为我们指点迷津,给我们人生的启迪。
  • 建站时间:2019-8-30
  • 网站程序:php,laravel-swoole框架
  • 今日流量:71(10分钟统计一次)
  • 本月流量:9412
  • 浏览总量:388339
  • 统计方式:中间件,redis消息队列,定时任务
    君不见黄河之水天上来,奔流到海不复回。 君不见高堂明镜悲白发,朝如青丝暮成雪。 人生得意须尽欢,莫使金樽空对月。 天生我材必有用,千金散尽还复来。 烹羊宰牛且为乐,会须一饮三百杯。