laravels
是一个胶水项目,用于快速集成swoole
到laravel
或lumen
,然后赋予它们更好的性能、更多可能性。github
特性
内置http/websocket服务器多端口混合协议协程自定义进程常驻内存异步的事件监听异步的任务队列毫秒级定时任务平滑reload修改代码后自动reload同时支持laravel与lumen,兼容主流版本简单,开箱即用>= 5.5.9
推荐php7+
swoole>= 1.7.19
从2.0.12开始不再支持php5
推荐4.2.3+
laravel/lumen>= 5.1
推荐5.6+
1.通过compor安装()。有可能找不到3.0
版本,解决方案移步。
compor require "hhxsv5/laravel-s:~3.5.0" -vvv# 确保你的compor.lock文件是在版本控制中
2.注册rvice provider(以下两步二选一)。
laravel
: 修改文件config/app.php
,laravel 5.5+支持包自动发现,你应该跳过这步
'providers' => [ //... hhxsv5\laravels\illuminate\laravelsrviceprovider::class,],
lumen
: 修改文件bootstrap/app.php
$app->register(hhxsv5\laravels\illuminate\laravelsrviceprovider::class);
3.发布配置和二进制文件。
每次升级laravels后,需重新publish;点击relea去了解各个版本的变更记录。
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
停止laravelsrestart
重启laravels,支持选项 “-d|–daemonize” 和 “-e|–env“reload
平滑重启所有task/worker/timer进程(这些进程内包含了你的业务代码),并触发自定义进程的onreload
方法,不会重启master/manger进程;修改config/laravels.php
后,你只能
调用restart
来实现重启info
显示组件的版本信息help
显示帮助信息建议通过supervisord监管主进程,前提是不能加
-d
选项并且设置swoole.daemonize
为fal
。
[program:laravel-s-test]command=/ur/local/bin/php /opt/www/laravel-s-test/bin/laravels start -inumprocs=1autostart=trueautorestart=truestartretries=3ur=www-dataredirect_stderr=truestdout_logfile=/opt/www/laravel-s-test/storage/logs/supervisord-stdout.log
。
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 连接 rver 127.0.0.1:5200 weight=5 max_fails=3 fail_timeout=30s; # 通过 unixsocket stream 连接,小诀窍:将socket文件放在/dev/shm目录下,可获得更好的性能 #rver unix:/xxxpath/laravel-s-test/storage/laravels.sock weight=5 max_fails=3 fail_timeout=30s; #rver 192.168.1.1:5200 weight=3 max_fails=3 fail_timeout=30s; #rver 192.168.1.2:5200 backup; keepalive 16;}rver { listen 80; # 别忘了绑host哟 rver_name laravels.com; root /xxxpath/laravel-s-test/public; access_log /yyypath/log/nginx/$rver_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_nd_timeout 60s; # proxy_read_timeout 120s; proxy_http_version 1.1; proxy_t_header connection ""; proxy_t_header x-real-ip $remote_addr; proxy_t_header x-real-port $remote_port; proxy_t_header x-forwarded-for $proxy_add_x_forwarded_for; proxy_t_header host $http_host; proxy_t_header scheme $scheme; proxy_t_header rver-protocol $rver_protocol; proxy_t_header rver-name $rver_name; proxy_t_header rver-addr $rver_addr; proxy_t_header rver-port $rver_port; proxy_pass http://swoole; }}狐臭如何治疗
1 loadmodule proxy_module /yyypath/modules/mod_deflate.so 2 <ifmodule deflate_module> 3 toutputfilter deflate 4 deflatecompressionlevel 2 5 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 6 </ifmodule> 7 8 <virtualhost *:80> 9 # 别忘了绑host哟10 rvername www.laravels.com11 rveradmin hhxsv5@sina.com12 13 documentroot /xxxpath/laravel-s-test/public;14 directoryindex index.html index.htm15 <directory "/">16 allowoverride none17 require all granted18 </directory>19 20 loadmodule proxy_module /yyypath/modules/mod_proxy.so21 loadmodule proxy_module /yyypath/modules/mod_proxy_balancer.so22 loadmodule proxy_module /yyypath/modules/mod_lbmethod_byrequests.so.so23 loadmodule proxy_module /yyypath/modules/mod_proxy_http.so.so24 loadmodule proxy_module /yyypath/modules/mod_slotmem_shm.so25 loadmodule proxy_module /yyypath/modules/mod_rewrite.so26 27 proxyrequests off28 proxyprervehost on29 <proxy balancer://laravels> 30 balancermember http://192.168.1.1:5200 loadfactor=731 #balancermember http://192.168.1.2:5200 loadfactor=332 #balancermember http://192.168.1.3:5200 loadfactor=1 status=+h33 proxyt lbmethod=byrequests34 </proxy>35 #proxypass / balancer://laravels/36 #proxypassrever / balancer://laravels/37 38 # apache处理静态资源,laravels处理动态资源。39 rewriteengine on40 rewritecond %{document_root}%{request_filename} !-d41 rewritecond %{document_root}%{request_filename} !-f42 rewriterule ^/(.*)$ balancer://laravels/%{request_uri} [p,l]43 44 errorlog ${apache_log美国南北战争_dir}/www.laravels.com.error.log45 customlog ${apache_log_dir}/www.laravels.com.access.log combined46 </virtualhost>
websocket服务器监听的ip和端口与http服务器相同。
1.创建websocket handler类,并实现接口websockethandlerinterface
。start时会自动实例化,不需要手动创建实例。
1 namespace app\rvices; 2 u hhxsv5\laravels\swoole\websockethandlerinterface; 3 u swoole\http\request; 4 u swoole\websocket\frame; 5 u swoole\websocket\rver; 6 /** 7 * @e https://wiki.swoole.com/wiki/page/400.html 8 */ 9 class websocketrvice implements websockethandlerinterface10 {11 // 声明没有参数的构造函数12 public function __construct()13 {14 }15 public function onopen(rver $rver, request $request)16 {17 // 在触发onopen事件之前,建立websocket的http请求已经经过了laravel的路由,18 // 所以laravel的request、auth等信息是可读的,ssion是可读写的,但仅限在onopen事件中。19 // \log::info('new websocket connection', [$request->fd, request()->all(), ssion()->getid(), ssion('xxx'), ssion(['yyy' => time()])]);20 $rver->push($request->fd, 'welcome to laravels');21 // throw new \exception('an exception');// 此时抛出的异常上层会忽略,并记录到swoole日志,需要开发者try/catch捕获处理22 }23 public function onmessage(rver $rver, frame $frame)24 {25 // \log::info('received message', [$frame->fd, $frame->data, $frame->opcode, $frame->finish]);26 $rver->push($frame->fd, date('y-m-d h:i:s'));27 // throw new \exception('an exception');// 此时抛出的异常上层会忽略,并记录到swoole日志,需要开发者try/catch捕获处理28 }29 public function onclo(rver $rver, $fd, $reactorid)30 {31 // throw new \exception('an exception');// 此时抛出的异常上层会忽略,并记录到swoole日志,需要开发者try/catch捕获处理32 }33 }
2.更改配置config/laravels.php
。
1 // ... 2 'websocket' => [ 3 'enable' => true, // 看清楚,这里是true 4 'handler' => \app\rvices\websocketrvice::class, 5 ], 6 'swoole' => [ 7 //... 8 // dispatch_mode只能设置为2、4、5,https://wiki.swoole.com/wiki/page/277.html 9 'dispatch_mode' => 2,10 //...11 ],12 // ...
3.使用swooletable
绑定fd与urid,可选的,swoole table示例。也可以用其他全局存储服务,例如redis/memcached/mysql,但需要注意多个swoole rver
实例时fd可能冲突。
4.与nginx配合使用(推荐)
参考websocket代理
1 map $http_upgrade $connection_upgrade { 2 default upgrade; 3 '' clo; 4 } 5 upstream swoole { 6 # 通过 ip:port 连接 7 rver 127.0.0.1:5200 weight=5 max_fails=3 fail_timeout=30s; 8 # 通过 unixsocket stream 连接,小诀窍:将socket文件放在/dev/shm目录下,可获得更好的性能 9 #rver unix:/xxxpath/laravel-s-test/storage/laravels.sock weight=5 max_fails=3 fail_timeout=30s;10 #rver 192.168.1.1:5200 weight=3 max_fails=3 fail_timeout=30s;11 #rver 192.168.1.2:5200 backup;12 keepalive 16;13 }14 rver {15 listen 80;16五邑大学分数线 # 别忘了绑host哟17 rver_name laravels.com;18 root /xxxpath/laravel-s-test/public;19 access_log /yyypath/log/nginx/$rver_name.access.log main;20 autoindex off;21 index index.html index.htm;22 # nginx处理静态资源(建议开启gzip),laravels处理动态资源。23 location / {24 try_files $uri @laravels;25 }26 # 当请求php文件时直接响应404,防止暴露public/*.php27 #location ~* \.php$ {28 # return 404;29 #}30 # http和websocket共存,nginx通过location区分31 # !!! websocket连接时路径为/ws32 # javascript: var ws = new websocket("ws://laravels.com/ws");33 location =/ws {34 # proxy_connect_timeout 60s;35 # proxy_nd_timeout 60s;36 # proxy_read_timeout:如果60秒内被代理的服务器没有响应数据给nginx,那么nginx会关闭当前连接;同时,swoole的心跳设置也会影响连接的关闭37 # proxy_read_timeout 60s;38 proxy_http_version 1.1;39 proxy_t_header x-real-ip $remote_addr;40 proxy_t_header x-real-port $remote_port;41 proxy_t_header x-forwarded-for $proxy_add_x_forwarded_for;42 proxy_t_header host $http_host;43 proxy_t_header scheme $scheme;44 proxy_t_header rver-protocol $rver_protocol;45 proxy_t_header rver-name $rver_name;46 proxy_t_header rver-addr $rver_addr;47 proxy_t_header rver-port $rver_port;48 proxy_t_header upgrade $http_upgrade;49 proxy_t_header connection $connection_upgrade;50 proxy_pass http://swoole;51 }52 location @laravels {53 # proxy_connect_timeout 60s;54 # proxy_nd_timeout 60s;55 # proxy_read_timeout 60s;56 proxy_http_version 1.1;57 proxy_t_header connection "";58 proxy_t_header x-real-ip $remote_addr;59 proxy_t_header x-real-port $remote_port;60 proxy_t_header x-forwarded-for $proxy_add_x_forwarded_for;61 proxy_t_header host $http_host;62 proxy_t_header scheme $scheme;63 proxy_t_header rver-protocol $rver_protocol;64 proxy_t_header rver-name $rver_name;65 proxy_t_header rver-addr $rver_addr;66 proxy_t_header rver-port $rver_port;67 proxy_pass http://swoole;68 }69 }
5.心跳配置
swoole的心跳配置
1 // config/laravels.php2 'swoole' => [3 //...4 // 表示每60秒遍历一次,一个连接如果600秒内未向服务器发送任何数据,此连接将被强制关闭5 'heartbeat_idle_time' => 600,6 'heartbeat_check_interval' => 60,7 //...8 ],
nginx读取代理服务器超时的配置
# 如果60秒内被代理的服务器没有响应数据给nginx,那么nginx会关闭当前连接proxy_read_timeout 60s;
通常,你可以在这些事件中重置或销毁一些全局或静态的变量,也可以修改当前的请求和响应。
laravels.received_request
将swoole\http\request
转成illuminate\http\request
后,在laravel内核处理请求前。
1 // 修改`app/providers/eventrviceprovider.php`, 添加下面监听代码到boot方法中2 // 如果变量$events不存在,你也可以通过facade调用\event::listen()。3 $events->listen('laravels.received_request', function (\illuminate\http\request $req, $app) {4 $req->query->t('get_key', 'hhxsv5');// 修改querystring5 $req->request->t('post_key', 'hhxsv5'); // 修改post body6 });
laravels.generated_respon
在laravel内核处理完请求后,将illuminate\http\respon
转成swoole\http\respon
之前(下一步将响应给客户端)。
1 // 修改`app/providers/eventrviceprovider.php`, 添加下面监听代码到boot方法中2 // 如果变量$events不存在,你也可以通过facade调用\event::listen()。3 $events->listen('laravels.generated_respon', function (\illuminate\http\request $req, \symfony\component\httpfoundation\respon $rsp, $app) {4 $rsp->headers->t('header-key', 'hhxsv5');// 修改header5 });
此特性依赖
swoole
的asynctask
,必须先设置config/laravels.php
的swoole.task_worker_num
。异步事件的处理能力受task进程数影响,需合理设置。
1.创建事件类。
1 u hhxsv5\laravels\swoole\task\event; 2 class testevent extends event 3 { 4 private $data; 5 public function __construct($data) 6 { 7 $this->data = $data; 8 } 9 public function getdata()10 {11 return $this->data;12 }13 }
2.创建监听器类。
1 u hhxsv5\laravels\swoole\task\task; 2 u hhxsv5\laravels\swoole\task\event; 3 u hhxsv5\laravels\swoole\task\listener; 4 class testlistener1 extends listener 5 { 6 // 声明没有参数的构造函数 7 public function __construct() 8 { 9 }10 public function handle(event $event)11 {12 \log::info(__class__ . ':handle start', [$event->getdata()]);13 sleep(2);// 模拟一些慢速的事件处理14 // 监听器中也可以投递task,但不支持task的finish()回调。15 // 注意:16 // 1.参数2需传true17 // 2.config/laravels.php中修改配置task_ipc_mode为1或2,参考 https://wiki.swoole.com/wiki/page/296.html18 $ret = task::deliver(new testtask('task data'), true);19 var_dump($ret);20 // throw new \exception('an exception');// handle时抛出的异常上层会忽略,并记录到swoole日志,需要开发者try/catch捕获处理21 }22 }
3.绑定事件与监听器。
1 // 在"config/laravels.php"中绑定事件与监听器,一个事件可以有多个监听器,多个监听器按顺序执行 2 [ 3 // ... 4 'events' => [ 5 \app\tasks\testevent::class => [ 6 \app\tasks\testlistener1::class, 7 //\app\tasks\testlistener2::class, 8 ], 9 ],10 // ...11 ];
4.触发事件。
1 // 实例化testevent并通过fire触发,此操作是异步的,触发后立即返回,由task进程继续处理监听器中的handle逻辑2 u hhxsv5\laravels\swoole\task\event;3 $success = event::fire(new testevent('event data'));4 var_dump($success);//判断是否触发成功
此特性依赖
swoole
的asynctask
,必须先设置config/laravels.php
的swoole.task_worker_num
。异步任务的处理能力受task进程数影响,需合理设置。
1.创建任务类。
1 u hhxsv5\laravels\swoole\task\task; 2 class testtask extends task 3 { 4 private $data; 5 private $result; 6 public function __construct($data) 7 { 8 $this->data = $data; 9 }10 // 处理任务的逻辑,运行在task进程中,不能投递任务11 public function handle()12 {13 \log::info(__class__ . ':handle start', [$this->data]);14 sleep(2);// 模拟一些慢速的事件处理15 // throw new \exception('an exception');// handle时抛出的异常上层会忽略,并记录到swoole日志,需要开发者try/catch捕获处理16 $this->result = 'the result of ' . $this->data;17 }18 // 可选的,完成事件,任务处理完后的逻辑,运行在worker进程中,可以投递任务19 public function finish()20 {21 \log::info(__class__ . ':finish start', [$this->result]);22 task::deliver(new testtask2('task2')); // 投递其他任务23 }24 }
2.投递任务。
1 // 实例化testtask并通过deliver投递,此操作是异步的,投递后立即返回,由task进程继续处理testtask中的handle逻辑2 u hhxsv5\laravels\swoole\task\task;3 $task = new testtask('task data');4 // $task->delay(3);// 延迟3秒投放任务5 $ret = task::deliver($task);6 var_dump($ret);//判断是否投递成功
基于swoole的毫秒定时器,封装的定时任务,取代
linux
的crontab
。
1.创建定时任务类。
1 namespace app\jobs\timer; 2 u app\tasks\testtask; 3 u swoole\coroutine; 4 u hhxsv5\laravels\swoole\task\task; 5 u hhxsv5\laravels\swoole\timer\cronjob; 6 class testcronjob extends cronjob 7 { 8 protected $i = 0; 9 // !!! 定时任务的`interval`和`isimmediate`有两种配置方式(二选一):一是重载对应的方法,二是注册定时任务时传入参数。10 // --- 重载对应的方法来返回配置:开始11 public function interval()12 {13 return 1000;// 每1秒运行一次14 }15 public function isimmediate()16 {17 return fal;// 是否立即执行第一次,fal则等待间隔时间后执行第一次18 }19 // --- 重载对应的方法来返回配置:结束20 public function run()21 {22 \log::info(__method__, ['start', $this->i, microtime(true)]);23 // do something24 // sleep(1); // swoole < 2.125 coroutine::sleep(1); // swoole>=2.1 run()方法已自动创建了协程。26 $this->i++;27 \log::info(__method__, ['end', $this->i, microtime(true)]);28 29 if ($this->i >= 10) { // 运行10次后不再执行30 \log::info(__method__, ['stop', $this->i, microtime(true)]);31 $this->stop(); // 终止此任务32 // cronjob中也可以投递task,但不支持task的finish()回调。33 // 注意:34 // 1.参数2需传true35 // 2.config/laravels.php中修改配置task_ipc_mode为1或2,参考 https://wiki.swoole.com/wiki/page/296.html36 $ret = task::deliver(new testtask('task data'), true);37 var_dump($ret);38 }39 // throw new \exception('an exception');// 此时抛出的异常上层会忽略,并记录到swoole日志,需要开发者try/catch捕获处理40 }41 }
2.注册定时任务类。
1 // 在"config/laravels.php"注册定时任务类 2 [ 3 // ... 4 'timer' => [ 5 'enable' => true, // 启用timer 6 'jobs' => [ // 注册的定时任务类列表 7 // 启用laravelschedulejob来执行`php artisan schedule:run`,每分钟一次,替代linux crontab 8 // \hhxsv5\laravels\illuminate\laravelschedulejob::class, 9 // 两种配置参数的方式:10 // [\app\jobs\timer\testcronjob::class, [1000, true]], // 注册时传入参数11 \app\jobs\timer\testcronjob::class, // 重载对应的方法来返回参数12 ],13 'max_wait_time' => 5, // reload时最大等待时间14 ],15 // ...16 ];
3.注意在构建服务器集群时,会启动多个定时器
,要确保只启动一个定期器,避免重复执行定时任务。
4.laravelsv3.4.0
开始支持热重启[reload]定时器
进程,laravels 在收到sigusr1
信号后会等待max_wait_time
(默认5)秒再结束进程,然后manager
进程会重新拉起定时器
进程。
inotify
,仅支持linux。1.安装扩展。
2.开启配置项。
3.注意:inotify
只有在linux
内修改文件才能收到文件变更事件,建议使用最新版docker,vagrant解决方案。
基于fswatch
,支持os x、linux、windows。
1.安装。
2.在项目根目录下运行命令。
# 监听当前目录./bin/fswatch# 监听app目录./bin/fswatch ./app
基于inotifywait
,仅支持linux。
1.安装。
2.在项目根目录下运行命令。
# 监听当前目录./bin/inotify# 监听app目录./bin/inotify ./app
swoolerver
实例/** * 如果启用websocket rver,$swoole是`swoole\websocket\rver`的实例,否则是是`swoole\http\rver`的实例 * @var \swoole\websocket\rver|\swoole\http\rver $swoole */$swoole = app('swoole');var_dump($swoole->stats());// 单例
swooletable
1.定义table,支持定义多个table。
swoole启动之前会创建定义的所有table。
1 // 在"config/laravels.php"配置 2 [ 3 // ... 4 'swoole_tables' => [ 5 // 场景:websocket中urid与fd绑定 6 'ws' => [// key为table名称,使用时会自动添加table后缀,避免重名。这里定义名为wstable的table 7 'size' => 102400,//table的最大行数 8 'column' => [// table的列定义 9 ['name' => 'value', 'type' => \swoole\table::type_int, 'size' => 8],10 ],11 ],12 //...继续定义其他table13 ],14 // ...15 ];
2.访问table:所有的table实例均绑定在swoolerver
上,通过app('swoole')->xxxtable
访问。
1 namespace app\rvices; 2 u hhxsv5\laravels\swoole\websockethandlerinterface; 3 u swoole\http\request; 4 u swoole\websocket\frame; 5 u swoole\websocket\rver; 6 class websocketrvice implements websockethandlerinterface 7 { 8 /**@var \swoole\table $wstable */ 9 private $wstable;10 public function __construct()11 {12 $this->wstable = app('swoole')->wstable;13 }14 // 场景:websocket中urid与fd绑定15 public function onopen(rver $rver, request $request)16 {17 // var_dump(app('swoole') === $rver);// 同一实例18 /**19 * 获取当前登录的用户20 * 此特性要求建立websocket连接的路径要经过authenticate之类的中间件。21 * 例如:22 * 浏览器端:var ws = new websocket("ws://127.0.0.1:5200/ws");23 * 那么laravel中/ws路由就需要加上类似authenticate的中间件。24 */25 // $ur = auth::ur();26 // $urid = $ur ? $ur->id : 0; // 0 表示未登录的访客用户27 $urid = mt_rand(1000, 10000);28 $this->wstable->t('uid:' . $urid, ['value' => $request->fd]);// 绑定uid到fd的映射29 $this->wstable->t('fd:' . $request->fd, ['value' => $urid]);// 绑定fd到uid的映射30 $rver->push($request->fd, "welcome to laravels #{$request->fd}");31 }32 public function onmessage(rver $rver, frame $frame)33 {34 // 广播35 foreach ($this->wstable as $key => $row) {36 if (strpos($key, 'uid:') === 0 && $rver->istablished($row['value'])) {37 $content = sprintf('broadcast: new message "%s" from #%d', $frame->data, $frame->fd);38 $rver->push($row['value'], $content);39 }40 }41 }42 public function onclo(rver $rver, $fd, $reactorid)43 {44 $uid = $this->wstable->get('fd:' . $fd);45 if ($uid !== fal) {46 $this->wstable->del('uid:' . $uid['value']); // 解绑uid映射47 }48 $this->wstable->del('fd:' . $fd);// 解绑fd映射49 $rver->push($fd, "goodbye #{$fd}");50 }51 }
更多的信息,请参考swoole增加监听的端口与
为了使我们的主服务器能支持除http
和websocket
外的更多协议,我们引入了swoole
的多端口混合协议
特性,在laravels中称为socket
。现在,可以很方便地在laravel
上构建tcp/udp
应用。
创建socket处理类,继承hhxsv5\laravels\swoole\socket\{tcpsocket|udpsocket|http|websocket}
1 namespace app\sockets; 2 u hhxsv5\laravels\swoole\socket\tcpsocket; 3 u swoole\rver; 4 class testtcpsocket extends tcpsocket 5 { 6 public function onconnect(rver $rver, $fd, $reactorid) 7 { 8 \log::info('new tcp connection', [$fd]); 9 $rver->nd($fd, 'welcome to laravels.');10 }11 public function onreceive(rver $rver, $fd, $reactorid, $data)12 {13 \log::info('received data', [$fd, $data]);14 $rver->nd($fd, 'laravels: ' . $data);15 if ($data === "quit\r\n") {16 $rver->nd($fd, 'laravels: bye' . php_eol);17 $rver->clo($fd);18 }19 }20 public function onclo(rver $rver, $fd, $reactorid)21 {22 \log::info('clo tcp connection', [$fd]);23 $rver->nd($fd, 'goodbye');24 }25 }
这些连接和主服务器上的http/websocket连接共享worker进程,因此可以在这些事件操作中使用laravels提供的异步任务投递
、swooletable
、laravel提供的组件如db
、eloquent
等。同时,如果需要使用该协议端口的swoole\rver\port
对象,只需要像如下代码一样访问socket
类的成员swooleport
即可。
public function onreceive(rver $rver, $fd, $reactorid, $data){ $port = $this->swooleport; //获得`swoole\rver\port`对象}
注册套接字。
1 // 修改文件 config/laravels.php 2 // ... 3 'sockets' => [ 4 [ 5 'host' => '127.0.0.1', 6 'port' => 5291, 7 'type' => swoole_sock_tcp,// 支持的嵌套字类型:https://wiki.swoole.com/wiki/page/16.html#entry_h2_0 8 'ttings' => [// swoole可用的配置项:https://wiki.swoole.com/wiki/page/526.html 9 'open_eof_check' => true,10 'package_eof' => "\r\n",11 ],12 'handler' => \app\sockets\testtcpsocket::class,13 ],14 ],
关于心跳配置,只能设置在主服务器
上,不能配置在套接字
上,但套接字
会继承主服务器
的心跳配置。
对于tcp协议,dispatch_mode
选项设为1/3
时,底层会屏蔽onconnect
/onclo
事件,原因是这两种模式下无法保证onconnect
/onclo
/onreceive
的顺序。如果需要用到这两个事件,请将dispatch_mode
改为2/4/5
,。
'swoole' => [ //... 'dispatch_mode' => 2, //...];测试。tcp:
telnet 127.0.0.1 5291
udp:linux下echo "hello laravels" > /dev/udp/127.0.0.1/5292
其他协议的注册示例。
udp'sockets' => [ [ 'host' => '0.0.0.0', 'port' => 5292, 'type' => swoole_sock_udp, 'ttings' => [ 'open_eof_check' => true, 'package_eof' => "\r\n", ], 'handler' => \app\sockets\testudpsocket::class, ],],http
1 'sockets' => [ 2 [ 3 'host' => '0.0.0.0', 4 'port' => 5293, 5 'type' => swoole_sock_tcp, 6 'ttings' => [ 7 'open_http_protocol' => true, 8 ], 9 'handler' => \app\sockets\testhttp::class,10 ],11 ],websocket:主服务器必须
开启websocket
,即需要将websocket.enable
置为true
。1 'sockets' => [ 2 [ 3 'host' => '0.0.0.0', 4 'port' => 5294, 5 'type' => swoole_sock_tcp, 6 'ttings' => [ 7 'open_http_protocol' => true, 8 'open_websocket_protocol' => true, 9 ],10 'handler' => \app\sockets\testwebsocket::class,11 ],12 ],13 协程
警告:协程下代码执行顺序是乱序的,请求级的数据应该以协程id隔离,但laravel/lumen中存在很多单例、静态属性,不同请求间的数据会相互影响,这是swoole原始文档
不安全
的。比如数据库连接就是单例,同一个数据库连接共享同一个pdo资源,这在同步阻塞模式下是没问题的,但在异步协程下是不行的,每次查询需要创建不同的连接,维护不同的io状态,这就需要用到连接池。所以不要
打开协程,仅自定义进程
中可使用协程。启用协程,默认是关闭的。
1 // 修改文件 `config/laravels.php`2 [3 //...4 'swoole' => [5 //...6 'enable_coroutine' => true7 ],8 ]:需
swoole>=2.0
。:需swoole>=4.1.0
,同时启用下面的配置。
// 修改文件 `config/laravels.php`[ //... 'enable_coroutine_runtime' => true]
支持开发者创建一些特殊的工作进程,用于监控、上报或者其他特殊的任务,参考addprocess。
创建proccess类,实现customprocessinterface接口。
1 namespace app\process; 2 u app\tasks\testtask; 3 u hhxsv5\laravels\swoole\process\customprocessinterface; 4 u hhxsv5\laravels\swoole\task\task; 5 u swoole\coroutine; 6 u swoole\http\rver; 7 u swoole\process; 8 class testprocess implements customprocessinterface 9 {10 public static function getname()11 {12 // 进程名称13 return 'test';14 }15 public static function callback(rver $swoole, process $process)16 {17 // 进程运行的代码,不能退出,一旦退出manager进程会自动再次创建该进程。18 \log::info(__method__, [posix_getpid(), $swoole->stats()]);19 while (true) {20 \log::info('do something');21 // sleep(1); // swoole < 2.122 coroutine::sleep(1); // swoole>=2.1 callback()方法已自动创建了协程。23 // 自定义进程中也可以投递task,但不支持task的finish()回调。24 // 注意:25 // 1.参数2需传true26 // 2.config/laravels.php中修改配置task_ipc_mode为1或2,参考 https://wiki.swoole.com/wiki/page/296.html27 $ret = task::deliver(new testtask('task data'), true);28 var_dump($ret);29 // 上层会捕获callback中抛出的异常,并记录到swoole日志,如果异常数达到10次,此进程会退出,manager进程会重新创建进程,所以建议开发者自行try/catch捕获,避免创建进程过于频繁。30 // throw new \exception('an exception');31 }32 }33 // 要求:laravels >= v3.4.0 并且 callback() 必须是异步非阻塞程序。34 public static function onreload(rver $swoole, process $process)35 {36 // stop the process...37 // then end process38 $process->exit(0);39 }40 }
注册testprocess。
1 // 修改文件 config/laravels.php 2 // ... 3 'process' => [ 4 [ 5 'class' => \app\process\testprocess::class, 6 'redirect' => fal, // 是否重定向输入输出 7 'pipe' => 0 // 管道类型:0不创建管道,1创建sock_stream类型管道,2创建sock_dgram类型管道 8 'enable' => true // 是否启用,默认true 9 ],10 ],注意:testprocess::callback()方法不能退出,如果退出次数达到10次,manager进程将会重新创建进程。
swoole
的事件回调函数支持的事件列表:
此事件中不应处理复杂的业务逻辑,只能做一些初始化的简单工作
rverstophhxsv5laravelsswooleeventsrverstopinterface发生在rver正常退出时,此事件中不能使用异步或协程相关的api
workerstarthhxsv5laravelsswooleeventsworkerstartinterface发生在worker/task进程启动完成后workerstophhxsv5laravelsswooleeventsworkerstopinterface发生在worker/task进程正常退出后workererrorhhxsv5laravelsswooleeventsworkererrorinterface发生在worker/task进程发生异常或致命错误时1.创建事件处理类,实现相应的接口。
1 namespace app\events; 2 u hhxsv5\laravels\swoole\events\rverstartinterface; 3 u swoole\atomic; 4 u swoole\http\rver; 5 class rverstartevent implements rverstartinterface 6 { 7 public function __construct() 8 { 9 }10 public function handle(rver $rver)11 {12 // 初始化一个全局计数器(跨进程的可用)13 $rver->atomiccount = new atomic(2233);14 15 // 控制器中大熊猫 可爱调用:app('swoole')->atomiccount->get();16 }17 }18dinner什么意思 namespace app\events;19 u hhxsv5\laravels\swoole\events\workerstartinterface;20 u swoole\http\rver;21 class workerstartevent implements workerstartinterface22 {23 public function __construct()24 {25 }26 public function handle(rver $rver, $workerid)27 {28 // 初始化一个数据库连接池对象29 // databaconnectionpool::init();30 }31 }
2.配置。
1 // 修改文件 config/laravels.php2 'event_handlers' => [3 'rverstart' => \app\events\rverstartevent::class,4 'workerstart' => \app\events\workerstartevent::class,5 ],
单例问题
常见的解决方案:
写一个xxxcleaner
类来清理单例对象状态,此类需实现接口hhxsv5\laravels\illuminate\cleaners\cleanerinterface
,然后注册到laravels.php
的cleaners
中。用一个中间件
来重置
单例对象的状态。如果是以rviceprovider
注册的单例对象,可添加该rviceprovider
到laravels.php
的register_providers
中,这样每次请求会重新注册该rviceprovider
,重新实例化单例对象,参考。laravels 已经内置了一些cleaner。常见问题:一揽子的已知问题和解决方案。调试方式:记录日志、laravel dump rver(laravel 5.7已默认集成)应通过illuminate\http\request
对象来获取请求信息,是可读取的,_rver是部分可读的,不能使用
、_post、、_cookie、、_ssion、$globals。
1 public function form(\illuminate\http\request $request) 2 { 3 $name = $request->input('name'); 4 $all = $request->all(); 5 $ssionid = $request->cookie('ssionid'); 6 $photo = $request->file('photo'); 7 // 调用getcontent()来获取原始的post body,而不能用file_get_contents('php://input') 8 $rawcontent = $request->getcontent(); 9 //...10 }
推荐通过返回illuminate\http\respon
对象来响应请求,兼容echo、vardump()、print_r(),不能使用
函数 dd()、exit()、die()、header()、tcookie()、http_respon_code()。
public function json(){ return respon()->json(['time' => time()])->header('header1', 'value1')->withcookie('c1', 'v1');}各种
单例的连接
将被常驻内存,建议开启持久连接
。数据库连接,连接断开后会自动重连
1 // config/databa.php 2 'connections' => [ 3 'my_conn' => [ 4 'driver' => 'mysql', 5 'host' => env('db_my_conn_host', 'localhost'), 6 'port' => env('db_my_conn_port', 3306), 7 'databa' => env('db_my_conn_databa', 'forge'), 8 'urname' => env('db_my_conn_urname', 'forge'), 9 'password' => env('db_my_conn_password', ''),10 'chart' => 'utf8mb4',11 'collation' => 'utf8mb4_unicode_ci',12 'prefix' => '',13 'strict' => fal,14 'options' => [15 // 开启持久连接16 \pdo::attr_persistent => true,17 ],18 ],19 //...20 ],21 //...
redis连接,连接断开后不会立即
自动重连,会抛出一个关于连接断开的异常,下次会自动重连。需确保每次操作redis前正确的lect db
。
1 // config/databa.php 2 'redis' => [ 3 'client' => env('redis_client', 'phpredis'), // 推荐使用phpredis,以获得更好的性能 4 'default' => [ 5 'host' => env('redis_host', 'localhost'), 6 'password' => env('redis_password', null), 7 'port' => env('redis_port', 6379), 8 'databa' => 0, 9 'persistent' => true, // 开启持久连接10 ],11 ],12 //...你声明的全局、静态变量必须手动清理或重置。
无限追加元素到静态或全局变量中,将导致内存爆满。
1 // 某类 2 class test 3 { 4 public static $array = []; 5 public static $string = ''; 6 } 7 8 // 某控制器 9 public function test(request $req)10 {11 // 内存爆满12 test::$array[] = $req->input('param1');13 test::$string .= $req->input('param2');14 }linux内核参数调整
推荐阅读:
实现websocket 主动消息推送,用laravel+swoole
php laravel+thrift+swoole打造微服务框架
用swoole+react 实现的聊天室
swoole和redis实现的并发队列处理系统
php swoole实现毫秒级定时任务
本文发布于:2023-04-07 22:25:57,感谢您对本站的认可!
本文链接:https://www.wtabcd.cn/fanwen/zuowen/2254cc2890feaf929862815b7b9ef9cf.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文word下载地址:LaravelS – 基于Swoole加速Laravel/Lumen.doc
本文 PDF 下载地址:LaravelS – 基于Swoole加速Laravel/Lumen.pdf
留言与评论(共有 0 条评论) |