dart-lang / web_socket_channel

StreamChannel wrappers for WebSockets.
https://pub.dev/packages/web_socket_channel
BSD 3-Clause "New" or "Revised" License
426 stars 112 forks source link

An ability to get state of connection #16

Open gradddev opened 6 years ago

nex3 commented 6 years ago

What do you mean by "the state", exactly?

gradddev commented 6 years ago

https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#Ready_state_constants

gradddev commented 6 years ago

An event is also needed for each state.

dionjwa commented 6 years ago

This is needed to handle reconnects.

With this package I have no idea how to: 1) Handle reconnects 2) Know the state of the connection.

gradddev commented 6 years ago

@dionjwa What package do you use now?

dionjwa commented 6 years ago

@AlexeySemigradsky This is my first foray into using flutter. I want to write a cross-platform app, where the core requirement is a persistent websocket connection. It doesn't look like flutter is ready to support this as yet.

gradddev commented 6 years ago

@nex3 So what about this feature? Should I wait for its implementation?

gradddev commented 6 years ago

I started working on the Socket.IO plugin for the Flutter instead of using web_socket_channel.

nex3 commented 6 years ago

Because WebSocketChannel needs to work on both the Dart VM and in the browser, the best we can do is expose the subset of APIs supported on both platforms. We can (and probably should) expose a readyState getter, but because dart:io's WebSocket class doesn't provide any events when that state changes there's no way for this package to support them either.

dionjwa commented 6 years ago

I don't understand how you're supposed to use a websocket package that is meant for mobile devices that has no way to handle disconnects (so you can reconnect). That's not something that you would ever expose in a production ready product. One disconnect because you go under a bridge, and that's it?

nex3 commented 6 years ago

The stream will emit a done event when the socket has disconnected--is that not enough?

dionjwa commented 6 years ago

@nex3 Somehow that wasn't clear. Yes, that would definitely be sufficient. Thank you!

nailgilaziev commented 6 years ago

@nex3 , when server interface is going away stream didn't emit a done event! and this is a bug.

nex3 commented 6 years ago

@nailgilaziev that sounds like a bug with dart:io's implementation. I suggest finding a reproduction that just uses dart:io's WebSocket class and filing a bug against the dart-lang/sdk repo.

nailgilaziev commented 6 years ago

I think that is a lib bug(lack of implementation), because I rewrite my code to use pure dart:io If I use this

done → Future
Return a future which is completed when the StreamSink is finished.

read-only, inherited

https://api.dartlang.org/stable/1.24.3/dart-io/WebSocket-class.html

then error event exist

nex3 commented 6 years ago

Can you produce a minimal reproduction, including server code, where dart:io emits a done event but this package does not?

nailgilaziev commented 6 years ago

My server code running on kotlin - ktor. repo But is does not matter. You can use public ehho server. ws://echo.websocket.org What matter is that you need emulate bad network condition. For this I use Network Link Conditioner with 100% loss profile.

Full code listings: 1) channel_example code 2) dart io example code

channel_example code is a tutorial example code from cookbook but with two difference: 1) added pingInterval to connect method pingInterval: Duration(seconds: 10) 2) print lines for debugging in StreamBuilder

dart io example code contains with handy reconnect functionality and other debug stuffs, but main thing is next:

ws.done.then((v) {
          setState(() {
            ex = new Exception("Connection is done with v=$v");
            timeprint("DONE");
          });
        });

This lambda called when connection is down. Not a lambda for onDone parameter for `ws.listen()' function.

What the behaviour when we see the difference between channel package and dart:io code: 1) Firstly, we connected to ws server and try to send and receive messages - both works fine. 2) Then we switch on Network Link Conditioner tool with 100% loss profile. 3) wait

And here the difference come. Because we specife in both code that we use pingInterval after around 20sec dart:io code call done lambda. But channel example don't call anything, no matter how you wait.

sgon00 commented 5 years ago

Hi, I am new to websocket and there are really not much flutter websocket articles online. Can anyone please show me how to do reconnection in flutter when the connection is lost somehow (for example, the server machine restarts). When I search the keywords flutter websocket reconnect, I find nothing related in google except this issue. Thank you very much.

angel1st commented 5 years ago

Any recent updates on this request? It is something really required in case you are going after mobile app on production - see #40 as well...

ghost commented 5 years ago

In my case, I do not receive 'onDone' message or anything for that matter if the mobile loses data connection. Will it be possible for me to ask the IOWebSocketChannel if it is still connected every 5 seconds or so? I imagine it will know if it is following WebSocket protocol because it won't be receiving frames. Thanks!

Allan-Nava commented 5 years ago

Is possible to reconnect after the websocket is die?

ignertic commented 5 years ago

@Allan-Nava; wrap connection initialization in a function and call it every time the onDone event is triggered

Allan-Nava commented 5 years ago

@Allan-Nava; wrap connection initialization in a function and call it every time the onDone event is triggered

Can you please give me an example, thanks ❤️

prijindal commented 5 years ago

@Allan-Nava I do something like this

WebSocket socket;

void _onDisconnected() {
  print('Disconnected');
  if (socket != null) {
    socket.close();
  }
  socket = null;
  _tryConnect();
}

void _tryConnect() { 
  WebSocket.connect(url).then((ws) {
    socekt = ws;
    socket.listen(
      (dynamic message) {
        print(message)
      },
      onDone: _onDisconnected,
      onError: (dynamic error) => _onDisconnected(),
    );
    socket.done.then((dynamic _) => _onDisconnected());
  });
}  
Allan-Nava commented 5 years ago

@Allan-Nava I do something like this

WebSocket socket;

void _onDisconnected() {
  print('Disconnected');
  if (socket != null) {
    socket.close();
  }
  socket = null;
  _tryConnect();
}

void _tryConnect() { 
  WebSocket.connect(url).then((ws) {
    socekt = ws;
    socket.listen(
      (dynamic message) {
        print(message)
      },
      onDone: _onDisconnected,
      onError: (dynamic error) => _onDisconnected(),
    );
    socket.done.then((dynamic _) => _onDisconnected());
  });
}  

Thanks

pyzenberg commented 5 years ago

I'm using this solution and works for me. https://gist.github.com/pyzenberg/4037e11627a8cac1c442183cc7cf172a

konsultaner commented 4 years ago

Any news on this? I implemented a WebSocket sub protocol (WAMP) with dart (+flutter) and it seems impossible to handle reconnects in a nice way. I'll end up to hack something in combination with custom Ping/Pongs and Bad state: No element errors to make this work with WebSockets:io and Sockets:io.

WebSockets:html works as expected as this is the browser implementation.

The issues I experience (so far) are:

isinghmitesh commented 4 years ago

Hi everyone, I got a workaround. And it works (Did not test is very hard). I am using flutter package: https://pub.dev/packages/connectivity

So with this package, I can listen to connection state changes like this :

StreamSubscription<ConnectivityResult> _connectivitySubscription;
_connectivitySubscription = Connectivity()
       .onConnectivityChanged.listen((ConnectivityResult result) {
      if (result == ConnectivityResult.wifi ||
          result == ConnectivityResult.mobile) {
        _tryConnect();
      }
    });

and then as above mentioned _tryConnect Function :

void _tryConnect() {
    final channel = IOWebSocketChannel.connect(
        "url");
    channel.sink.add("connected");
    channel.stream.listen(
      (message) {
        print(message);
      },
      onError: (dynamic error) => print(error),
    );
  }

In this way, if connection drops then the _connectivitySubscription will change to none and when back online which will result in wifi or mobile. it will call _tryConnect() and connect. Open to suggestions. (Peace)

isco-msyv commented 4 years ago

Can be helpful: https://stackoverflow.com/questions/55503083/flutter-websockets-autoreconnect-how-to-implement/62095514#62095514

Atomos-X commented 4 years ago

I make GUI app for aria2, same problem, when I read this: https://www.didierboelens.com/2018/06/web-sockets-build-a-real-time-game/

so my way is, Singleton handler, check connection befor send message.it works fine

import 'package:flutter/foundation.dart';
import 'package:web_socket_channel/io.dart';

WebSocketsHandler sockets = new WebSocketsHandler();

const String _SERVER_ADDRESS = "ws://127.0.0.1:6800/jsonrpc";

class WebSocketsHandler {
  static final WebSocketsHandler _sockets = new WebSocketsHandler._internal();

  factory WebSocketsHandler() => _sockets;

  WebSocketsHandler._internal();

  IOWebSocketChannel _channel;

  bool _disconnected = true;

  int _times = 0;

  ObserverList<Function> _listeners = new ObserverList<Function>();

  initCommunication() async {
    reset();
    try {
      _times += 1;
      debugPrint("try to connect $_times time(s)");
      _channel = new IOWebSocketChannel.connect(_SERVER_ADDRESS);
      _disconnected = false;
      _channel.stream.listen(
      _onReceptionOfMessage,
      onError:(error) {/* debugPrint(error); */},
      onDone:(){_disconnected = true;});
    } catch (e) {/** */}
  }

  reset() {
    if (_channel != null) {
      if (_channel.sink != null) {
        _channel.sink.close();
        _disconnected = true;
      }
    }
  }

  send(String message) {
    ///check connection before send message
    if (_disconnected) {
      initCommunication();
    }
    if (_channel != null) {
      if (_channel.sink != null && _disconnected == false) {
        _channel.sink.add(message);
      }
    }
  }

  addListener(Function callback) {
    _listeners.add(callback);
  }

  removeListener(Function callback) {
    _listeners.remove(callback);
  }

  _onReceptionOfMessage(message) {
    _disconnected = false;
    _listeners.forEach((Function callback) {
      callback(message);
    });
  }
}
konsultaner commented 4 years ago

People could use the WAMP protocol with my dart client to work with web sockets / sockets to deliver messages. I have implemented all that is needed to handle the states.

flukejones commented 4 years ago

@Allan-Nava I do something like this

WebSocket socket;

void _onDisconnected() {
  print('Disconnected');
  if (socket != null) {
    socket.close();
  }
  socket = null;
  _tryConnect();
}

void _tryConnect() { 
  WebSocket.connect(url).then((ws) {
    socekt = ws;
    socket.listen(
      (dynamic message) {
        print(message)
      },
      onDone: _onDisconnected,
      onError: (dynamic error) => _onDisconnected(),
    );
    socket.done.then((dynamic _) => _onDisconnected());
  });
}  

I've recently tried doing something similar. My situation is a little different though - I need to know when a particular machine is turned off, in this case there does not seem to be anything emitted in any particular way (neither the socket or the stream emit onDone or onError) until I turn the machine back on, then the connection is reset and errors happen.

I need to know if the TCP socket is still live without sending a command over the socket to get the machine to respond.

ingmferrer commented 4 years ago

My clients randomly disconnects and i need to find a solution for this.

jdeltoft commented 4 years ago

@Lyuzonggui I'm not sure I follow. I'm using websockets with flutter web, and it works. But I'm seeing the connection drop I think some times if I close my laptop or minimize chrome. Haven't really characterized fully. But I would love a way to check if the stream is there in the flutter web periodically and reconnect if down. I see your reset() function but I don't see any way this checks if it's down?

  reset() {
    if (_channel != null) {
      if (_channel.sink != null) {
        _channel.sink.close();
        _disconnected = true;
      }
    }
  }

Is checking for sink being null maybe a check for a connection being gone?

faganchalabizada commented 2 years ago

2022 and there is no solid way yet ?!)

konsultaner commented 2 years ago

2022 and there is no solid way yet ?!)

@faganchalabizada seems like this is of very low priority. I guess the problem here is to implement this for all plattforms

hogdamian commented 2 years ago

I am still having the same issue. catching the connect function is not working. I want to catch the error. For example "Connection failed"

monolidth commented 2 years ago

@hogdamian You can implement auto reconnect by modifying the code of @Lyuzonggui slightly because the send method call initCommunication() method, this would mostly fail if the send method is invoked frequently causing a lot of socket recreations.

import 'package:flutter/foundation.dart';
import 'package:web_socket_channel/io.dart';

WebSocketsHandler sockets = new WebSocketsHandler();

const String _SERVER_ADDRESS = "ws://127.0.0.1:6800/jsonrpc";

class WebSocketsHandler {
  static final WebSocketsHandler _sockets = new WebSocketsHandler._internal();

  factory WebSocketsHandler() => _sockets;

  WebSocketsHandler._internal();

  IOWebSocketChannel _channel;

  bool _disconnected = true;

  int _times = 0;

  ObserverList<Function> _listeners = new ObserverList<Function>();

  initCommunication() async {
    reset();
    try {
      _times += 1;
      debugPrint("try to connect $_times time(s)");
      _channel = new IOWebSocketChannel.connect(_SERVER_ADDRESS);
      _disconnected = false;
      _channel.stream.listen(
      _onReceptionOfMessage,
      onError:(error) {/* debugPrint(error); */},
     onDone: () {
        debugPrint("Receive done event.");
        _disconnected = true;
       // add delay here
        Future.delayed(const Duration(seconds: 1))
            .then((value) => initCommunication());
      });
    } catch (e) {/** */}
  }

  reset() {
    if (_channel != null) {
      if (_channel.sink != null) {
        _channel.sink.close();
        _disconnected = true;
      }
    }
  }

  List<String> failedKeys = [];
  send(String message) {
    ///check connection before send message
    if (_disconnected) {
      // add to list here 
     failedKeys.add(message);
    }
    if (_channel != null) {
      if (_channel.sink != null && _disconnected == false) {
        _channel.sink.add(message);
      }
      // add them later
    }
  }

  addListener(Function callback) {
    _listeners.add(callback);
  }

  removeListener(Function callback) {
    _listeners.remove(callback);
  }

  _onReceptionOfMessage(message) {
    _disconnected = false;
    _listeners.forEach((Function callback) {
      callback(message);
    });
  }
}

And create a timer that will fetch the missing keys:

Timer.periodic(const Duration(seconds: 5), (timer) {
      if (failedKeys.isEmpty) return;
      if (kDebugMode) print("Update failed key ${failedKeys.length}");
      for (var entry in failedKeys) {
        channel!.sink.add(entry.websocketData);
      }

Just call the initCommunication method in the main.dart method.

Regards,

William

danaodai951 commented 2 years ago

IOWebSocketChannel - innerWebSocket - readyState

/// Possible states of the connection. static const int connecting = 0; static const int open = 1; static const int closing = 2; static const int closed = 3;

Bungeefan commented 1 year ago

@nex3 Are there any plans to ever implement the readyState? It is clearly stated that several people would need this, and I couldn't identify any problem which would block this from being implemented.

Would a pull request help?

nex3 commented 1 year ago

Sorry, I'm no longer on the Dart team and no longer a maintainer of this package.

gauravmehta13 commented 6 months ago

2024 and there is no solid way yet ?!)

gokturkDev commented 3 weeks ago

I wish the close reason worked as intended... Even when the websocket is closed the close reason remains null