颤抖的websockets自动重新连接-如何实施

时间:2019-04-03 19:40:11

标签: websocket flutter dart-io

我正在努力实现扑朔迷离的QAction *a = new QAction(IconsManager::icon(fa::refresh), "Refresh", ui->list); a->setShortcut(QKeySequence(Qt::Key_F5)); a->setShortcutVisibleInContextMenu(true); connect(a, &QAction::triggered, this, &UserPlaylistsSubWidget::refreshList); ui->list->addAction(a); 自动重新连接。我使用web_socket_channel,但是,该插件只包装了dart.io WebSocket,因此任何基于websockets类的解决方案对我也都适用。

我已经弄清楚了如何捕获套接字断开连接,请参见下面的代码片段:

WebSocket

我本来想从 try { _channel = IOWebSocketChannel.connect( wsUrl, ); /// /// Start listening to new notifications / messages /// _channel.stream.listen( _onMessageFromServer, onDone: () { debugPrint('ws channel closed'); }, onError: (error) { debugPrint('ws error $error'); }, ); } catch (e) { /// /// General error handling /// TODO handle connection failure /// debugPrint('Connection exception $e'); } 内部调用IOWebSocketChannel.connect,但这会导致无限循环-因为我必须在调用onDone之前先关闭_channel再次,这又再次调用connect,依此类推。

任何帮助将不胜感激!

3 个答案:

答案 0 :(得分:3)

对于package:web_socket_channel(IOWebSocketChannel),没有任何方法可以实现套接字连接的重新连接。但是您可以使用WebSocket类来实现可重新连接。

您可以实现WebSocket通道,然后使用StreamController类广播消息。工作示例:

import 'dart:async';
import 'dart:io';

class NotificationController {

  static final NotificationController _singleton = new NotificationController._internal();

  StreamController<String> streamController = new StreamController.broadcast(sync: true);

  String wsUrl = 'ws://YOUR_WEBSERVICE_URL';

  WebSocket channel;

  factory NotificationController() {
    return _singleton;
  }

  NotificationController._internal() {
    initWebSocketConnection();
  }

  initWebSocketConnection() async {
    print("conecting...");
    this.channel = await connectWs();
    print("socket connection initializied");
    this.channel.done.then((dynamic _) => _onDisconnected());
    broadcastNotifications();
  }

  broadcastNotifications() {
    this.channel.listen((streamData) {
      streamController.add(streamData);
    }, onDone: () {
      print("conecting aborted");
      initWebSocketConnection();
    }, onError: (e) {
      print('Server error: $e');
      initWebSocketConnection();
    });
  }

  connectWs() async{
    try {
      return await WebSocket.connect(wsUrl);
    } catch  (e) {
      print("Error! can not connect WS connectWs " + e.toString());
      await Future.delayed(Duration(milliseconds: 10000));
      return await connectWs();
    }

  }

  void _onDisconnected() {
    initWebSocketConnection();
  }
}

由于通知控制器返回一个单例实例,因此服务器与设备之间始终存在一个Socket连接。借助StreamController的广播方法,我们可以在多个使用者之间共享Websocket发送的数据

var _streamController = new NotificationController().streamController;

_streamController.stream.listen(pushNotifications);

答案 1 :(得分:1)

这就是我要做的:

void reconnect() {
    setState(() {
      _channel = IOWebSocketChannel.connect(wsUrl);
    });
    _channel.stream.listen((data) => processMessage(data), onDone: reconnect);
  }

然后启动您的websocket,只需对reconnect()进行初始调用即可。基本上,这样做是在调用onDone回调时重新创建WebSocket,这在连接被破坏时发生。因此,连接已销毁-好的,让我们自动重新连接。我还没有找到一种方法来重新创建_channel。理想情况下,希望有一个_channel.connect()可以重新连接到现有的URL,或某种自动重新连接功能,但是似乎不存在。

哦,这是更好的一点,如果远程服务器关闭,它将摆脱丑陋的重新连接异常回溯,并增加4秒的重新连接延迟。在这种情况下,cancelOnError参数会在发生任何错误时触发套接字关闭。

  wserror(err) async {
    print(new DateTime.now().toString() + " Connection error: $err");
    await reconnect();
  }

 reconnect() async {
    if (_channel != null) {
      // add in a reconnect delay
      await Future.delayed(Duration(seconds: 4));
    }
    setState(() {
      print(new DateTime.now().toString() + " Starting connection attempt...");
      _channel = IOWebSocketChannel.connect(wsUrl);
      print(new DateTime.now().toString() + " Connection attempt completed.");
    });
    _channel.stream.listen((data) => processMessage(data), onDone: reconnect, onError: wserror, cancelOnError: true);
  }

答案 2 :(得分:0)

我不直接使用connect()方法返回的通道,而是使用委托的StreamController来转发ws通道流中的数据:

StreamController<dynamic> delegatedCtrl = StreamController<String>();

Stream<int>void  IOWebSocketChannel.connect(endpoint).stream.listen((event) {
    ctrl.add(event);
  }, onError: (e) async {
    await Future.delayed(Duration(seconds: 3));
    connect(ctrl, endpoint);
  }, onDone: () async {
    await Future.delayed(Duration(seconds: 3));
    connect(ctrl, endpoint);
  }, cancelOnError: true);
}

然后,您可以在StreamBuilder中的任何地方使用它:

StreamBuilder(
  stream: delegatedCtrl.stream,
  builder: (context, snapshot) {
    return Text(snapshot.hasData ? '${snapshot.data}' : '');
  },
)