主頁 > 後端開發 > LaravelS - 基于Swoole加速Laravel/Lumen

LaravelS - 基于Swoole加速Laravel/Lumen

2020-09-22 16:46:56 後端開發

LaravelS

LaravelS是一個膠水專案,用于快速集成SwooleLaravelLumen,然后賦予它們更好的性能、更多可能性,Github

特性

  • 內置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.5.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.phpswoole.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 顯示幫助資訊

部署

建議通過Supervisord監管主行程,前提是不能加-d選項并且設定swoole.daemonizefalse
[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

 

與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配合使用

 1 LoadModule proxy_module /yyypath/modules/mod_deflate.so
 2 <IfModule deflate_module>
 3     SetOutputFilter 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     ServerName www.laravels.com
11     ServerAdmin [email protected]
12 
13     DocumentRoot /xxxpath/laravel-s-test/public;
14     DirectoryIndex index.html index.htm
15     <Directory "/">
16         AllowOverride None
17         Require all granted
18     </Directory>
19 
20     LoadModule proxy_module /yyypath/modules/mod_proxy.so
21     LoadModule proxy_module /yyypath/modules/mod_proxy_balancer.so
22     LoadModule proxy_module /yyypath/modules/mod_lbmethod_byrequests.so.so
23     LoadModule proxy_module /yyypath/modules/mod_proxy_http.so.so
24     LoadModule proxy_module /yyypath/modules/mod_slotmem_shm.so
25     LoadModule proxy_module /yyypath/modules/mod_rewrite.so
26 
27     ProxyRequests Off
28     ProxyPreserveHost On
29     <Proxy balancer://laravels>  
30         BalancerMember http://192.168.1.1:5200 loadfactor=7
31         #BalancerMember http://192.168.1.2:5200 loadfactor=3
32         #BalancerMember http://192.168.1.3:5200 loadfactor=1 status=+H
33         ProxySet lbmethod=byrequests
34     </Proxy>
35     #ProxyPass / balancer://laravels/
36     #ProxyPassReverse / balancer://laravels/
37 
38     # Apache處理靜態資源,LaravelS處理動態資源,
39     RewriteEngine On
40     RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} !-d
41     RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} !-f
42     RewriteRule ^/(.*)$ balancer://laravels/%{REQUEST_URI} [P,L]
43 
44     ErrorLog ${APACHE_LOG_DIR}/www.laravels.com.error.log
45     CustomLog ${APACHE_LOG_DIR}/www.laravels.com.access.log combined
46 </VirtualHost>

 

啟用WebSocket服務器

WebSocket服務器監聽的IP和埠與Http服務器相同,

1.創建WebSocket Handler類,并實作介面WebSocketHandlerInterface,start時會自動實體化,不需要手動創建實體,

 1 namespace App\Services;
 2 use Hhxsv5\LaravelS\Swoole\WebSocketHandlerInterface;
 3 use Swoole\Http\Request;
 4 use Swoole\WebSocket\Frame;
 5 use Swoole\WebSocket\Server;
 6 /**
 7  * @see https://wiki.swoole.com/wiki/page/400.html
 8  */
 9 class WebSocketService implements WebSocketHandlerInterface
10 {
11     // 宣告沒有引數的建構式
12     public function __construct()
13     {
14     }
15     public function onOpen(Server $server, Request $request)
16     {
17         // 在觸發onOpen事件之前,建立WebSocket的HTTP請求已經經過了Laravel的路由,
18         // 所以Laravel的Request、Auth等資訊是可讀的,Session是可讀寫的,但僅限在onOpen事件中,
19         // \Log::info('New WebSocket connection', [$request->fd, request()->all(), session()->getId(), session('xxx'), session(['yyy' => time()])]);
20         $server->push($request->fd, 'Welcome to LaravelS');
21         // throw new \Exception('an exception');// 此時拋出的例外上層會忽略,并記錄到Swoole日志,需要開發者try/catch捕獲處理
22     }
23     public function onMessage(Server $server, Frame $frame)
24     {
25         // \Log::info('Received message', [$frame->fd, $frame->data, $frame->opcode, $frame->finish]);
26         $server->push($frame->fd, date('Y-m-d H:i:s'));
27         // throw new \Exception('an exception');// 此時拋出的例外上層會忽略,并記錄到Swoole日志,需要開發者try/catch捕獲處理
28     }
29     public function onClose(Server $server, $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\Services\WebSocketService::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與UserId,可選的,Swoole Table示例,也可以用其他全域存盤服務,例如Redis/Memcached/MySQL,但需要注意多個Swoole Server實體時FD可能沖突,

4.與Nginx配合使用(推薦)

參考 WebSocket代理
 1 map $http_upgrade $connection_upgrade {
 2     default upgrade;
 3     ''      close;
 4 }
 5 upstream swoole {
 6     # 通過 IP:Port 連接
 7     server 127.0.0.1:5200 weight=5 max_fails=3 fail_timeout=30s;
 8     # 通過 UnixSocket Stream 連接,小訣竅:將socket檔案放在/dev/shm目錄下,可獲得更好的性能
 9     #server unix:/xxxpath/laravel-s-test/storage/laravels.sock weight=5 max_fails=3 fail_timeout=30s;
10     #server 192.168.1.1:5200 weight=3 max_fails=3 fail_timeout=30s;
11     #server 192.168.1.2:5200 backup;
12     keepalive 16;
13 }
14 server {
15     listen 80;
16     # 別忘了綁Host喲
17     server_name laravels.com;
18     root /xxxpath/laravel-s-test/public;
19     access_log /yyypath/log/nginx/$server_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/*.php
27     #location ~* \.php$ {
28     #    return 404;
29     #}
30     # Http和WebSocket共存,Nginx通過location區分
31     # !!! WebSocket連接時路徑為/ws
32     # Javascript: var ws = new WebSocket("ws://laravels.com/ws");
33     location =/ws {
34         # proxy_connect_timeout 60s;
35         # proxy_send_timeout 60s;
36         # proxy_read_timeout:如果60秒內被代理的服務器沒有回應資料給Nginx,那么Nginx會關閉當前連接;同時,Swoole的心跳設定也會影響連接的關閉
37         # proxy_read_timeout 60s;
38         proxy_http_version 1.1;
39         proxy_set_header X-Real-IP $remote_addr;
40         proxy_set_header X-Real-PORT $remote_port;
41         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
42         proxy_set_header Host $http_host;
43         proxy_set_header Scheme $scheme;
44         proxy_set_header Server-Protocol $server_protocol;
45         proxy_set_header Server-Name $server_name;
46         proxy_set_header Server-Addr $server_addr;
47         proxy_set_header Server-Port $server_port;
48         proxy_set_header Upgrade $http_upgrade;
49         proxy_set_header Connection $connection_upgrade;
50         proxy_pass http://swoole;
51     }
52     location @laravels {
53         # proxy_connect_timeout 60s;
54         # proxy_send_timeout 60s;
55         # proxy_read_timeout 60s;
56         proxy_http_version 1.1;
57         proxy_set_header Connection "";
58         proxy_set_header X-Real-IP $remote_addr;
59         proxy_set_header X-Real-PORT $remote_port;
60         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
61         proxy_set_header Host $http_host;
62         proxy_set_header Scheme $scheme;
63         proxy_set_header Server-Protocol $server_protocol;
64         proxy_set_header Server-Name $server_name;
65         proxy_set_header Server-Addr $server_addr;
66         proxy_set_header Server-Port $server_port;
67         proxy_pass http://swoole;
68     }
69 }

 

5.心跳配置

  • Swoole的心跳配置

    1 // config/laravels.php
    2 '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/EventServiceProvider.php`, 添加下面監聽代碼到boot方法中
    2 // 如果變數$events不存在,你也可以通過Facade呼叫\Event::listen(),
    3 $events->listen('laravels.received_request', function (\Illuminate\Http\Request $req, $app) {
    4     $req->query->set('get_key', 'hhxsv5');// 修改querystring
    5     $req->request->set('post_key', 'hhxsv5'); // 修改post body
    6 });

     

  • laravels.generated_response 在Laravel內核處理完請求后,將Illuminate\Http\Response轉成Swoole\Http\Response之前(下一步將回應給客戶端),

    1 // 修改`app/Providers/EventServiceProvider.php`, 添加下面監聽代碼到boot方法中
    2 // 如果變數$events不存在,你也可以通過Facade呼叫\Event::listen(),
    3 $events->listen('laravels.generated_response', function (\Illuminate\Http\Request $req, \Symfony\Component\HttpFoundation\Response $rsp, $app) {
    4     $rsp->headers->set('header-key', 'hhxsv5');// 修改header
    5 });

     

自定義的異步事件

此特性依賴SwooleAsyncTask,必須先設定config/laravels.phpswoole.task_worker_num,異步事件的處理能力受Task行程數影響,需合理設定task_worker_num,

1.創建事件類,

 1 use Hhxsv5\LaravelS\Swoole\Task\Event;
 2 class TestEvent extends Event
 3 {
 4     private $data;
 5     public function __construct($data)
 6     {
 7         $this->data =https://www.cnblogs.com/a609251438/p/ $data;
 8     }
 9     public function getData()
10     {
11         return $this->data;
12     }
13 }

 

2.創建監聽器類,

 1 use Hhxsv5\LaravelS\Swoole\Task\Task;
 2 use Hhxsv5\LaravelS\Swoole\Task\Event;
 3 use 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需傳true
17         // 2.config/laravels.php中修改配置task_ipc_mode為1或2,參考 https://wiki.swoole.com/wiki/page/296.html
18         $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 use Hhxsv5\LaravelS\Swoole\Task\Event;
3 $success = Event::fire(new TestEvent('event data'));
4 var_dump($success);//判斷是否觸發成功

 

異步的任務佇列

此特性依賴SwooleAsyncTask,必須先設定config/laravels.phpswoole.task_worker_num,異步任務的處理能力受Task行程數影響,需合理設定task_worker_num,

1.創建任務類,

 1 use 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 =https://www.cnblogs.com/a609251438/p/ $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 use 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的毫秒定時器,封裝的定時任務,取代LinuxCrontab

1.創建定時任務類,

 1 namespace App\Jobs\Timer;
 2 use App\Tasks\TestTask;
 3 use Swoole\Coroutine;
 4 use Hhxsv5\LaravelS\Swoole\Task\Task;
 5 use 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 false;// 是否立即執行第一次,false則等待間隔時間后執行第一次
18     }
19     // --- 多載對應的方法來回傳配置:結束
20     public function run()
21     {
22         \Log::info(__METHOD__, ['start', $this->i, microtime(true)]);
23         // do something
24         // sleep(1); // Swoole < 2.1
25         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需傳true
35             // 2.config/laravels.php中修改配置task_ipc_mode為1或2,參考 https://wiki.swoole.com/wiki/page/296.html
36             $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.LaravelS v3.4.0開始支持熱重啟[Reload]定時器行程,LaravelS 在收到SIGUSR1信號后會等待max_wait_time(默認5)秒再結束行程,然后Manager行程會重新拉起定時器行程,

修改代碼后自動Reload

  • 基于inotify,僅支持Linux,

    1.安裝inotify擴展,

    2.開啟配置項,

    3.注意:inotify只有在Linux內修改檔案才能收到檔案變更事件,建議使用最新版Docker,Vagrant解決方案,

  • 基于fswatch,支持OS X、Linux、Windows,

    1.安裝fswatch,

    2.在專案根目錄下運行命令,

    # 監聽當前目錄
    ./bin/fswatch
    # 監聽app目錄
    ./bin/fswatch ./app
  • 基于inotifywait,僅支持Linux,

    1.安裝inotify-tools,

    2.在專案根目錄下運行命令,

    # 監聽當前目錄
    ./bin/inotify
    # 監聽app目錄
    ./bin/inotify ./app

在你的專案中使用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,
 1 // 在"config/laravels.php"配置
 2 [
 3     // ...
 4     'swoole_tables'  => [
 5         // 場景:WebSocket中UserId與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         //...繼續定義其他Table
13     ],
14     // ...
15 ];

 

2.訪問Table:所有的Table實體均系結在SwooleServer上,通過app('swoole')->xxxTable訪問,

 1 namespace App\Services;
 2 use Hhxsv5\LaravelS\Swoole\WebsocketHandlerInterface;
 3 use Swoole\Http\Request;
 4 use Swoole\WebSocket\Frame;
 5 use Swoole\WebSocket\Server;
 6 class WebSocketService 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中UserId與FD系結
15     public function onOpen(Server $server, Request $request)
16     {
17         // var_dump(app('swoole') === $server);// 同一實體
18         /**
19          * 獲取當前登錄的用戶
20          * 此特性要求建立WebSocket連接的路徑要經過Authenticate之類的中間件,
21          * 例如:
22          * 瀏覽器端:var ws = new WebSocket("ws://127.0.0.1:5200/ws");
23          * 那么Laravel中/ws路由就需要加上類似Authenticate的中間件,
24          */
25         // $user = Auth::user();
26         // $userId = $user ? $user->id : 0; // 0 表示未登錄的訪客用戶
27         $userId = mt_rand(1000, 10000);
28         $this->wsTable->set('uid:' . $userId, ['value' => $request->fd]);// 系結uid到fd的映射
29         $this->wsTable->set('fd:' . $request->fd, ['value' => $userId]);// 系結fd到uid的映射
30         $server->push($request->fd, "Welcome to LaravelS #{$request->fd}");
31     }
32     public function onMessage(Server $server, Frame $frame)
33     {
34         // 廣播
35         foreach ($this->wsTable as $key => $row) {
36             if (strpos($key, 'uid:') === 0 && $server->isEstablished($row['value'])) {
37                 $content = sprintf('Broadcast: new message "%s" from #%d', $frame->data, $frame->fd);
38                 $server->push($row['value'], $content);
39             }
40         }
41     }
42     public function onClose(Server $server, $fd, $reactorId)
43     {
44         $uid = $this->wsTable->get('fd:' . $fd);
45         if ($uid !== false) {
46             $this->wsTable->del('uid:' . $uid['value']); // 解綁uid映射
47         }
48         $this->wsTable->del('fd:' . $fd);// 解綁fd映射
49         $server->push($fd, "Goodbye #{$fd}");
50     }
51 }

 

多埠混合協議

更多的資訊,請參考Swoole增加監聽的埠與多埠混合協議

為了使我們的主服務器能支持除HTTPWebSocket外的更多協議,我們引入了Swoole多埠混合協議特性,在LaravelS中稱為Socket,現在,可以很方便地在Laravel上構建TCP/UDP應用,

  1. 創建Socket處理類,繼承Hhxsv5\LaravelS\Swoole\Socket\{TcpSocket|UdpSocket|Http|WebSocket}

     1 namespace App\Sockets;
     2 use Hhxsv5\LaravelS\Swoole\Socket\TcpSocket;
     3 use Swoole\Server;
     4 class TestTcpSocket extends TcpSocket
     5 {
     6     public function onConnect(Server $server, $fd, $reactorId)
     7     {
     8         \Log::info('New TCP connection', [$fd]);
     9         $server->send($fd, 'Welcome to LaravelS.');
    10     }
    11     public function onReceive(Server $server, $fd, $reactorId, $data)
    12     {
    13         \Log::info('Received data', [$fd, $data]);
    14         $server->send($fd, 'LaravelS: ' . $data);
    15         if ($data =https://www.cnblogs.com/a609251438/p/== "quit\r\n") {
    16             $server->send($fd, 'LaravelS: bye' . PHP_EOL);
    17             $server->close($fd);
    18         }
    19     }
    20     public function onClose(Server $server, $fd, $reactorId)
    21     {
    22         \Log::info('Close TCP connection', [$fd]);
    23         $server->send($fd, 'Goodbye');
    24     }
    25 }

     

    這些連接和主服務器上的HTTP/WebSocket連接共享Worker行程,因此可以在這些事件操作中使用LaravelS提供的異步任務投遞SwooleTable、Laravel提供的組件如DBEloquent等,同時,如果需要使用該協議埠的Swoole\Server\Port物件,只需要像如下代碼一樣訪問Socket類的成員swoolePort即可,

    public function onReceive(Server $server, $fd, $reactorId, $data)
    {
        $port = $this->swoolePort; //獲得`Swoole\Server\Port`物件
    }

     

  2. 注冊套接字,

     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         'settings' => [// 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/onClose事件,原因是這兩種模式下無法保證onConnect/onClose/onReceive的順序,如果需要用到這兩個事件,請將dispatch_mode改為2/4/5,參考,

    'swoole' => [
        //...
        'dispatch_mode' => 2,
        //...
    ];

     

  3. 測驗,
  • TCP:telnet 127.0.0.1 5291
  • UDP:Linux下 echo "Hello LaravelS" > /dev/udp/127.0.0.1/5292
  1. 其他協議的注冊示例,

    • 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
     1 'sockets' => [
     2     [
     3         'host'     => '0.0.0.0',
     4         'port'     => 5293,
     5         'type'     => SWOOLE_SOCK_TCP,
     6         'settings' => [
     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         'settings' => [
     7             'open_http_protocol'      => true,
     8             'open_websocket_protocol' => true,
     9         ],
    10         'handler'  => \App\Sockets\TestWebSocket::class,
    11     ],
    12 ],
    13 協程

     

Swoole原始檔案
  • 警告:協程下代碼執行順序是亂序的,請求級的資料應該以協程ID隔離,但Laravel/Lumen中存在很多單例、靜態屬性,不同請求間的資料會相互影響,這是不安全的,比如資料庫連接就是單例,同一個資料庫連接共享同一個PDO資源,這在同步阻塞模式下是沒問題的,但在異步協程下是不行的,每次查詢需要創建不同的連接,維護不同的IO狀態,這就需要用到連接池,所以不要打開協程,僅自定義行程中可使用協程,
  • 啟用協程,默認是關閉的,

    1 // 修改檔案 `config/laravels.php`
    2 [
    3     //...
    4     'swoole' => [
    5         //...
    6         'enable_coroutine' => true
    7      ],
    8 ]

     

  • 協程客戶端:需Swoole>=2.0
  • 運行時協程:需Swoole>=4.1.0,同時啟用下面的配置,

    // 修改檔案 `config/laravels.php`
    [
        //...
        'enable_coroutine_runtime' => true
    ]

     

自定義行程

支持開發者創建一些特殊的作業行程,用于監控、上報或者其他特殊的任務,參考addProcess,
  1. 創建Proccess類,實作CustomProcessInterface介面,

     1 namespace App\Processes;
     2 use App\Tasks\TestTask;
     3 use Hhxsv5\LaravelS\Swoole\Process\CustomProcessInterface;
     4 use Hhxsv5\LaravelS\Swoole\Task\Task;
     5 use Swoole\Coroutine;
     6 use Swoole\Http\Server;
     7 use Swoole\Process;
     8 class TestProcess implements CustomProcessInterface
     9 {
    10     public static function getName()
    11     {
    12         // 行程名稱
    13         return 'test';
    14     }
    15     public static function callback(Server $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.1
    22             Coroutine::sleep(1); // Swoole>=2.1 callback()方法已自動創建了協程,
    23             // 自定義行程中也可以投遞Task,但不支持Task的finish()回呼,
    24             // 注意:
    25             // 1.引數2需傳true
    26             // 2.config/laravels.php中修改配置task_ipc_mode為1或2,參考 https://wiki.swoole.com/wiki/page/296.html
    27             $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(Server $swoole, Process $process)
    35     {
    36         // Stop the process...
    37         // Then end process
    38         $process->exit(0);
    39     }
    40 }

     

  2. 注冊TestProcess,

     1 // 修改檔案 config/laravels.php
     2 // ...
     3 'processes' => [
     4     [
     5         'class'    => \App\Processes\TestProcess::class,
     6         'redirect' => false, // 是否重定向輸入輸出
     7         'pipe'     => 0 // 管道型別:0不創建管道,1創建SOCK_STREAM型別管道,2創建SOCK_DGRAM型別管道
     8         'enable'   => true // 是否啟用,默認true
     9     ],
    10 ],

     

  3. 注意:TestProcess::callback()方法不能退出,如果退出次數達到10次,Manager行程將會重新創建行程,

其他特性

配置Swoole的事件回呼函式

支持的事件串列:

事件需實作的介面發生時機
ServerStart Hhxsv5LaravelSSwooleEventsServerStartInterface 發生在Master行程啟動時,此事件中不應處理復雜的業務邏輯,只能做一些初始化的簡單作業
ServerStop Hhxsv5LaravelSSwooleEventsServerStopInterface 發生在Server正常退出時,此事件中不能使用異步或協程相關的API
WorkerStart Hhxsv5LaravelSSwooleEventsWorkerStartInterface 發生在Worker/Task行程啟動完成后
WorkerStop Hhxsv5LaravelSSwooleEventsWorkerStopInterface 發生在Worker/Task行程正常退出后
WorkerError Hhxsv5LaravelSSwooleEventsWorkerErrorInterface 發生在Worker/Task行程發生例外或致命錯誤時

1.創建事件處理類,實作相應的介面,

 1 namespace App\Events;
 2 use Hhxsv5\LaravelS\Swoole\Events\ServerStartInterface;
 3 use Swoole\Atomic;
 4 use Swoole\Http\Server;
 5 class ServerStartEvent implements ServerStartInterface
 6 {
 7     public function __construct()
 8     {
 9     }
10     public function handle(Server $server)
11     {
12         // 初始化一個全域計數器(跨行程的可用)
13         $server->atomicCount = new Atomic(2233);
14 
15         // 控制器中呼叫:app('swoole')->atomicCount->get();
16     }
17 }
18 namespace App\Events;
19 use Hhxsv5\LaravelS\Swoole\Events\WorkerStartInterface;
20 use Swoole\Http\Server;
21 class WorkerStartEvent implements WorkerStartInterface
22 {
23     public function __construct()
24     {
25     }
26     public function handle(Server $server, $workerId)
27     {
28         // 初始化一個資料庫連接池物件
29         // DatabaseConnectionPool::init();
30     }
31 }

 

2.配置,

1 // 修改檔案 config/laravels.php
2 'event_handlers' => [
3     'ServerStart' => \App\Events\ServerStartEvent::class,
4     'WorkerStart' => \App\Events\WorkerStartEvent::class,
5 ],

 

注意事項

  • 單例問題

    • 傳統FPM下,單例模式的物件的生命周期僅在每次請求中,請求開始=>實體化單例=>請求結束后=>單例物件資源回收,
    • Swoole Server下,所有單例物件會常駐于記憶體,這個時候單例物件的生命周期與FPM不同,請求開始=>實體化單例=>請求結束=>單例物件依舊保留,需要開發者自己維護單例的狀態,
    • 常見的解決方案:

      1. 寫一個XxxCleaner類來清理單例物件狀態,此類需實作介面Hhxsv5\LaravelS\Illuminate\Cleaners\CleanerInterface,然后注冊到laravels.phpcleaners中,
      2. 用一個中間件重置單例物件的狀態,
      3. 如果是以ServiceProvider注冊的單例物件,可添加該ServiceProviderlaravels.phpregister_providers中,這樣每次請求會重新注冊該ServiceProvider,重新實體化單例物件,參考,
    • LaravelS 已經內置了一些Cleaner,
  • 常見問題:一攬子的已知問題和解決方案,
  • 除錯方式:記錄日志、Laravel Dump Server(Laravel 5.7已默認集成)
  • 應通過Illuminate\Http\Request物件來獲取請求資訊,是可讀取的,_SERVER是部分可讀的,不能使用、_POST、、_COOKIE、、_SESSION、$GLOBALS,

     1 public function form(\Illuminate\Http\Request $request)
     2 {
     3     $name = $request->input('name');
     4     $all = $request->all();
     5     $sessionId = $request->cookie('sessionId');
     6     $photo = $request->file('photo');
     7     // 呼叫getContent()來獲取原始的POST body,而不能用file_get_contents('php://input')
     8     $rawContent = $request->getContent();
     9     //...
    10 }

     

  • 推薦通過回傳Illuminate\Http\Response物件來回應請求,兼容echo、vardump()、print_r(),不能使用函式 dd()、exit()、die()、header()、setcookie()、http_response_code(),

    public function json()
    {
        return response()->json(['time' => time()])->header('header1', 'value1')->withCookie('c1', 'v1');
    }

     

  • 各種單例的連接將被常駐記憶體,建議開啟持久連接
  1. 資料庫連接,連接斷開后會自動重連

     1 // config/database.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         'database'  => env('DB_MY_CONN_DATABASE', 'forge'),
     8         'username'  => env('DB_MY_CONN_USERNAME', 'forge'),
     9         'password'  => env('DB_MY_CONN_PASSWORD', ''),
    10         'charset'   => 'utf8mb4',
    11         'collation' => 'utf8mb4_unicode_ci',
    12         'prefix'    => '',
    13         'strict'    => false,
    14         'options'   => [
    15             // 開啟持久連接
    16             \PDO::ATTR_PERSISTENT => true,
    17         ],
    18     ],
    19     //...
    20 ],
    21 //...

     

  2. Redis連接,連接斷開后不會立即自動重連,會拋出一個關于連接斷開的例外,下次會自動重連,需確保每次操作Redis前正確的SELECT DB

     1 // config/database.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             'database'   => 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內核引數調整
  • 壓力測驗

用戶與案例

  • KuCoin
  • 醫聯:WEB站、M站、APP、小程式的賬戶體系服務,
  • ITOK在線客服平臺:用戶IT工單的處理跟蹤及在線實時溝通,
  • 盟呱呱
  • 微信公眾號-廣州塔:活動、商城
  • 企鵝游戲盒子、明星新勢力、以及小程式廣告服務
  • 小程式-修機匠手機上門維修服務:手機維修服務,提供上門服務,支持在線維修,
  • 億健APP

 

推薦閱讀:

實作websocket 主動訊息推送,用laravel+Swoole

PHP laravel+thrift+swoole打造微服務框架

用Swoole+React 實作的聊天室

Swoole和Redis實作的并發佇列處理系統

php Swoole實作毫秒級定時任務    

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/105963.html

標籤:PHP

上一篇:【Linux系列】Centos 7部署Laravel專案(七)

下一篇:Redis的面試問題總結,面試跳槽必備

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more