没有互联网时,Flutter中的gRPC崩溃

时间:2019-01-09 07:57:40

标签: networking dart flutter grpc

我正在使用gRPC开发Flutter应用程序,并且一切正常,直到我决定看看如果没有Internet连接会发生什么情况。

这样做并发出请求后,出现以下错误:

  

E / flutter(26480):gRPC错误(14,调用时出错:状态错误:http / 2连接不再有效,因此不能用于创建新的流。)

问题在于,即使重新启用连接后,仍然会发生错误。
我必须重新创建clientChannel吗?

const String serverUrl = 'theaddress.com';
const int serverPort = 50051;

final ClientChannel defaultClientChannel = ClientChannel(
  serverUrl,
  port: serverPort,
  options: const ChannelOptions(
    credentials: const ChannelCredentials.insecure(),
  ),
);

我只是想抛出一些错误,但是一旦互联网连接恢复正常,就可以正常工作。

3 个答案:

答案 0 :(得分:2)

我想你是少数尝试它的人之一。

GRPC连接需要花费一些时间来创建新的连接,不仅是dart,而且是所有其他语言。如果需要,可以将捕获侦听器放在错误代码14上,然后手动终止连接并重新连接。还有idleTimeout个频道选项可能会对您有所帮助,默认选项是在grpc-dart中为5分钟

有一个针对未解决的崩溃问题https://github.com/grpc/grpc-dart/issues/131的修复程序,因此请尝试更新依赖项(grpc-dart)以防止崩溃,但是网络上的重新连接问题仍然存在。

此修复程序之后,崩溃已经停止,但是对于我来说,过时的连接问题仍然存在。我求助于显示小吃店,上面写着“无法连接服务器,请过几分钟再试”。

答案 1 :(得分:2)

基于@Ishaan的建议,我使用了Connectivity程序包创建了一个客户端,该客户端在互联网备份后会重新连接。到目前为止,它似乎一直有效。

import 'dart:async';

import 'package:connectivity/connectivity.dart';
import 'package:flutter_worker_app/generated/api.pbgrpc.dart';
import 'package:grpc/grpc.dart';
import 'package:rxdart/rxdart.dart';

class ConnectiveClient extends ApiClient {

  final CallOptions _options;
  final Connectivity _connectivity;
  ClientChannel _channel;
  bool hasRecentlyFailed = false;


  ConnectiveClient(this._connectivity, this._channel, {CallOptions options})
      : _options = options ?? CallOptions(),
        super(_channel) {
    //TODO: Cancel connectivity subscription
    _connectivity.onConnectivityChanged.listen((result) {
      if (hasRecentlyFailed && result != ConnectivityResult.none) {
        _restoreChannel();
      }
    });
  }

  ///Create new channel from original channel
  _restoreChannel() {
    _channel = ClientChannel(_channel.host,
        port: _channel.port, options: _channel.options);
    hasRecentlyFailed = false;
  }

  @override
  ClientCall<Q, R> $createCall<Q, R>(
      ClientMethod<Q, R> method, Stream<Q> requests,
      {CallOptions options}) {
    //create call
    BroadcastCall<Q, R> call = createChannelCall(
      method,
      requests,
      _options.mergedWith(options),
    );
    //listen if there was an error
    call.response.listen((_) {}, onError: (Object error) async {
      //Cannot connect - we assume it's internet problem
      if (error is GrpcError && error.code == StatusCode.unavailable) {
        //check connection
        _connectivity.checkConnectivity().then((result) {
          if (result != ConnectivityResult.none) {
            _restoreChannel();
          }
        });
        hasRecentlyFailed = true;
      }
    });
    //return original call
    return call;
  }

  /// Initiates a new RPC on this connection.
  /// This is copy of [ClientChannel.createCall]
  /// The only difference is that it creates [BroadcastCall] instead of [ClientCall]
  ClientCall<Q, R> createChannelCall<Q, R>(
      ClientMethod<Q, R> method, Stream<Q> requests, CallOptions options) {
    final call = new BroadcastCall(method, requests, options);
    _channel.getConnection().then((connection) {
      if (call.isCancelled) return;
      connection.dispatchCall(call);
    }, onError: call.onConnectionError);
    return call;
  }
}

///A ClientCall that can be listened multiple times
class BroadcastCall<Q, R> extends ClientCall<Q, R> {
  ///I wanted to use super.response.asBroadcastStream(), but it didn't work.
  ///I don't know why...
  BehaviorSubject<R> subject = BehaviorSubject<R>();

  BroadcastCall(
      ClientMethod<Q, R> method, Stream<Q> requests, CallOptions options)
      : super(method, requests, options) {
    super.response.listen(
          (data) => subject.add(data),
          onError: (error) => subject.addError(error),
          onDone: () => subject.close(),
        );
  }

  @override
  Stream<R> get response => subject.stream;
}

答案 2 :(得分:1)

直到今天我一直没有使用gRPC。

由于我花了时间尝试模拟此错误,因此将答案发布在这里,但是我所有的intel都是我赞成的 @ishann 答案,这应该是接受了。

我刚刚尝试过dart hello world example

我的计算机上正在运行server,而Flutter应用程序中是client

当我不运行服务器时,出现错误

gRPC Error (14, Error connecting: SocketException:

enter image description here

但是一旦服务器启动,所有设备都将按预期方式工作,但是后来我意识到我每次都在重新创建通道,所以这不是OP方案。

那是我的拳头Flutter代码:

void _foo() async {
  final channel = new ClientChannel('192.168.xxx.xxx',
      port: 50051,
      options: const ChannelOptions(
          credentials: const ChannelCredentials.insecure()));
  final stub = new GreeterClient(channel);

  final name = 'world';

  var _waitHelloMessage = true;
  while (_waitHelloMessage) {
    try {
      final response = await stub.sayHello(new HelloRequest()..name = name);
      print('Greeter client received: ${response.message}');
      _waitHelloMessage = false;
    } catch (e) {
      print('Caught error: $e');
      sleep(Duration(seconds: 1));
    }
  }
  print('exiting');
  await channel.shutdown();
}

如果将设备置于 airplain模式,然后又切换回正常的wifi / lte连接,则行为相同。

在另一个游乐场项目中,我还是转载了

Caught error: gRPC Error (14, Error making call: Bad state: The http/2 connection is no longer active and can therefore not be used to make new streams.)

在不重新创建频道的情况下您将无法出现,并且

Caught error: gRPC Error (14, Error connecting: SocketException: OS Error: Connection refused, errno = 111, address = 192.168.1.58, port = 38120)

(例如关闭服务器),您可以从中重新启动而无需重新创建频道。

以前的错误代码不太容易获得,因为似乎wifi和LTE连接之间的通道限制。

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_app_test_grpc/grpc/generated/helloworld.pbgrpc.dart';
import 'package:grpc/grpc.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  ClientChannel _channel;

  @override
  void dispose() {
    _shutdown();
    super.dispose();
  }

  void _shutdown() async {
    if (null != _channel) {
      print('shutting down...');
      await _channel.shutdown();
      print('shut down');
      _channel = null;
    } else {
      print ('connect first');
    }
  }

  void _connect() {
    print('connecting...');
    _channel = new ClientChannel('192.168.xxx.xxx',
        port: 50051,
        options: const ChannelOptions(
            credentials: const ChannelCredentials.insecure()));
    print('connected');
  }

  void _sayHello() async {
    if (_channel != null) {
      final stub = new GreeterClient(_channel);

      final name = 'world';

      try {
        final response = await stub.sayHello(new HelloRequest()..name = name);
        print('Greeter client received: ${response.message}');
      } catch (e) {
        print('Caught error: $e');
        //sleep(Duration(seconds: 2));
      }

      //print('exiting');
      //await channel.shutdown();
    } else {
      print('connect first!');
    }
  }

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: Padding(
        padding: const EdgeInsets.only(left: 36.0),
        child: Row(
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: FloatingActionButton(
                onPressed: _connect,
                tooltip: 'Increment',
                child: Icon(Icons.wifi),
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: FloatingActionButton(
                onPressed: _sayHello,
                tooltip: 'Increment',
                child: Icon(Icons.send),
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: FloatingActionButton(
                onPressed: _shutdown,
                tooltip: 'Increment',
                child: Icon(Icons.close),
              ),
            ),
          ],
        ),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

enter image description here

如果可以的话,那就是我的flutter doctor -v

$ flutter doctor -v
[✓] Flutter (Channel beta, v1.0.0, on Mac OS X 10.14.1 18B75, locale en-IT)
    • Flutter version 1.0.0 at /Users/shadowsheep/flutter/flutter
    • Framework revision 5391447fae (6 weeks ago), 2018-11-29 19:41:26 -0800
    • Engine revision 7375a0f414
    • Dart version 2.1.0 (build 2.1.0-dev.9.4 f9ebf21297)

[✓] Android toolchain - develop for Android devices (Android SDK 28.0.3)
    • Android SDK at /Users/shadowsheep/Library/Android/sdk
    • Android NDK location not configured (optional; useful for native profiling support)
    • Platform android-28, build-tools 28.0.3
    • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1248-b01)
    • All Android licenses accepted.

[✓] iOS toolchain - develop for iOS devices (Xcode 10.1)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Xcode 10.1, Build version 10B61
    • ios-deploy 1.9.4
    • CocoaPods version 1.5.3

[✓] Android Studio (version 3.3)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin version 31.3.3
    • Dart plugin version 182.5124
    • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1248-b01)

[✓] VS Code (version 1.30.1)
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 2.21.1

[✓] Connected device (1 available)
    [...]

• No issues found!