gRPC в Flutter вылетает, когда нет интернета - PullRequest
0 голосов
/ 09 января 2019

Я разрабатываю приложение Flutter с использованием gRPC, и все работало правильно, пока я не решил посмотреть, что произойдет, если нет подключения к Интернету.

После этого и запроса я получаю следующую ошибку:

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 голосов
/ 09 января 2019

Я не использовал gRPC до сегодняшнего дня.

Так как я потратил время, чтобы попытаться смоделировать эту ошибку, я опубликую здесь свой ответ, но все мои сведения были получены под @ ishann ответом, за который я проголосовал и который должен быть принят один.

Я только что попробовал пример с миром дротиков .

У меня server работает на моей машине и client как приложение Flutter.

Когда я не запускаю сервер, я получаю сообщение об ошибке

gRPC Error (14, Error connecting: SocketException:

enter image description here

Но как только сервер работает, все начинают работать, как и ожидалось, но потом я понял, что каждый раз воссоздаю канал, так что это не сценарий OP.

Это мой первый код флаттера:

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 и затем переключаюсь обратно в нормальное соединение Wi-Fi / 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)

(например, выключить сервер), с которого вы можете снова встать без воссоздания канала.

Прежний код ошибки не так легко получить, потому что кажется, что канал дросселя между Wi-Fi и 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!
0 голосов
/ 17 января 2019

По предложению @ 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;
}
0 голосов
/ 09 января 2019

Полагаю, вы один из немногих, кто пытается это сделать.

GRPC-соединениям требуется немного времени для создания нового соединения, не только в dart, но и во всех других языках. Если вы хотите, вы можете установить обработчик перехвата на код ошибки 14, вручную разорвать соединение и заново подключиться. Также есть опция канала idleTimeout, которая может вам помочь, по умолчанию 5 минут в grpc-dart

.

Исправлено непредвиденное возникновение сбоя https://github.com/grpc/grpc-dart/issues/131, поэтому попробуйте обновить ваши зависимости (grpc-dart), что предотвратит сбой, но проблема переподключения в сети может остаться.

После этого исправления сбои прекратились, но проблема устаревшего соединения остается и для меня. Я прибег к отображению снэк-баров с предложениями типа «Не удается подключиться к серверам, повторите попытку через несколько минут».

...