# Laravel WebSocket 建置 1. Terminal輸入`composer create-project laravel/laravel=7.* XXX` 安裝Larave7最後版本 `Laravel_websocket` 2. 安裝登入系統 `composer require laravel/ui` `php artisan ui vue --auth` `npm install` `npm run dev` 3. 建立資料庫 4. 安裝Laravel WebSocket套件 Terminal輸入`composer require beyondcode/laravel-websockets` 此時安裝版本有機率為2.0 beta版本 ![](https://hackmd.io/_uploads/BkuDNkR63.png) 要在project裡的檔案composer.json中手動更改為1.14並且Terminal輸入`composer update` 更新至版本1.14 ![](https://hackmd.io/_uploads/BkFoVkCah.png) 5. 設定 .env ```php= DB_DATABASE='websocket_3' BROADCAST_DRIVER=pusher PUSHER_APP_ID=room PUSHER_APP_KEY=chatchat PUSHER_APP_SECRET=chatchatchat PUSHER_APP_CLUSTER=ap1 PUSHER_APP_PORT=6001 ``` >DB_DATABASE='websocket_3' >//資料庫名稱 > >BROADCAST_DRIVER=pusher >//使用pusher的部分功能 > >PUSHER_APP_ID=room >//可自行設定 > >PUSHER_APP_KEY=chatchat >//可自行設定 > >PUSHER_APP_SECRET=chatchatchat >//可自行設定 > >PUSHER_APP_PORT=6001 >//預設port為6001 6. Terminal輸入 `php artisan vendor:publish provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider" --tag="migrations"` `php artisan migrate` `php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider" --tag="config"` >發布套件相關設定 7. 安裝Pusher&Laravel Echo廣播套件 Terminal輸入: `composer require pusher/pusher-php-server "~3.0"` `npm install --save-dev laravel-echo pusher-js` 8. config/broadcasting.php Server端Websocket設定 ```php= 'pusher' => [ 'driver' => 'pusher', 'key' => env('PUSHER_APP_KEY'), 'secret' => env('PUSHER_APP_SECRET'), 'app_id' => env('PUSHER_APP_ID'), 'options' => [ 'cluster' => env('PUSHER_APP_CLUSTER'), 'encrypted' => true, 'host' => '127.0.0.1', 'port' => env('PUSHER_APP_PORT'), 'scheme' => 'http' ], ], ``` 9. config/websockets.php (可不設定) ```php= <?php return [ /* * This package comes with multi tenancy out of the box. Here you can * configure the different apps that can use the webSockets server. * * Optionally you can disable client events so clients cannot send * messages to each other via the webSockets. */ 'apps' => [ [ 'id' => env('PUSHER_APP_ID'), 'name' => env('APP_NAME'), 'key' => env('PUSHER_APP_KEY'), 'secret' => env('PUSHER_APP_SECRET'), 'enable_client_messages' => false, 'enable_statistics' => true, ], ], /* * This class is responsible for finding the apps. The default provider * will use the apps defined in this config file. * * You can create a custom provider by implementing the * `AppProvider` interface. */ 'app_provider' => BeyondCode\LaravelWebSockets\Apps\ConfigAppProvider::class, /* * This array contains the hosts of which you want to allow incoming requests. * Leave this empty if you want to accept requests from all hosts. */ 'allowed_origins' => [ // ], /* * The maximum request size in kilobytes that is allowed for an incoming WebSocket request. */ 'max_request_size_in_kb' => 250, /* * This path will be used to register the necessary routes for the package. */ 'path' => 'laravel-websockets', 'statistics' => [ /* * This model will be used to store the statistics of the WebSocketsServer. * The only requirement is that the model should extend * `WebSocketsStatisticsEntry` provided by this package. */ 'model' => \BeyondCode\LaravelWebSockets\Statistics\Models\WebSocketsStatisticsEntry::class, /* * Here you can specify the interval in seconds at which statistics should be logged. */ 'interval_in_seconds' => 60, /* * When the clean-command is executed, all recorded statistics older than * the number of days specified here will be deleted. */ 'delete_statistics_older_than_days' => 60, /* * Use an DNS resolver to make the requests to the statistics logger * default is to resolve everything to 127.0.0.1. */ 'perform_dns_lookup' => false, ], /* * Define the optional SSL context for your WebSocket connections. * You can see all available options at: http://php.net/manual/en/context.ssl.php */ 'ssl' => [ /* * Path to local certificate file on filesystem. It must be a PEM encoded file which * contains your certificate and private key. It can optionally contain the * certificate chain of issuers. The private key also may be contained * in a separate file specified by local_pk. */ 'local_cert' => null, /* * Path to local private key file on filesystem in case of separate files for * certificate (local_cert) and private key. */ 'local_pk' => null, /* * Passphrase for your local_cert file. */ 'passphrase' => null ], ]; ``` 10. resources/js/app.js 登入系統使用預設Vue來運作 但在本次不需要使用所以 Vue相關的都刪除or註解 ```php= // window.Vue = require('vue'); // Vue.component('example-component', require('./components/ExampleComponent.vue').default); // const app = new Vue({ // el: '#app', // }); ``` 11. resources/js/bootstrap.js Client端WebSocket廣播系統設定 ```php= 最上面新增import import Echo from 'laravel-echo' ; import Pusher from 'pusher-js' ; --------------------------------- 最下面新增 window.Pusher = require('pusher-js'); window.Echo = new Echo({ broadcaster: 'pusher', key: process.env.MIX_PUSHER_APP_KEY, cluster: process.env.MIX_PUSHER_APP_CLUSTER, wsPath: '/liam/hao', wsHost: window.location.hostname, wsPort: 6001, forceTLS: false, disableStats: true, enabledTransports: ['ws', 'wss'], }); ``` 12. routes/web.php 路由設定 ```php= use BeyondCode\LaravelWebSockets\Facades\WebSocketsRouter; use BeyondCode\LaravelWebSockets\WebSockets\Channels\Channel; use BeyondCode\LaravelWebSockets\WebSockets\Channels\PrivateChannel; Route::get('/', function () { return view('websocket'); }); WebSocketsRouter::webSocket('/liam/hao/app/{appKey}', \App\Http\Controllers\MyWebsocketHandler::class); ``` 13. resources/views/websocket.blade.php Client端程式碼 ```php= <!DOCTYPE html> <html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <head> <meta charset="utf-8"> <meta name="csrf-token" content="{{ csrf_token() }}"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Websocket</title> <link href="https://fonts.googleapis.com/css2?family=Nunito:wght@200;600&display=swap" rel="stylesheet"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css" integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N" crossorigin="anonymous"> <style> html, body { background-color: #fff; color: #636b6f; font-family: 'Nunito', sans-serif; font-weight: 200; } </style> </head> <body> <div class="mt-5"> <div class="d-inline-block ml-5 font-weight-bold"> <textarea class="d-inline-block font-weight-bold" cols="50" rows="10" id="txtShow" disabled></textarea> <br> <div class="d-inline-block"> <div id="idn" class="d-block">User = {{ auth()->user()?auth()->user()->name:'???' }}</div> <input type="text" id="txtInput" class="d-block mb-2"> @if (auth()->user()) <input type="button" id="enter" class="d-inline-block" value="進入頻道"> @else <div class="d-inline-block border border-dark p-1"> 無登入帳號( 無法訂閱頻道 ) </div> @endif <input type="submit" id="btnSend" class="d-inline-block" value="送出"> </div> <div id="idch" class="d-block font-weight-bold">Status:</div> <a href="{{route('login')}}"class="d-inline-block"><button>登入頁面</button></a> </div> </div> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.0/jquery.min.js" integrity="sha512-3gJwYpMe3QewGELv8k/BX9vcqhryRdzRMxVfq6ngyWXwo03GFEzjsUm8Q7RZcHPHksttq7/GFoxjCVUjkjvPdw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> <script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.min.js" integrity="sha384-+sLIOodYLS7CIrQpBjl+C7nPvqq+FbNUBDunl/OZv93DB7Ln/533i8e/mZXLi/P+" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-Fy6S3B9q64WdZWQUiU+q4/2Lc9npb8tCaSX9FK7E8HnRr0Jz8D6OP9dO5Vg3Q9ct" crossorigin="anonymous"></script> <script src="{{ asset('js/app.js') }}"></script> <script> let orderId = 20; let keyinDom = $('#txtInput'); let showDom = $('#txtShow'); let name = "{{ auth()->user()?auth()->user()->name:'???' }}"; let chat = ''; let hr = ''; let min = ''; let sec = ''; let time = ''; function timeSet() { hr = new Date().getHours() < 10 ? '0' + new Date().getHours() : new Date().getHours(); min = new Date().getMinutes() < 10 ? '0' + new Date().getMinutes() : new Date().getMinutes(); sec = new Date().getSeconds() < 10 ? '0' + new Date().getSeconds() : new Date().getSeconds(); time= hr + ":" + min + ":" + sec + " |"; } // 送出留言 $(document).on('click','#btnSend',function () { let txt = keyinDom.val(); console.log('click',txt); Echo.connector.pusher.send_event('Send_Message', { Name: name, Message: keyinDom.val(), }); keyinDom.val(''); }) // Enter鍵發送 $(document).on('keypress','#txtInput',function () { let txt = keyinDom.val(); if (event.key === "Enter") { // if (event.which === 13) { if (txt.length === 0 || txt.trim() == "") { } else { event.preventDefault(); $("#btnSend").click(); } } }) // 訂閱頻道 $(document).on('click','#enter',function () { Echo.private(`order.${orderId}`) .listen('.pusher:Send_Message', (e) => { console.log(e); }); console.log('Echo Channel List:',Echo.connector.channels); var Allchannel=[]; $.each(Echo.connector.channels,function (index,data) { Allchannel.push(data.name); }); $('#idch').html('Status:'+'<br>'+'已進入頻道:'+Allchannel); }) // 接收到廣播的後續處理 Echo.connector.pusher.connection.bind('message',function (e) { timeSet(); console.log(e); if (e.event=='Send_Message') { let commenter = e.data.Name; let message = e.data.Message; chat=`${chat} ${time} ${commenter} : ${message}\n`; showDom.val(chat); } if (e.event=='pusher:subscribe') { chat=`${chat} ${time} 使用者進入頻道\n`; showDom.val(chat); } }) // 離開公共頻道 // Echo.leaveChannel('orders'); // 離開私人頻道 // Echo.leave('orders'); </script> </body> </html> ``` 14. routes/channels.php 開啟頻道 ```php= Broadcast::channel('order.{orderId}', function () { return true; }); Broadcast::channel('orders.{ordersId}', function () { return true; }); ``` 15. app/http/Controllers/MyWebsocketHandler.php WebSocket Server端核心處理器 ```php= <?php namespace App\Http\Controllers; use BeyondCode\LaravelWebSockets\WebSockets\Messages\PusherMessageFactory; use BeyondCode\LaravelWebSockets\WebSockets\WebSocketHandler; use Illuminate\Broadcasting\PrivateChannel; use Ratchet\ConnectionInterface; use Ratchet\RFC6455\Messaging\MessageInterface; class MyWebsocketHandler extends WebSocketHandler { public function onMessage(ConnectionInterface $connection, MessageInterface $message) { parent::onMessage($connection, $message); $payload=json_decode($message->getPayload()); // var_dump($payload->event); $broadcast_channels = $this->channelManager->find($connection->app->id, 'private-order.20'); $send_data=$payload; if ($payload->event !='pusher_internal:subscription_succeeded' && $payload->event !='pusher:ping' && $payload->event !='pusher:pong' && $broadcast_channels) { foreach ($broadcast_channels->getSubscribedConnections() as $broadcast_channel) { $broadcast_channel->send(json_encode($send_data)); } } } } ``` 16. config/app.php 把註解解除 `App\Providers\BroadcastServiceProvider::class,` 17. Terminal輸入`npm run dev` 開啟本地伺服器,在更動程式碼後進行更新 18. 實際運行後Terminal有可能會出現Error 如下圖↓ ![](https://hackmd.io/_uploads/BkbsiTj-a.jpg) 這是因為版本不同而造成的問題 在composer.lock檔 搜尋`react/promise` 在有安裝react/promise的情境下, 會有10個結果 第7個會如下圖一樣版本為3.0 ![](https://hackmd.io/_uploads/SklPA6sW6.png) 在react/promise 3.0版本中 有模組已經被移除了, 但pusher-server還需要使用那個模組,所以造成找不到模組而Error **解決方法**為 ==降檔== 在Terminal輸入指令`composer require react/promise:^2.7` 安裝react/promise 2.7版本 2.7版本中模組仍存在,Pusher-server也可以找的到,所以Error就不會發生 ### ==[這個Error在Github上的討論串](https://github.com/beyondcode/laravel-websockets/issues/1142)== ![](https://hackmd.io/_uploads/H1O-5ao-6.png) 19. 在登入頁面進行登入後,方可開始訂閱頻道 ![](https://hackmd.io/_uploads/r1kw-nz0n.png) ### Websocket Server端伺服器 開啟指令:`php artisan websockets:serve` ### Websocket Server端伺服器 開啟指令:`php artisan websockets:serve` ### Websocket Server端伺服器 開啟指令:`php artisan websockets:serve` 需要指定開在某個port的指令: ```shell= php artisan websockets:serve --port=xxxx example: php artisan websockets:serve --port=7001 ``` >參考資料 >https://beyondco.de/docs/laravel-websockets/getting-started/introduction >https://learnku.com/articles/55724 >https://laravel.com/docs/7.x/broadcasting#using-example-application >https://github.com/beyondcode/laravel-websockets/issues/1142