Laravel-Websockets组件实践

给Laravel项目搭建Websocket服务时,不想用pusher这种在线服务? 觉得用laravel-echo-server监听事件不方便? 不想用swoole自己实现? 那你可以考虑Laravel官方推荐的laravel-websockets组件。

该组件有以下优点:

  • 免费
  • 使用Pusher驱动
  • 方便继承扩展,实现流程控制

引入

1
2
composer require "beyondcode/laravel-websockets:^2.0"
composer require digitaltolk/multiple-broadcaster

1.x版本存在一些问题,扩展的时候有一些麻烦,组件作者也是推荐2.x版本

安装

1
2
3
php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider" --tag="migrations"
php artisan migrate
php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider" --tag="config"

自定义

需求

  • 后台用户和普通用户的服务互不干扰
  • 监听普通用户的行为

事件

创建几个基础事件。

  • UserEnterChannel: 用户订阅channel
  • UserExitChannel: 用户取消订阅channel
  • UserAppBackground: 用户APP切换至后台
  • UserAppForeground: 用户APP切换至前台台

继承BeyondCode\LaravelWebSockets\Server\WebSocketHandler,监听客户端消息,触发UserAppBackgroundUserAppForeground事件。

注意WebSocketHandler@onOpen($connection)中可以获取的信息非常有限,无法识别出用户。组件自带的事件NewConnection和ConnectionClosed也只有appId和socketId。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
<?php

namespace App\Services\WebSockets\Server;

use App\Events\UserAppBackground;
use App\Events\UserAppForeground;
use App\Services\WebSockets\ChannelManagers\LocalChannelManager;
use BeyondCode\LaravelWebSockets\Server\WebSocketHandler as BaseWebSocketHandler;
use Ratchet\ConnectionInterface;
use Exception;
use Ratchet\RFC6455\Messaging\MessageInterface;

/**
* Class Handler
* @package App\WebSockets
* @link
*/
class WebSocketHandler extends BaseWebSocketHandler
{
/**
* The channel manager.
*
* @var LocalChannelManager
*/
protected $channelManager;

/**
* Handle the socket opening.
*
* @param \Ratchet\ConnectionInterface $connection
* @return void
* @throws \Exception
*/
public function onOpen(ConnectionInterface $connection)
{
parent::onOpen($connection);
}

/**
* Handle the websocket close.
*
* @param \Ratchet\ConnectionInterface $connection
* @return void
*/
public function onClose(ConnectionInterface $connection)
{
parent::onClose($connection);
}

/**
* Handle the websocket errors.
*
* @param \Ratchet\ConnectionInterface $connection
* @param Exception $exception
* @return void
*/
public function onError(ConnectionInterface $connection, Exception $exception)
{
parent::onError($connection, $exception);
}

/**
* Handle the incoming message.
*
* @param \Ratchet\ConnectionInterface $connection
* @param \Ratchet\RFC6455\Messaging\MessageInterface $message
* @return void
*/
public function onMessage(ConnectionInterface $connection, MessageInterface $message)
{
$payload = json_decode($message->getPayload());

if ($payload->event == 'client-app.background') {
$user = getUserByChannel($payload->channel);
UserAppBackground::dispatch($user->id, $user->gid);
} elseif ($payload->event == 'client-app.foreground') {
$user = getUserByChannel($payload->channel);
UserAppForeground::dispatch($user->id, $user->gid);
}

parent::onMessage($connection, $message);
}
}

频道

主要改写PrivateChanel,触发UserEnterChannelUserExitChannel事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
<?php

namespace App\Services\WebSockets\Channels;

use App\Events\UserEnterChannel;
use App\Events\UserExitChannel;
use App\Services\WebSockets\ChannelManagers\LocalChannelManager;
use BeyondCode\LaravelWebSockets\Channels\PrivateChannel as BasePrivateChannel;
use BeyondCode\LaravelWebSockets\DashboardLogger;
use BeyondCode\LaravelWebSockets\Events\SubscribedToChannel;
use BeyondCode\LaravelWebSockets\Events\UnsubscribedFromChannel;
use BeyondCode\LaravelWebSockets\Server\Exceptions\InvalidSignature;
use Ratchet\ConnectionInterface;
use stdClass;

class PrivateChannel extends BasePrivateChannel
{
/**
* @var LocalChannelManager
*/
protected $channelManager;

/**
* Subscribe to the channel.
*
* @see https://pusher.com/docs/pusher_protocol#presence-channel-events
* @param \Ratchet\ConnectionInterface $connection
* @param \stdClass $payload
* @return bool
* @throws InvalidSignature
*/
public function subscribe(ConnectionInterface $connection, stdClass $payload): bool
{
$this->verifySignature($connection, $payload);

// 针对特殊频道
if (isUserChannel($payload->channel)) {
// 识别用户
$user = getUserByChannel($payload->channel);

// 触发事件
UserEnterChannel::dispatch($user->id, $user->gid);

// 保存在成员数组中
$this->channelManager
->userJoinedPrivateChannel($connection, $user, $this->getName(), $payload)
->then(function ($users) use ($connection) {
$connection->send(json_encode([
'event' => 'pusher_internal:subscription_succeeded',
'channel' => $this->getName(),
'data' => '[]',
]));
})
->then(function () use ($connection, $user, $payload) {
// The `pusher_internal:member_added` event is triggered when a user joins a channel.
// It's quite possible that a user can have multiple connections to the same channel
// (for example by having multiple browser tabs open)
// and in this case the events will only be triggered when the first tab is opened.
$this->channelManager
->getMemberSockets($user->id, $connection->app->id, $this->getName())
->then(function ($sockets) use ($payload, $connection, $user) {
if (count($sockets) === 1) {
$memberAddedPayload = [
'event' => 'pusher_internal:member_added',
'channel' => $this->getName(),
'data' => $payload->channel_data,
];

$this->broadcastToEveryoneExcept(
(object) $memberAddedPayload,
$connection->socketId,
$connection->app->id
);

SubscribedToChannel::dispatch(
$connection->app->id,
$connection->socketId,
$this->getName(),
$user
);
}

DashboardLogger::log($connection->app->id, DashboardLogger::TYPE_SUBSCRIBED, [
'socketId' => $connection->socketId,
'channel' => $this->getName(),
'duplicate-connection' => count($sockets) > 1,
]);
});
});
}

return parent::subscribe($connection, $payload);
}

/**
* Unsubscribe connection from the channel.
*
* @param \Ratchet\ConnectionInterface $connection
* @return bool
*/
public function unsubscribe(ConnectionInterface $connection): bool
{
$truth = parent::unsubscribe($connection);

$this->channelManager
->getChannelUser($connection, $this->getName())
->then(function ($user) {
$user = @json_decode($user);

if ($user) {
// 触发事件
UserExitChannel::dispatch($user->id, $user->gid);
}

return $user;
})
->then(function ($user) use ($connection) {
if (! $user) {
return;
}

$this->channelManager
->userLeftPrivateChannel($connection, $user, $this->getName())
->then(function () use ($connection, $user) {
// The `pusher_internal:member_removed` is triggered when a user leaves a channel.
// It's quite possible that a user can have multiple connections to the same channel
// (for example by having multiple browser tabs open)
// and in this case the events will only be triggered when the last one is closed.
$this->channelManager
->getMemberSockets($user->id, $connection->app->id, $this->getName())
->then(function ($sockets) use ($connection, $user) {
if (count($sockets) === 0) {
$memberRemovedPayload = [
'event' => 'pusher_internal:member_removed',
'channel' => $this->getName(),
'data' => json_encode([
'uid' => $user->id,
]),
];

$this->broadcastToEveryoneExcept(
(object) $memberRemovedPayload,
$connection->socketId,
$connection->app->id
);

UnsubscribedFromChannel::dispatch(
$connection->app->id,
$connection->socketId,
$this->getName(),
$user
);
}
});
});
});

return $truth;
}
}

涉及的几个助手函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?php

if (!function_exists('getUserChannelPrefix')) {
/**
* 获取玩家的频道前缀
*
* @return string
*/
function getUserChannelPrefix(): string
{
return 'private-user.';
}
}

if (!function_exists('isUserChannel')) {
/**
* 判断是否为玩家用户的频道
*
* @param string $channel
* @return bool
*/
function isUserChannel(string $channel): bool
{
$userChannelPrefix = getUserChannelPrefix();
$isUserChannel = Str::startsWith($channel, $userChannelPrefix);

return $isUserChannel;
}
}

if (!function_exists('getIdsByChannel')) {
/**
* 根据玩家频道名获取各ID
*
* @param string $channel
* @return stdClass
*/
function getUserByChannel(string $channel): stdClass
{
$userChannelPrefix = getUserChannelPrefix();

$idString = str_replace($userChannelPrefix, '', $channel);
list($uid, $gid) = explode('.', $idString);

$user = new stdClass();
$user->id = $uid;
$user->gid = $gid;

return $user;
}
}

继承BeyondCode\LaravelWebSockets\ChannelManagers\LocalChannelManager,实现userJoinedPrivateChanneluserLeftPrivateChannelgetChannelUser三个方法,在PrivateChannel中使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
<?php

namespace App\Services\WebSockets\ChannelManagers;

use App\Services\WebSockets\Channels\Channel;
use App\Services\WebSockets\Channels\PresenceChannel;
use App\Services\WebSockets\Channels\PrivateChannel;
use BeyondCode\LaravelWebSockets\ChannelManagers\LocalChannelManager as BaseLocalChannelManager;
use BeyondCode\LaravelWebSockets\Helpers;
use Illuminate\Support\Str;
use Ratchet\ConnectionInterface;
use stdClass;
use React\Promise\PromiseInterface;

class LocalChannelManager extends BaseLocalChannelManager
{
/**
* The list of users that joined the presence channel or private channel.
*
* private channel: key => UserObject
* presence channel: key => [ socketId => UserObject ]
* @var array
*/
protected $users = [];

/**
* The list of users by socket and their attached id.
*
* @var array
*/
protected $userSockets = [];

/**
* Get the channel class by the channel name.
*
* @param string $channelName
* @return string
*/
protected function getChannelClassName(string $channelName): string
{
if (Str::startsWith($channelName, 'private-')) {
return PrivateChannel::class;
}

if (Str::startsWith($channelName, 'presence-')) {
return PresenceChannel::class;
}

return Channel::class;
}

/**
* Get a member from a private channel based on connection.
*
* @param \Ratchet\ConnectionInterface $connection
* @param string $channel
* @return \React\Promise\PromiseInterface
*/
public function getChannelUser(ConnectionInterface $connection, string $channel): PromiseInterface
{
$user = $this->users["{$connection->app->id}:{$channel}"] ?? null;

return Helpers::createFulfilledPromise($user);
}

/**
* Handle the user when it joined a private channel.
*
* @param \Ratchet\ConnectionInterface $connection
* @param stdClass $user
* @param string $channel
* @param stdClass $payload
* @return PromiseInterface[bool]
*/
public function userJoinedPrivateChannel(ConnectionInterface $connection, stdClass $user, string $channel, stdClass $payload): PromiseInterface
{
$this->users["{$connection->app->id}:{$channel}"] = json_encode($user);
$this->userSockets["{$connection->app->id}:{$channel}:{$user->id}"][] = $connection->socketId;
used($payload);

return Helpers::createFulfilledPromise(true);
}

/**
* Handle the user when it left a presence channel.
*
* @param \Ratchet\ConnectionInterface $connection
* @param stdClass $user
* @param string $channel
* @return PromiseInterface[bool]
*/
public function userLeftPrivateChannel(ConnectionInterface $connection, stdClass $user, string $channel): PromiseInterface
{
unset($this->users["{$connection->app->id}:{$channel}"]);

$deletableSocketKey = array_search(
$connection->socketId,
$this->userSockets["{$connection->app->id}:{$channel}:{$user->id}"]
);

if ($deletableSocketKey !== false) {
unset($this->userSockets["{$connection->app->id}:{$channel}:{$user->id}"][$deletableSocketKey]);

if (count($this->userSockets["{$connection->app->id}:{$channel}:{$user->id}"]) === 0) {
unset($this->userSockets["{$connection->app->id}:{$channel}:{$user->id}"]);
}
}

return Helpers::createFulfilledPromise(true);
}
}

配置

修改websockets.php,使用继承后的类,并配置多app。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
<?php

return [

/*
|--------------------------------------------------------------------------
| Dashboard Settings
|--------------------------------------------------------------------------
|
| You can configure the dashboard settings from here.
|
*/

'dashboard' => [

'port' => env('LARAVEL_WEBSOCKETS_PORT', 6001),

'domain' => env('LARAVEL_WEBSOCKETS_DOMAIN'),

'path' => env('LARAVEL_WEBSOCKETS_PATH', 'websockets'),

'middleware' => [
'web',
\BeyondCode\LaravelWebSockets\Dashboard\Http\Middleware\Authorize::class,
],

],

'managers' => [

/*
|--------------------------------------------------------------------------
| Application Manager
|--------------------------------------------------------------------------
|
| An Application manager determines how your websocket server allows
| the use of the TCP protocol based on, for example, a list of allowed
| applications.
| By default, it uses the defined array in the config file, but you can
| anytime implement the same interface as the class and add your own
| custom method to retrieve the apps.
|
*/

'app' => \BeyondCode\LaravelWebSockets\Apps\ConfigAppManager::class,

],

/*
|--------------------------------------------------------------------------
| Applications Repository
|--------------------------------------------------------------------------
|
| By default, the only allowed app is the one you define with
| your PUSHER_* variables from .env.
| You can configure to use multiple apps if you need to, or use
| a custom App Manager that will handle the apps from a database, per se.
|
| You can apply multiple settings, like the maximum capacity, enable
| client-to-client messages or statistics.
|
*/

'apps' => [
[
'id' => 'admin-app-id',
'name' => 'ADMIN',
'host' => null,
'key' => 'admin-app-key',
'secret' => 'admin-app-secret',
'path' => null,
'capacity' => null,
'enable_client_messages' => true,
'enable_statistics' => true,
'allowed_origins' => [
// env('LARAVEL_WEBSOCKETS_DOMAIN_ADMIN'),
],
],
[
'id' => 'api-app-id',
'name' => 'API',
'host' => null,
'key' => 'api-app-key',
'secret' => 'api-app-secret',
'path' => null,
'capacity' => null,
'enable_client_messages' => true,
'enable_statistics' => true,
'allowed_origins' => [
// env('LARAVEL_WEBSOCKETS_DOMAIN'),
],
],
],

/*
|--------------------------------------------------------------------------
| Broadcasting Replication PubSub
|--------------------------------------------------------------------------
|
| You can enable replication to publish and subscribe to
| messages across the driver.
|
| By default, it is set to 'local', but you can configure it to use drivers
| like Redis to ensure connection between multiple instances of
| WebSocket servers. Just set the driver to 'redis' to enable the PubSub using Redis.
|
*/

'replication' => [

'mode' => 'local',

'modes' => [

/*
|--------------------------------------------------------------------------
| Local Replication
|--------------------------------------------------------------------------
|
| Local replication is actually a null replicator, meaning that it
| is the default behaviour of storing the connections into an array.
|
*/

'local' => [

/*
|--------------------------------------------------------------------------
| Channel Manager
|--------------------------------------------------------------------------
|
| The channel manager is responsible for storing, tracking and retrieving
| the channels as long as their members and connections.
|
*/

'channel_manager' => \App\Services\WebSockets\ChannelManagers\LocalChannelManager::class,

/*
|--------------------------------------------------------------------------
| Statistics Collector
|--------------------------------------------------------------------------
|
| The Statistics Collector will, by default, handle the incoming statistics,
| storing them until they will become dumped into another database, usually
| a MySQL database or a time-series database.
|
*/

'collector' => \BeyondCode\LaravelWebSockets\Statistics\Collectors\MemoryCollector::class,

],

'redis' => [

'connection' => env('WEBSOCKETS_REDIS_REPLICATION_CONNECTION', 'default'),

/*
|--------------------------------------------------------------------------
| Channel Manager
|--------------------------------------------------------------------------
|
| The channel manager is responsible for storing, tracking and retrieving
| the channels as long as their members and connections.
|
*/

'channel_manager' => BeyondCode\LaravelWebSockets\ChannelManagers\RedisChannelManager::class,

/*
|--------------------------------------------------------------------------
| Statistics Collector
|--------------------------------------------------------------------------
|
| The Statistics Collector will, by default, handle the incoming statistics,
| storing them until they will become dumped into another database, usually
| a MySQL database or a time-series database.
|
*/

'collector' => \BeyondCode\LaravelWebSockets\Statistics\Collectors\RedisCollector::class,

],

],

],

'statistics' => [

/*
|--------------------------------------------------------------------------
| Statistics Store
|--------------------------------------------------------------------------
|
| The Statistics Store is the place where all the temporary stats will
| be dumped. This is a much reliable store and will be used to display
| graphs or handle it later on your app.
|
*/

'store' => \BeyondCode\LaravelWebSockets\Statistics\Stores\DatabaseStore::class,

/*
|--------------------------------------------------------------------------
| Statistics Interval Period
|--------------------------------------------------------------------------
|
| Here you can specify the interval in seconds at which
| statistics should be logged.
|
*/

'interval_in_seconds' => 60,

/*
|--------------------------------------------------------------------------
| Statistics Deletion Period
|--------------------------------------------------------------------------
|
| 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,

],

/*
|--------------------------------------------------------------------------
| Maximum Request Size
|--------------------------------------------------------------------------
|
| The maximum request size in kilobytes that is allowed for
| an incoming WebSocket request.
|
*/

'max_request_size_in_kb' => 250,

/*
|--------------------------------------------------------------------------
| SSL Configuration
|--------------------------------------------------------------------------
|
| By default, the configuration allows only on HTTP. For SSL, you need
| to set up the the certificate, the key, and optionally, the passphrase
| for the private key.
| You will need to restart the server for the settings to take place.
|
*/

'ssl' => [

'local_cert' => env('LARAVEL_WEBSOCKETS_SSL_LOCAL_CERT', null),

'capath' => env('LARAVEL_WEBSOCKETS_SSL_CA', null),

'local_pk' => env('LARAVEL_WEBSOCKETS_SSL_LOCAL_PK', null),

'passphrase' => env('LARAVEL_WEBSOCKETS_SSL_PASSPHRASE', null),

'verify_peer' => env('APP_ENV') === 'production',

'allow_self_signed' => env('APP_ENV') !== 'production',

],

/*
|--------------------------------------------------------------------------
| Route Handlers
|--------------------------------------------------------------------------
|
| Here you can specify the route handlers that will take over
| the incoming/outgoing websocket connections. You can extend the
| original class and implement your own logic, alongside
| with the existing logic.
|
*/

'handlers' => [

'websocket' => \App\Services\WebSockets\Server\WebSocketHandler::class,

'health' => \BeyondCode\LaravelWebSockets\Server\HealthHandler::class,

'trigger_event' => \BeyondCode\LaravelWebSockets\API\TriggerEvent::class,

'fetch_channels' => \BeyondCode\LaravelWebSockets\API\FetchChannels::class,

'fetch_channel' => \BeyondCode\LaravelWebSockets\API\FetchChannel::class,

'fetch_users' => \BeyondCode\LaravelWebSockets\API\FetchUsers::class,

],

/*
|--------------------------------------------------------------------------
| Promise Resolver
|--------------------------------------------------------------------------
|
| The promise resolver is a class that takes a input value and is
| able to make sure the PHP code runs async by using ->then(). You can
| use your own Promise Resolver. This is usually changed when you want to
| intercept values by the promises throughout the app, like in testing
| to switch from async to sync.
|
*/

'promise_resolver' => \React\Promise\FulfilledPromise::class,

];

修改broadcasting.php,以实现同时在两个app中进行广播

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
<?php

return [

/*
|--------------------------------------------------------------------------
| Default Broadcaster
|--------------------------------------------------------------------------
|
| This option controls the default broadcaster that will be used by the
| framework when an event needs to be broadcast. You may set this to
| any of the connections defined in the "connections" array below.
|
| Supported: "pusher", "redis", "log", "null"
|
*/

'default' => env('BROADCAST_DRIVER', 'null'),

/*
|--------------------------------------------------------------------------
| Broadcast Connections
|--------------------------------------------------------------------------
|
| Here you may define all of the broadcast connections that will be used
| to broadcast events to other systems or over websockets. Samples of
| each available type of connection are provided inside this array.
|
*/

'connections' => [

'pusher' => [
'driver' => 'multiple',
'connections' => ['pusher-for-admin', 'pusher-for-api'],
'options' => [
'cluster' => 'mt1',
'useTLS' => true,
'encrypted' => true,
'host' => '127.0.0.1',
'port' => 6001,
'scheme' => 'http',
],
],

'pusher-for-admin' => [
'driver' => 'pusher',
'key' => 'admin-app-key',
'secret' => 'admin-app-secret',
'app_id' => 'admin-app-id',
'options' => [
'cluster' => 'mt1',
'useTLS' => true,
'encrypted' => true,
'host' => '127.0.0.1',
'port' => 6001,
'scheme' => 'http',
],
],

'pusher-for-api' => [
'driver' => 'pusher',
'key' => 'api-app-key',
'secret' => 'api-app-secret',
'app_id' => 'api-app-id',
'options' => [
'cluster' => 'mt1',
'useTLS' => true,
'encrypted' => true,
'host' => '127.0.0.1',
'port' => 6001,
'scheme' => 'http',
],
],

'redis' => [
'driver' => 'redis',
'connection' => 'default',
],

'log' => [
'driver' => 'log',
],

'null' => [
'driver' => 'null',
],

],

];

最后将ENV文件中的BROADCAST_DRIVER改为pusher即可。

启动

1
php artisan websockets:serve

客户端接入

后台Nodejs

1
2
3
4
5
6
7
8
9
10
11
12
import Echo from 'laravel-echo';

window.Pusher = require('pusher-js');

window.Echo = new Echo({
broadcaster: 'pusher',
key: 'admin-app-key',
wsHost: window.location.hostname,
wsPort: 6001,
forceTLS: false,
disableStats: true,
});

Android 使用 pusher-websocket-java SDK
IOS 使用 pusher-websocket-swift SDK

连接参数

1
2
3
4
host: {Websocket服务域名} (我这边和laravel项目域名是同一个)
port: 6001
app_key: api-app-key
auth_url: {laravel项目域名}/broadcasting/auth

注意pusher-websocket-swift要使用8.0.0版本,9.x版本有bug无法连接服务