Flutter WebSocket通信插件pusher_websockets_client的使用

Flutter WebSocket通信插件pusher_websockets_client的使用


简介

dart_pusher_channels 是一个实现纯 Dart 的 Pusher Channels 客户端库。该库的结构根据 Pusher Channels 协议的官方文档进行构建。

从版本 1.0.0 开始,该库提供了更规范的 Pusher Channels 客户端 API。如果您有使用旧版本的项目,可以在 pubspec.yaml 中设置依赖项:

dart_pusher_channels: 0.3.1+1

主要里程碑

从版本 1.1.0 开始,实现了以下主要功能:

  • 存在通道 ✓
  • 私有加密通道 ✓
  • 触发客户端事件 ✓
  • 在所有六个平台上进行了测试 ✓

贡献者

维护者:Kerim Amansaryyev

贡献者:

使用说明

选项

为了通过 Pusher Channels 协议连接到服务器,客户端必须提供一些元数据作为 URL。可以使用 PusherChannelsOptions 的一个构造函数来满足不同的用例需求。

PusherChannelsOptions.fromCluster

如果您的 URL 模式类似于 {scheme}://ws-{cluster_name}.{host}:{port}/app/{key},可以使用此构造函数:

const clusterOptions = PusherChannelsOptions.fromCluster(
  scheme: 'wss', // 通常为 ws 或 wss
  cluster: 'mt1', // 应用程序的集群
  key: 'a0173cd5499b34d93109', // 应用程序的密钥
  host: 'pusher.com', // 自定义主机名
  shouldSupplyMetadataQueries: true, // 是否发送额外的元数据查询参数
  metadata: PusherChannelsOptionsMetadata.byDefault(), // 应用自定义元数据
  port: 443,
);

print(clusterOptions.uri.toString()); // 输出: wss://ws-mt1.pusher.com:443/app/a0173cd5499b34d93109?client=dart&version=0.8.0&protocol=7
PusherChannelsOptions.fromHost

如果您的 Pusher Channels 安装在自己的域名主机上,且 URL 模式类似于 {scheme}://{host}:{port}/app/{key},可以使用此构造函数:

const hostOptions = PusherChannelsOptions.fromHost(
  scheme: 'wss',
  host: 'my.domain.com',
  key: 'my_key',
  shouldSupplyMetadataQueries: true,
  metadata: PusherChannelsOptionsMetadata.byDefault(),
  port: 443,
);

print(hostOptions.uri.toString()); // 输出: wss://my.domain.com:443/app/my_key?client=dart&version=0.8.0&protocol=7
PusherChannelsOptions.custom

如果上述构造函数都不符合您的用例需求,可以使用此构造函数:

final customOptions = PusherChannelsOptions.custom(
  uriResolver: (metadata) => Uri.parse('wss://my.custom.domain/my/custom/path'),
);

print(customOptions.uri.toString()); // 输出: wss://my.custom.domain/my/custom/path
客户端

当您已经决定使用哪种选项时,可以将其应用到 PusherChannelsClient 的实例中。

PusherChannelsClient.websocket

使用此构造函数可以构建支持 WebSocket 连接的客户端。该库依赖于 web_socket_channel 来支持此功能:

const testOptions = PusherChannelsOptions.fromCluster(
  scheme: 'wss',
  cluster: 'mt1',
  key: 'a0173cd5499b34d93109',
  host: 'pusher.com',
  shouldSupplyMetadataQueries: true,
  metadata: PusherChannelsOptionsMetadata.byDefault(),
  port: 443,
);

final client = PusherChannelsClient.websocket(
  options: testOptions,
  connectionErrorHandler: (exception, trace, refresh) {
    // 处理连接错误
    refresh(); // 重新连接客户端
  },
  minimumReconnectDelayDuration: const Duration(seconds: 1),
  defaultActivityDuration: const Duration(seconds: 120),
  activityDurationOverride: const Duration(seconds: 120),
  waitForPongDuration: const Duration(seconds: 30),
);
自定义连接实现

如前所述,该库支持通过 WebSocket 使用 PusherChannelsClient.websocket 进行连接。您可以实现自己的 PusherChannelsConnection。可以查看 PusherChannelsWebSocketConnection 类的实现作为示例:

final myClient = PusherChannelsClient.custom(
  connectionDelegate: () => MyConnection(),
  connectionErrorHandler: ((exception, trace, refresh) {
    refresh();
  }),
);
公共频道

公共频道应用于公开访问的数据,无需任何形式的授权即可订阅。创建公共频道的方法如下:

client.publicChannel('public-MyChannel');
私有频道

私有频道应用于需要限制访问的频道。为了订阅私有频道,用户必须被授权。授权通过在调用 subscribe 方法时向可配置的授权 URL 发送 HTTP 请求来完成:

final myPrivateChannel = client.privateChannel(
  'private-channel',
  authorizationDelegate: EndpointAuthorizableChannelTokenAuthorizationDelegate.forPrivateChannel(
    authorizationEndpoint: Uri.parse('https://test.pusher.com/pusher/auth'),
    headers: const {},
  ),
);
私有加密频道

端到端加密频道提供了与私有频道相同的订阅限制,并且在发布到这些频道的事件数据字段在离开服务器之前会使用 NaCl 中定义的 Secretbox 加密标准进行加密。只有经过授权的订阅者才能访问特定频道的解密密钥。

PrivateEncryptedChannel myEncryptedChannel = client.privateEncryptedChannel(
  'private-encrypted-channel',
  eventDataEncodeDelegate: (bytes) => utf8.decode(bytes),
  authorizationDelegate: EndpointAuthorizableChannelTokenAuthorizationDelegate
      .forPrivateEncryptedChannel(
    authorizationEndpoint: Uri.parse('https://test.pusher.com/pusher/auth'),
    headers: const {},
  ),
);
存在频道

存在频道基于私有频道的安全性,并提供了额外的功能,即了解谁订阅了该频道。这使得构建聊天室和“谁在线”类型的功能变得非常容易。同样需要一个 EndpointAuthorizableChannelAuthorizationDelegate 实例来进行授权:

final myPresenceChannel = client.presenceChannel(
  'presence-channel',
  authorizationDelegate: EndpointAuthorizableChannelTokenAuthorizationDelegate.forPresenceChannel(
    authorizationEndpoint: Uri.parse('https://test.pusher.com/pusher/auth'),
    headers: const {},
  ),
);
订阅、取消订阅和连接

以下是连接客户端并订阅频道的示例:

// 组织所有频道以提高可读性
final allChannels = <Channel>[
  myPresenceChannel,
  myPrivateChannel,
  myPublicChannel,
];

// 高度推荐在客户端的 .onConnectionEstablished Stream 发出事件时订阅频道,
// 因为这样可以在客户端因连接错误而重新连接时重新订阅
final StreamSubscription connectionSubs = client.onConnectionEstablished.listen((_) {
  for (final channel in allChannels) {
    // 如果没有故意取消订阅,则订阅频道
    channel.subscribeIfNotUnsubscribed();
  }
});

// 使用客户端连接
client.connect();

// 如果不再需要客户端 - 取消连接订阅并销毁它

// 在未来某个时间点
await Future.delayed(const Duration(seconds: 5));
connectionSubs.cancel();
client.dispose();
取消订阅频道
// 如果需要再次订阅,也可以取消订阅
myChannel.unsubscribe();
绑定到事件

与其他 SDK 不同,dart_pusher_channels 提供了通过 Dart 流绑定到事件的功能,因此建议为每个要订阅的事件创建 StreamSubscription。记住:除非取消订阅或取消 StreamSubscription,否则这些流将继续接收事件。

// 为了绑定到事件,使用 Channel 实例的 .bind 方法
StreamSubscription<ChannelReadEvent> somePrivateChannelEventSubs = myPrivateChannel.bind('private-MyEvent').listen((event) {
  print('Event from the private channel fired!');
});

// 如果想取消绑定 - 只需取消订阅
somePrivateChannelEventSubs.cancel();
使用扩展快捷方式绑定

您可以使用 Channel 实例上的扩展快捷方式方法来绑定到预定义的事件(特别是存在频道):

.whenSubscriptionSucceeded()

绑定到 pusher:subscription_succeeded

.whenSubscriptionCount()

绑定到 pusher:subscription_count

.whenMemberAdded()

绑定到 pusher:member_added

.whenMemberRemoved()

绑定到 pusher:member_removed

.onSubscriptionError({String? errorType})

绑定到 pusher:subscription_error 并通过 errorType 进行过滤

.onAuthenticationSubscriptionFailed()

绑定到 pusher:subscription_error 并通过 errorTypeAuthError 进行过滤

监听频道的所有事件
StreamSubscription<ChannelReadEvent> allEventsSubs = myChannel.bindToAll().listen((event) {
  // 做一些操作
});
监听来自客户端的所有事件
StreamSubscription<PusherChannelsReadEvent> allEventsSubs = client.eventStream.listen((event) {
  // 做一些操作
});
触发事件

私有频道和存在频道支持触发客户端事件:

myPresenceChannel.trigger(
  eventName: 'client-event',
  data: {'hello': 'Hello'},
);

示例代码

import 'dart:async';

import 'package:pusher_websockets_client/dart_pusher_channels.dart';

void connectToPusher() async {
  // 启用或禁用日志
  PusherChannelsPackageLogger.enableLogs();
  // 创建 PusherChannelsOptions 实例
  const testOptions = PusherChannelsOptions.fromCluster(
    scheme: 'wss',
    cluster: 'mt1',
    key: 'a0173cd5499b34d93109',
    port: 443,
  );
  // 创建 PusherChannelsClient 实例
  final client = PusherChannelsClient.websocket(
    options: testOptions,
    // 连接异常在这里处理
    connectionErrorHandler: (exception, trace, refresh) async {
      // 如果发生任何错误,允许重新连接
      refresh();
    },
  );

  // 创建 Channel 实例
  PresenceChannel myPresenceChannel = client.presenceChannel(
    'presence-channel',
    // 私有和存在频道需要用户授权
    // 使用 EndpointAuthorizableChannelTokenAuthorizationDelegate 通过 HTTP 端点授权
    // 或创建您自己的 EndpointAuthorizableChannelAuthorizationDelegate 实现
    authorizationDelegate: EndpointAuthorizableChannelTokenAuthorizationDelegate
        .forPresenceChannel(
      authorizationEndpoint: Uri.parse('https://test.pusher.com/pusher/auth'),
      headers: const {},
    ),
  );
  PrivateChannel myPrivateChannel = client.privateChannel(
    'private-channel',
    authorizationDelegate:
        EndpointAuthorizableChannelTokenAuthorizationDelegate.forPrivateChannel(
      authorizationEndpoint: Uri.parse('https://test.pusher.com/pusher/auth'),
      headers: const {},
    ),
  );
  PublicChannel myPublicChannel = client.publicChannel(
    'public-channel',
  );

  // 与其他 SDK 不同,dart_pusher_channels 提供了通过 Dart 流绑定到事件的功能
  // 因此建议为每个要订阅的事件创建 StreamSubscription

  // 记住:除非取消订阅或取消 StreamSubscription,否则这些流将继续接收事件
  // 这意味着:如果取消 StreamSubscription 实例 - 事件将不会被接收
  // 如果取消频道订阅 - 流不会关闭,但会被阻止接收事件,除非再次订阅频道

  // 使用 .bind 方法监听频道事件
  StreamSubscription<ChannelReadEvent> somePrivateChannelEventSubs =
      myPrivateChannel.bind('private-MyEvent').listen((event) {
    print('Event from the private channel fired!');
  });
  StreamSubscription<ChannelReadEvent> somePublicChannelEventSubs =
      myPublicChannel.bind('public-MyEvent').listen((event) {
    print('Event from the public channel fired!');
  });

  // 您可以使用一些有用的扩展快捷方式方法来绑定预定义的频道事件
  // 例如,此方法绑定了名为 'pusher:member_added' 的事件
  StreamSubscription<ChannelReadEvent> presenceMembersAddedSubs =
      myPresenceChannel.whenMemberAdded().listen((event) {
    print(
      'Member added, now members count is ${myPresenceChannel.state?.members?.membersCount}',
    );
  });

  // 将所有订阅组织到一个列表中以便阅读
  final allEventSubs = <StreamSubscription?>[
    presenceMembersAddedSubs,
    somePrivateChannelEventSubs,
    somePublicChannelEventSubs,
  ];
  // 将所有频道组织到一个列表中以便阅读
  final allChannels = <Channel>[
    myPresenceChannel,
    myPrivateChannel,
    myPublicChannel,
  ];

  // 高度推荐在客户端的 .onConnectionEstablished Stream 发出事件时订阅频道
  // 因为这样可以在客户端因连接错误而重新连接时重新订阅
  final StreamSubscription connectionSubs =
      client.onConnectionEstablished.listen((_) {
    for (final channel in allChannels) {
      // 如果没有故意取消订阅,则订阅频道
      channel.subscribeIfNotUnsubscribed();
    }
  });

  // 使用客户端连接
  unawaited(client.connect());

  // 您可以从私有和存在频道触发事件

  // 在未来的某个时间点
  await Future.delayed(
    const Duration(seconds: 5),
  );

  myPresenceChannel.trigger(
    eventName: 'client-event',
    data: {'hello': 'Hello'},
  );

  // 如果不再需要频道 - 取消订阅。频道实例是可重用的,
  // 所以如果需要,可以在以后再次订阅
  // 在未来的某个时间点
  await Future.delayed(const Duration(seconds: 5));
  myPresenceChannel.unsubscribe();
  // 在未来的某个时间点
  await Future.delayed(const Duration(seconds: 5));
  myPresenceChannel.subscribe();

  // 如果想取消事件绑定 - 只需取消事件订阅
  // 在未来的某个时间点
  await Future.delayed(const Duration(seconds: 5));
  await presenceMembersAddedSubs.cancel();

  // 如果不再需要客户端 - 取消连接订阅并销毁它

  // 在未来的某个时间点
  await Future.delayed(const Duration(seconds: 5));
  await connectionSubs.cancel();
  // 考虑取消事件订阅
  for (final subscription in allEventSubs) {
    subscription?.cancel();
  }
  client.dispose();
}

更多关于Flutter WebSocket通信插件pusher_websockets_client的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter WebSocket通信插件pusher_websockets_client的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是一个使用 pusher_websockets_client 插件在 Flutter 中实现 WebSocket 通信的示例代码。这个插件允许你与 Pusher Channels 或任何兼容的 WebSocket 服务器进行通信。

首先,确保你已经在 pubspec.yaml 文件中添加了 pusher_websockets_client 依赖:

dependencies:
  flutter:
    sdk: flutter
  pusher_websockets_client: ^x.y.z  # 请替换为最新版本号

然后,运行 flutter pub get 来安装依赖。

接下来是一个简单的 Flutter 应用示例,展示了如何使用 pusher_websockets_client 进行 WebSocket 通信:

import 'package:flutter/material.dart';
import 'package:pusher_websockets_client/pusher_websockets_client.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  PusherClient? _pusherClient;
  String _channelMessage = "";

  @override
  void initState() {
    super.initState();
    
    // 初始化 Pusher 客户端
    _pusherClient = PusherClient(
      key: 'YOUR_PUSHER_APP_KEY',  // 替换为你的 Pusher 应用密钥
      cluster: 'YOUR_PUSHER_CLUSTER',  // 替换为你的 Pusher 集群
      useTLS: true,
    );

    // 订阅频道并监听事件
    _pusherClient?.subscribe('test_channel')
      .bind('my_event', (data) {
        // 收到事件时更新状态
        setState(() {
          _channelMessage = data.message;
        });
      })
      .catchError((error) {
        print('Error subscribing to channel: $error');
      });

    // 连接 Pusher
    _pusherClient?.connect()
      .then((_) {
        print('Connected to Pusher');
      })
      .catchError((error) {
        print('Error connecting to Pusher: $error');
      });
  }

  @override
  void dispose() {
    // 断开连接并释放资源
    _pusherClient?.disconnect();
    _pusherClient = null;
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter Pusher WebSocket Demo'),
        ),
        body: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              Text(
                'Channel Message:',
                style: TextStyle(fontSize: 18),
              ),
              SizedBox(height: 8),
              Text(
                _channelMessage,
                style: TextStyle(fontSize: 16),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

在这个示例中:

  1. 初始化 Pusher 客户端:在 initState 方法中,我们创建了一个 PusherClient 实例,并传入 Pusher 应用的密钥和集群信息。
  2. 订阅频道:使用 _pusherClient?.subscribe('test_channel') 方法订阅了一个名为 test_channel 的频道,并绑定了一个名为 my_event 的事件。当该事件发生时,会更新 _channelMessage 状态。
  3. 连接 Pusher:调用 _pusherClient?.connect() 方法连接到 Pusher 服务器。
  4. UI 显示:在 build 方法中,我们构建了一个简单的 UI,用于显示从频道接收到的消息。
  5. 资源释放:在 dispose 方法中,我们断开了与 Pusher 的连接并释放了资源。

请确保将 YOUR_PUSHER_APP_KEYYOUR_PUSHER_CLUSTER 替换为你自己的 Pusher 应用密钥和集群信息。

这个示例展示了如何使用 pusher_websockets_client 插件进行基本的 WebSocket 通信。根据实际需求,你可以扩展这个示例,比如处理更多的事件、发送消息到频道等。

回到顶部