_ _ _____
| | | |/ ____|
| | __ _ _ __ __ ___ _____| | (___
| | / _` | '__/ _` \ \ / / _ \ |\___ \
| |___| (_| | | | (_| |\ V / __/ |____) |
|______\__,_|_| \__,_| \_/ \___|_|_____/
🚀
LaravelS
is a glue that is used to quickly integrateSwoole
intoLaravel
orLumen
and then give them better performance and more possibilities.
- Features
- Requirements
- Install
- Run
- Deploy
- Cooperate with Nginx (Recommended)
- Cooperate with Apache
- Enable WebSocket server
- Listen events
- Asynchronous task queue
- Millisecond cron job
- Reload automatically when code is modified
- Get the instance of swoole_server in your project
- Use swoole_table
- Multi-port mixed protocol
- Coroutine
- Custom process
- Other features
- Important notices
- Users and cases
- Todo list
- Alternatives
- License
-
Built-in Http/WebSocket server
-
Memory resident
-
Gracefully reload
-
Support Laravel/Lumen both, good compatibility
-
Simple & Out of the box
Dependency | Requirement |
---|---|
PHP | >= 5.5.9 Recommend PHP7+ |
Swoole | >= 1.7.19 No longer support PHP5 since 2.0.12 Recommend 4.2.3+ |
Laravel/Lumen | >= 5.1 Recommend 5.6+ |
1.Require package via Composer(packagist).
composer require "hhxsv5/laravel-s:~3.3" -vvv
# Make sure that your composer.lock file is under the VCS
2.Register service provider(pick one of two).
-
Laravel
: inconfig/app.php
file,Laravel 5.5+ supports package discovery automatically, you should skip this step
'providers' => [ //... Hhxsv5\LaravelS\Illuminate\LaravelSServiceProvider::class, ],
-
Lumen
: inbootstrap/app.php
file$app->register(Hhxsv5\LaravelS\Illuminate\LaravelSServiceProvider::class);
3.Publish configuration and binaries.
Suggest that do publish after upgrade LaravelS every time
php artisan laravels publish
# Configuration: config/laravels.php
# Binary: bin/laravels bin/fswatch
Special for Lumen
: you DO NOT
need to load this configuration manually in bootstrap/app.php
file, LaravelS will load it automatically.
// Unnecessary to call configure()
$app->configure('laravels');
4.Change config/laravels.php
: listen_ip, listen_port, refer Settings.
php bin/laravels {start|stop|restart|reload|info|help}
Please read the notices carefully before running
, Important notices.
Command | Description |
---|---|
start |
Start LaravelS, list the processes by "ps -ef|grep laravels". Support the option "-d|--daemonize" to run as a daemon; Support the option "-e|--env" to specify the environment to run, such as --env=testing will use the configuration file .env.testing firstly, this feature requires Laravel 5.2+ |
stop |
Stop LaravelS |
restart |
Restart LaravelS, support the options "-d|--daemonize" and "-e|--env" |
reload |
Reload all Task/Worker processes which contain your business codes, exclude Master/Manger/Timer/Custom processes |
info |
Display component version information |
help |
Display help information |
It is recommended to supervise the main process through Supervisord, the premise is without option
-d
and to setswoole.daemonize
tofalse
.
[program:laravel-s-test]
command=/user/local/bin/php /opt/www/laravel-s-test/bin/laravels start -i
numprocs=1
autostart=true
autorestart=true
startretries=3
user=www-data
redirect_stderr=true
stdout_logfile=/opt/www/laravel-s-test/storage/logs/supervisord-stdout.log
Demo.
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 laravels {
# Connect IP:Port
server 127.0.0.1:5200 weight=5 max_fails=3 fail_timeout=30s;
# Connect UnixSocket Stream file, tips: put the socket file in the /dev/shm directory to get better performance
#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;
# Don't forget to bind the 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 handles the static resources(recommend enabling gzip), LaravelS handles the dynamic resource.
location / {
try_files $uri @laravels;
}
# Response 404 directly when request the PHP file, to avoid exposing 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://laravels;
}
}
LoadModule proxy_module /yyypath/modules/mod_deflate.so
<IfModule deflate_module>
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
</IfModule>
<VirtualHost *:80>
# Don't forget to bind the host
ServerName www.laravels.com
ServerAdmin [email protected]
DocumentRoot /xxxpath/laravel-s-test/public;
DirectoryIndex index.html index.htm
<Directory "/">
AllowOverride None
Require all granted
</Directory>
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
<Proxy balancer://laravels>
BalancerMember http://192.168.1.1:8011 loadfactor=7
#BalancerMember http://192.168.1.2:8011 loadfactor=3
#BalancerMember http://192.168.1.3:8011 loadfactor=1 status=+H
ProxySet lbmethod=byrequests
</Proxy>
#ProxyPass / balancer://laravels/
#ProxyPassReverse / balancer://laravels/
# Apache handles the static resources, LaravelS handles the dynamic resource.
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
</VirtualHost>
The Listening address of WebSocket Sever is the same as Http Server.
1.Create WebSocket Handler class, and implement interface WebSocketHandlerInterface
.The instant is automatically instantiated when start, you do not need to manually create it.
namespace App\Services;
use Hhxsv5\LaravelS\Swoole\WebSocketHandlerInterface;
/**
* @see https://www.swoole.co.uk/docs/modules/swoole-websocket-server
*/
class WebSocketService implements WebSocketHandlerInterface
{
// Declare constructor without parameters
public function __construct()
{
}
public function onOpen(\swoole_websocket_server $server, \swoole_http_request $request)
{
// Laravel has finished its lifetime before triggering onOpen event, so Laravel's Request & Session are available here, Request is readable only, Session is readable & writable both.
\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');// all exceptions will be ignored, then record them into Swoole log, you need to try/catch them
}
public function onMessage(\swoole_websocket_server $server, \swoole_websocket_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');// all exceptions will be ignored, then record them into Swoole log, you need to try/catch them
}
public function onClose(\swoole_websocket_server $server, $fd, $reactorId)
{
// throw new \Exception('an exception');// all exceptions will be ignored, then record them into Swoole log, you need to try/catch them
}
}
2.Modify config/laravels.php
.
// ...
'websocket' => [
'enable' => true, // Here is true
'handler' => \App\Services\WebSocketService::class,
],
'swoole' => [
//...
// Must set dispatch_mode in (2, 4, 5), see https://www.swoole.co.uk/docs/modules/swoole-server/configuration
'dispatch_mode' => 2,
//...
],
// ...
3.Use swoole_table
to bind FD & UserId, optional, Swoole Table Demo. Also you can use the other global storage services, like Redis/Memcached/MySQL, but be careful that FD will be possible conflicting between multiple Swoole Servers
.
4.Cooperate with Nginx (Recommended)
Refer WebSocket Proxy
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
upstream laravels {
# Connect IP:Port
server 127.0.0.1:5200 weight=5 max_fails=3 fail_timeout=30s;
# Connect UnixSocket Stream file, tips: put the socket file in the /dev/shm directory to get better performance
#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;
# Don't forget to bind the 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 handles the static resources(recommend enabling gzip), LaravelS handles the dynamic resource.
location / {
try_files $uri @laravels;
}
# Response 404 directly when request the PHP file, to avoid exposing public/*.php
#location ~* \.php$ {
# return 404;
#}
# Http and WebSocket are concomitant, Nginx identifies them by "location"
# !!! The location of WebSocket is "/ws"
# Javascript: var ws = new WebSocket("ws://laravels.com/ws");
location =/ws {
# proxy_connect_timeout 60s;
# proxy_send_timeout 60s;
# proxy_read_timeout: Nginx will close the connection if the proxied server does not send data to Nginx in 60 seconds; At the same time, this close behavior is also affected by heartbeat setting of 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://laravels;
}
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://laravels;
}
}
5.Heartbeat setting
-
Heartbeat setting of Swoole
// config/laravels.php 'swoole' => [ //... // All connections are traversed every 60 seconds. If a connection does not send any data to the server within 600 seconds, the connection will be forced to close. 'heartbeat_idle_time' => 600, 'heartbeat_check_interval' => 60, //... ],
-
Proxy read timeout of Nginx
# Nginx will close the connection if the proxied server does not send data to Nginx in 60 seconds proxy_read_timeout 60s;
Usually, you can reset/destroy some
global/static
variables, or change the currentRequest/Response
object.
-
laravels.received_request
After LaravelS parsedswoole_http_request
toIlluminate\Http\Request
, before Laravel's Kernel handles this request.// Edit file `app/Providers/EventServiceProvider.php`, add the following code into method `boot` // If no variable $events, you can also call Facade \Event::listen(). $events->listen('laravels.received_request', function (\Illuminate\Http\Request $req, $app) { $req->query->set('get_key', 'hhxsv5');// Change query of request $req->request->set('post_key', 'hhxsv5'); // Change post of request });
-
laravels.generated_response
After Laravel's Kernel handled the request, before LaravelS parsesIlluminate\Http\Response
toswoole_http_response
.// Edit file `app/Providers/EventServiceProvider.php`, add the following code into method `boot` // If no variable $events, you can also call 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');// Change header of response });
This feature depends on
AsyncTask
ofSwoole
, your need to setswoole.task_worker_num
inconfig/laravels.php
firstly. The performance of asynchronous event processing is influenced by number of Swoole task process, you need to set task_worker_num appropriately.
1.Create event class.
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.Create listener class.
use Hhxsv5\LaravelS\Swoole\Task\Task;
use Hhxsv5\LaravelS\Swoole\Task\Event;
use Hhxsv5\LaravelS\Swoole\Task\Listener;
class TestListener1 extends Listener
{
// Declare constructor without parameters
public function __construct()
{
}
public function handle(Event $event)
{
\Log::info(__CLASS__ . ':handle start', [$event->getData()]);
sleep(2);// Simulate the slow codes
// Deliver task in CronJob, but NOT support callback finish() of task.
// Note:
// 1.Set parameter 2 to true
// 2.Modify task_ipc_mode to 1 or 2 in config/laravels.php, see https://www.swoole.co.uk/docs/modules/swoole-server/configuration
$ret = Task::deliver(new TestTask('task data'), true);
var_dump($ret);
// throw new \Exception('an exception');// all exceptions will be ignored, then record them into Swoole log, you need to try/catch them
}
}
3.Bind event & listeners.
// Bind event & listeners in file "config/laravels.php", one event => many listeners
[
// ...
'events' => [
\App\Tasks\TestEvent::class => [
\App\Tasks\TestListener1::class,
//\App\Tasks\TestListener2::class,
],
],
// ...
];
4.Fire event.
// Create instance of event and fire it, "fire" is asynchronous.
use Hhxsv5\LaravelS\Swoole\Task\Event;
$success = Event::fire(new TestEvent('event data'));
var_dump($success);// Return true if sucess, otherwise false
This feature depends on
AsyncTask
ofSwoole
, your need to setswoole.task_worker_num
inconfig/laravels.php
firstly. The performance of task processing is influenced by number of Swoole task process, you need to set task_worker_num appropriately.
1.Create task class.
use Hhxsv5\LaravelS\Swoole\Task\Task;
class TestTask extends Task
{
private $data;
private $result;
public function __construct($data)
{
$this->data = $data;
}
// The logic of task handling, run in task process, CAN NOT deliver task
public function handle()
{
\Log::info(__CLASS__ . ':handle start', [$this->data]);
sleep(2);// Simulate the slow codes
// throw new \Exception('an exception');// all exceptions will be ignored, then record them into Swoole log, you need to try/catch them
$this->result = 'the result of ' . $this->data;
}
// Optional, finish event, the logic of after task handling, run in worker process, CAN deliver task
public function finish()
{
\Log::info(__CLASS__ . ':finish start', [$this->result]);
Task::deliver(new TestTask2('task2 data')); // Deliver the other task
}
}
2.Deliver task.
// Create instance of TestTask and deliver it, "deliver" is asynchronous.
use Hhxsv5\LaravelS\Swoole\Task\Task;
$task = new TestTask('task data');
// $task->delay(3);// delay 3 seconds to deliver task
$ret = Task::deliver($task);
var_dump($ret);// Return true if sucess, otherwise false
Wrapper cron job base on Swoole's Millisecond Timer, replace
Linux
Crontab
.
1.Create cron job class.
namespace App\Jobs\Timer;
use App\Tasks\TestTask;
use Hhxsv5\LaravelS\Swoole\Task\Task;
use Hhxsv5\LaravelS\Swoole\Timer\CronJob;
class TestCronJob extends CronJob
{
protected $i = 0;
// !!! The `interval` and `isImmediate` of cron job can be configured in two ways(pick one of two): one is to overload the corresponding method, and the other is to pass parameters when registering cron job.
// --- Override the corresponding method to return the configuration: begin
public function interval()
{
return 1000;// Run every 1000ms
}
public function isImmediate()
{
return false;// Whether to trigger `run` immediately after setting up
}
// --- Override the corresponding method to return the configuration: end
public function run()
{
\Log::info(__METHOD__, ['start', $this->i, microtime(true)]);
// do something
$this->i++;
\Log::info(__METHOD__, ['end', $this->i, microtime(true)]);
if ($this->i >= 10) { // Run 10 times only
\Log::info(__METHOD__, ['stop', $this->i, microtime(true)]);
$this->stop(); // Stop this cron job
// Deliver task in CronJob, but NOT support callback finish() of task.
// Note:
// 1.Set parameter 2 to true
// 2.Modify task_ipc_mode to 1 or 2 in config/laravels.php, see https://www.swoole.co.uk/docs/modules/swoole-server/configuration
$ret = Task::deliver(new TestTask('task data'), true);
var_dump($ret);
}
// throw new \Exception('an exception');// all exceptions will be ignored, then record them into Swoole log, you need to try/catch them
}
}
2.Register cron job.
// Register cron jobs in file "config/laravels.php"
[
// ...
'timer' => [
'enable' => true, // Enable Timer
'jobs' => [ // The list of cron job
// Enable LaravelScheduleJob to run `php artisan schedule:run` every 1 minute, replace Linux Crontab
// \Hhxsv5\LaravelS\Illuminate\LaravelScheduleJob::class,
// Two ways to configure parameters:
// [\App\Jobs\Timer\TestCronJob::class, [1000, true]], // Pass in parameters when registering
\App\Jobs\Timer\TestCronJob::class, // Override the corresponding method to return the configuration
],
],
// ...
];
3.Note: it will launch multiple timers when build the server cluster, so you need to make sure that launch one timer only to avoid running repetitive task.
-
Via
inotify
, support Linux only.1.Install inotify extension.
2.Turn on the switch in Settings.
3.Notice: Modify the file only in
Linux
to receive the file change events. It's recommended to use the latest Docker. Vagrant Solution. -
Via
fswatch
, support OS X/Linux/Windows.1.Install fswatch.
2.Run command in your project root directory.
# Watch current directory ./bin/fswatch # Watch app directory ./bin/fswatch ./app
/**
* $swoole is the instance of `swoole_websocket_server` if enable WebSocket server, otherwise `\swoole_http_server`
* @var \swoole_http_server|\swoole_websocket_server $swoole
*/
$swoole = app('swoole');
var_dump($swoole->stats());// Singleton
1.Define swoole_table
, support multiple.
All defined tables will be created before Swoole starting.
// in file "config/laravels.php"
[
// ...
'swoole_tables' => [
// Scene:bind UserId & FD in WebSocket
'ws' => [// The Key is table name, will add suffix "Table" to avoid naming conficts. Here defined a table named "wsTable"
'size' => 102400,// The max size
'column' => [// Define the columns
['name' => 'value', 'type' => \swoole_table::TYPE_INT, 'size' => 8],
],
],
//...Define the other tables
],
// ...
];
2.Access swoole_table
: all table instances will be bound on swoole_server
, access by app('swoole')->xxxTable
.
// Scene:bind UserId & FD in WebSocket
public function onOpen(\swoole_websocket_server $server, \swoole_http_request $request)
{
// var_dump(app('swoole') === $server);// The same instance
$userId = mt_rand(1000, 10000);
app('swoole')->wsTable->set('uid:' . $userId, ['value' => $request->fd]);// Bind map uid to fd
app('swoole')->wsTable->set('fd:' . $request->fd, ['value' => $userId]);// Bind map fd to uid
$server->push($request->fd, 'Welcome to LaravelS');
}
public function onMessage(\swoole_websocket_server $server, \swoole_websocket_frame $frame)
{
foreach (app('swoole')->wsTable as $key => $row) {
if (strpos($key, 'uid:') === 0 && $server->exist($row['value'])) {
$server->push($row['value'], 'Broadcast: ' . date('Y-m-d H:i:s'));// Broadcast
}
}
}
public function onClose(\swoole_websocket_server $server, $fd, $reactorId)
{
$uid = app('swoole')->wsTable->get('fd:' . $fd);
if ($uid !== false) {
app('swoole')->wsTable->del('uid:' . $uid['value']); // Unbind uid map
}
app('swoole')->wsTable->del('fd:' . $fd);// Unbind fd map
$server->push($fd, 'Goodbye');
}
For more information, please refer to Swoole Server AddListener
To make our main server support more protocols not just Http and WebSocket, we bring the feature multi-port mixed protocol
of Swoole in LaravelS and name it Socket
. Now, you can build TCP/UDP
applications easily on top of Laravel.
-
Create socket handler class, and extend
Hhxsv5\LaravelS\Swoole\Socket\{TcpSocket|UdpSocket|Http|WebSocket}
.namespace App\Sockets; use Hhxsv5\LaravelS\Swoole\Socket\TcpSocket; class TestTcpSocket extends TcpSocket { public function onConnect(\swoole_server $server, $fd, $reactorId) { \Log::info('New TCP connection', [$fd]); $server->send($fd, 'Welcome to LaravelS.'); } public function onReceive(\swoole_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(\swoole_server $server, $fd, $reactorId) { \Log::info('Close TCP connection', [$fd]); $server->send($fd, 'Goodbye'); } }
These
Socket
connections share the same worker processes with yourHTTP
/WebSocket
connections. So it won't be a problem at all if you want to deliver tasks, useswoole_table
, even Laravel components such as DB, Eloquent and so on. At the same time, you can accessswoole_server_port
object directly by member propertyswoolePort
.public function onReceive(\swoole_server $server, $fd, $reactorId, $data) { $port = $this->swoolePort; //There you go }
-
Register Sockets.
// Edit `config/laravels.php` //... 'sockets' => [ [ 'host' => '127.0.0.1', 'port' => 5291, 'type' => SWOOLE_SOCK_TCP,// Socket type: SWOOLE_SOCK_TCP/SWOOLE_SOCK_UDP 'settings' => [// Swoole settings:https://www.swoole.co.uk/docs/modules/swoole-server-methods#swoole_server-addlistener 'open_eof_check' => true, 'package_eof' => "\r\n", ], 'handler' => \App\Sockets\TestTcpSocket::class, ], ],
About the heartbeat configuration, it can only be set on the
main server
and cannot be configured onSocket
, but theSocket
inherits the heartbeat configuration of themain server
.For TCP socket,
onConnect
andonClose
events will be blocked whendispatch_mode
of Swoole is1/3
, so if you want to unblock these two events please setdispatch_mode
to2/4/5
.'swoole' => [ //... 'dispatch_mode' => 2, //... ];
-
Test.
-
TCP:
telnet 127.0.0.1 5291
-
UDP: [Linux]
echo "Hello LaravelS" > /dev/udp/127.0.0.1/5292
-
Register example of other protocols.
- 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
'sockets' => [ [ 'host' => '0.0.0.0', 'port' => 5294, 'type' => SWOOLE_SOCK_TCP, 'settings' => [ 'open_http_protocol' => true, 'open_websocket_protocol' => true, ], 'handler' => \App\Sockets\TestWebSocket::class, ], ],
-
Warning: There are a large number of singletons and static properties in Laravel/Lumen, which are
unsafe
in coroutine. It is NOT recommended to enable coroutine. -
Enable Coroutine, default disable.
// Edit `config/laravels.php` [ //... 'swoole' => [ //... 'enable_coroutine' => true ], ]
-
Coroutine Client: require
Swoole>=2.0
. -
Runtime Coroutine: require
Swoole>=4.1.0
, and enable it.// Edit `config/laravels.php` [ //... 'enable_coroutine_runtime' => true //... ]
Support developers to create special work processes for monitoring, reporting, or other special tasks. Refer addProcess.
-
Create Proccess class, implements CustomProcessInterface.
namespace App\Processes; use App\Tasks\TestTask; use Hhxsv5\LaravelS\Swoole\Task\Task; use Hhxsv5\LaravelS\Swoole\Process\CustomProcessInterface; class TestProcess implements CustomProcessInterface { public static function getName() { // The name of process return 'test'; } public static function isRedirectStdinStdout() { // Whether redirect stdin/stdout return false; } public static function getPipeType() { // The type of pipeline: 0 no pipeline, 1 \SOCK_STREAM, 2 \SOCK_DGRAM return 0; } public static function callback(\swoole_server $swoole) { // The callback method cannot exit. Once exited, Manager process will automatically create the process \Log::info(__METHOD__, [posix_getpid(), $swoole->stats()]); while (true) { \Log::info('Do something'); sleep(1); // Deliver task in custom process, but NOT support callback finish() of task. // Note: // 1.Set parameter 2 to true // 2.Modify task_ipc_mode to 1 or 2 in config/laravels.php, see https://www.swoole.co.uk/docs/modules/swoole-server/configuration $ret = Task::deliver(new TestTask('task data'), true); var_dump($ret); // The upper layer will capture the exception thrown in the callback and record it to the Swoole log. If the number of exceptions reaches 10, the process will exit and the Manager process will re-create the process. Therefore, developers are encouraged to try/catch to avoid creating the process too frequently. // throw new \Exception('an exception'); } } }
-
Register TestProcess.
// Edit `config/laravels.php` // ... 'processes' => [ \App\Processes\TestProcess::class, ],
-
Note: The TestProcess::callback() method cannot quit. If the number of quit reaches 10, the Manager process will re-create the process.
Supported events:
Event | Interface | When happened |
---|---|---|
WorkerStart | Hhxsv5\LaravelS\Swoole\Events\WorkerStartInterface | Occurs when the Worker process/Task process starts, and the Laravel initialization has been completed. |
1.Create an event class to implement the corresponding interface.
namespace App\Events;
use Hhxsv5\LaravelS\Swoole\Events\WorkerStartInterface;
class WorkerStartEvent implements WorkerStartInterface
{
public function __construct()
{
}
public function handle(\swoole_http_server $server, $workerId)
{
// Eg: Initialize a connection pool object, bound to the Swoole Server object, accessible via app('swoole')->connectionPool
if (!isset($server->connectionPool)) {
$server->connectionPool = new ConnectionPool();
}
}
}
2.Configuration.
// Edit `config/laravels.php`
'event_handlers' => [
'WorkerStart' => \App\Events\WorkerStartEvent::class,
],
-
Singleton Issue
-
Under FPM mode, singleton instances will be instantiated and recycled in every request, request start=>instantiate instance=>request end=>recycled instance.
-
Under Swoole Server, All singleton instances will be held in memory, different lifetime from FPM, request start=>instantiate instance=>request end=>do not recycle singleton instance. So need developer to maintain status of singleton instances in ervery request.
-
Common solutions:
-
Reset
status of singleton instances byMiddleware
. -
Re-register
ServiceProvider
, addXxxServiceProvider
intoregister_providers
of filelaravels.php
. So that reinitialize singleton instances in ervery request Refer.
-
-
-
Should get all request information from
Illuminate\Http\Request
Object, $_ENV is readable, $_SERVER is partially readable,CANNOT USE
$_GET/$_POST/$_FILES/$_COOKIE/$_REQUEST/$_SESSION/$GLOBALS.public function form(\Illuminate\Http\Request $request) { $name = $request->input('name'); $all = $request->all(); $sessionId = $request->cookie('sessionId'); $photo = $request->file('photo'); // Call getContent() to get the raw POST body, instead of file_get_contents('php://input') $rawContent = $request->getContent(); //... }
-
Respond by
Illuminate\Http\Response
Object, compatible with echo/vardump()/print_r(),CANNOT USE
functions dd()/exit()/die()/header()/setcookie()/http_response_code().public function json() { return response()->json(['time' => time()])->header('header1', 'value1')->withCookie('c1', 'v1'); }
-
The various
singleton connections
will bememory resident
, recommend to enablepersistent connection
.
-
Database connection, it
will
reconnect automaticallyimmediately
after disconnect.// config/database.php //... 'connections' => [ 'my_conn' => [ 'driver' => 'mysql', 'host' => env('DB_MY_CONN_HOST', 'localhost'), 'port' => env('DB_MY_CONN_PORT', 3306), 'database' => env('DB_MY_CONN_DATABASE', 'forge'), 'username' => env('DB_MY_CONN_USERNAME', 'forge'), 'password' => env('DB_MY_CONN_PASSWORD', ''), 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_unicode_ci', 'prefix' => '', 'strict' => false, 'options' => [ // Enable persistent connection \PDO::ATTR_PERSISTENT => true, ], ], //... ], //...
-
Redis connection, it
won't
reconnect automaticallyimmediately
after disconnect, and will throw an exception about lost connection, reconnect next time. You need to make sure thatSELECT DB
correctly before operating Redis every time.// config/database.php 'redis' => [ 'default' => [ 'host' => env('REDIS_HOST', 'localhost'), 'password' => env('REDIS_PASSWORD', null), 'port' => env('REDIS_PORT', 6379), 'database' => 0, 'persistent' => true, // Enable persistent connection ], ], //...
-
global
,static
variables which you declared are need to destroy(reset) manually. -
Infinitely appending element into
static
/global
variable will lead to memory leak.// Some class class Test { public static $array = []; public static $string = ''; } // Controller public function test(Request $req) { // Memory leak Test::$array[] = $req->input('param1'); Test::$string .= $req->input('param2'); }
- Connection pool for MySQL/Redis.