• Flutter:WebSocket封装-实现心跳、重连机制


    前言Permalink
    Flutter简介

    Flutter 是 Google推出并开源的移动应用开发框架,主打跨平台、高保真、高性能。开发者可以通过 Dart语言开发 App,一套代码同时运行在 iOS 和 Android平台。 Flutter提供了丰富的组件、接口,开发者可以很快地为 Flutter添加 native扩展。同时 Flutter还使用 Native引擎渲染视图,这无疑能为用户提供良好的体验。

    WebSocket简介

    Http协议是无状态的,只能由客户端主动发起,服务端再被动响应,服务端无法向客户端主动推送内容,并且一旦服务器响应结束,链接就会断开(见注解部分),所以无法进行实时通信。WebSocket协议正是为解决客户端与服务端实时通信而产生的技术,现在已经被主流浏览器支持,所以对于Web开发者来说应该比较熟悉了,Flutter也提供了专门的包来支持WebSocket协议。

    注意:Http协议中虽然可以通过keep-alive机制使服务器在响应结束后链接会保持一段时间,但最终还是会断开,keep-alive机制主要是用于避免在同一台服务器请求多个资源时频繁创建链接,它本质上是支持链接复用的技术,而并非用于实时通信,读者需要知道这两者的区别。

    WebSocket协议本质上是一个基于tcp的协议,它是先通过HTTP协议发起一条特殊的http请求进行握手后,如果服务端支持WebSocket协议,则会进行协议升级。WebSocket会使用http协议握手后创建的tcp链接,和http协议不同的是,WebSocket的tcp链接是个长链接(不会断开),所以服务端与客户端就可以通过此TCP连接进行实时通信。有关WebSocket协议细节,读者可以看RFC文档,下面我们重点看看Flutter中如何使用WebSocket。

    话不多说,直接撸代码Permalink
    添加依赖:

    web_socket_channel: ^1.1.0 # WebSocket
    新建web_socket_utility.dart工具类:

    import 'dart:async';
    
    import 'package:web_socket_channel/io.dart';
    import 'package:web_socket_channel/web_socket_channel.dart';
    
    /// WebSocket地址
    const String _SOCKET_URL = 'ws://121.40.165.18:8800';
    
    /// WebSocket状态
    enum SocketStatus {
      SocketStatusConnected, // 已连接
      SocketStatusFailed, // 失败
      SocketStatusClosed, // 连接关闭
    }
    
    class WebSocketUtility {
      /// 单例对象
      static WebSocketUtility _socket;
    
      /// 内部构造方法,可避免外部暴露构造函数,进行实例化
      WebSocketUtility._();
    
      /// 获取单例内部方法
      factory WebSocketUtility() {
        // 只能有一个实例
        if (_socket == null) {
          _socket = new WebSocketUtility._();
        }
        return _socket;
      }
    
      IOWebSocketChannel _webSocket; // WebSocket
      SocketStatus _socketStatus; // socket状态
      Timer _heartBeat; // 心跳定时器
      num _heartTimes = 3000; // 心跳间隔(毫秒)
      num _reconnectCount = 60; // 重连次数,默认60次
      num _reconnectTimes = 0; // 重连计数器
      Timer _reconnectTimer; // 重连定时器
      Function onError; // 连接错误回调
      Function onOpen; // 连接开启回调
      Function onMessage; // 接收消息回调
    
      /// 初始化WebSocket
      void initWebSocket({Function onOpen, Function onMessage, Function onError}) {
        this.onOpen = onOpen;
        this.onMessage = onMessage;
        this.onError = onError;
        openSocket();
      }
    
      /// 开启WebSocket连接
      void openSocket() {
        closeSocket();
        _webSocket = IOWebSocketChannel.connect(_SOCKET_URL);
        print('WebSocket连接成功: $_SOCKET_URL');
        // 连接成功,返回WebSocket实例
        _socketStatus = SocketStatus.SocketStatusConnected;
        // 连接成功,重置重连计数器
        _reconnectTimes = 0;
        if (_reconnectTimer != null) {
          _reconnectTimer.cancel();
          _reconnectTimer = null;
        }
        onOpen();
        // 接收消息
        _webSocket.stream.listen((data) => webSocketOnMessage(data),
            onError: webSocketOnError, onDone: webSocketOnDone);
      }
    
      /// WebSocket接收消息回调
      webSocketOnMessage(data) {
        onMessage(data);
      }
    
      /// WebSocket关闭连接回调
      webSocketOnDone() {
        print('closed');
        reconnect();
      }
    
      /// WebSocket连接错误回调
      webSocketOnError(e) {
        WebSocketChannelException ex = e;
        _socketStatus = SocketStatus.SocketStatusFailed;
        onError(ex.message);
        closeSocket();
      }
    
      /// 初始化心跳
      void initHeartBeat() {
        destroyHeartBeat();
        _heartBeat =
            new Timer.periodic(Duration(milliseconds: _heartTimes), (timer) {
          sentHeart();
        });
      }
    
      /// 心跳
      void sentHeart() {
        sendMessage('{"module": "HEART_CHECK", "message": "请求心跳"}');
      }
    
      /// 销毁心跳
      void destroyHeartBeat() {
        if (_heartBeat != null) {
          _heartBeat.cancel();
          _heartBeat = null;
        }
      }
    
      /// 关闭WebSocket
      void closeSocket() {
        if (_webSocket != null) {
          print('WebSocket连接关闭');
          _webSocket.sink.close();
          destroyHeartBeat();
          _socketStatus = SocketStatus.SocketStatusClosed;
        }
      }
    
      /// 发送WebSocket消息
      void sendMessage(message) {
        if (_webSocket != null) {
          switch (_socketStatus) {
            case SocketStatus.SocketStatusConnected:
              print('发送中:' + message);
              _webSocket.sink.add(message);
              break;
            case SocketStatus.SocketStatusClosed:
              print('连接已关闭');
              break;
            case SocketStatus.SocketStatusFailed:
              print('发送失败');
              break;
            default:
              break;
          }
        }
      }
    
      /// 重连机制
      void reconnect() {
        if (_reconnectTimes < _reconnectCount) {
          _reconnectTimes++;
          _reconnectTimer =
              new Timer.periodic(Duration(milliseconds: _heartTimes), (timer) {
            openSocket();
          });
        } else {
          if (_reconnectTimer != null) {
            print('重连次数超过最大次数');
            _reconnectTimer.cancel();
            _reconnectTimer = null;
          }
          return;
        }
      }
    }
    
    使用方法Permalink
    import 'package:my_app/utils/web_socket_utility.dart';
    
    WebSocketUtility().initWebSocket(onOpen: () {
      WebSocketUtility().initHeartBeat();
    }, onMessage: (data) {
      print(data);
    }, onError: (e) {
      print(e);
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169

    转自:https://ricardolsw.github.io/blog/Flutter-WebSocket%E5%B0%81%E8%A3%85-%E5%AE%9E%E7%8E%B0%E5%BF%83%E8%B7%B3-%E9%87%8D%E8%BF%9E%E6%9C%BA%E5%88%B6/

    更新dart版本后的代码:

    import 'dart:async';
    
    import 'package:web_socket_channel/io.dart';
    import 'package:web_socket_channel/web_socket_channel.dart';
    import 'package:kkview_kuaichuan/config.dart';
    /// WebSocket状态
    enum SocketStatus {
      socketStatusConnected, // 已连接
      socketStatusFailed, // 失败
      socketStatusClosed, // 连接关闭
    }
    
    class WebSocketUtility {
      /// 单例对象
      static final WebSocketUtility _socket = WebSocketUtility._internal();
    
      /// 内部构造方法,可避免外部暴露构造函数,进行实例化
      WebSocketUtility._internal();
    
      /// 获取单例内部方法
      factory WebSocketUtility() {
        return _socket;
      }
    
      late WebSocketChannel _webSocket; // WebSocket
      SocketStatus? _socketStatus; // socket状态
      Timer? _heartBeat; // 心跳定时器
      final int _heartTimes = 30000; // 心跳间隔(毫秒)
      final int _reconnectCount = 2; // 重连次数,默认60次
      int _reconnectTimes = 0; // 重连计数器
      Timer? _reconnectTimer; // 重连定时器
      late Function onError; // 连接错误回调
      late Function onOpen; // 连接开启回调
      late Function onMessage; // 接收消息回调
    
    
    
      /// 初始化WebSocket
      void initWebSocket({required Function onOpen, required Function onMessage, required Function onError}) {
        this.onOpen = onOpen;
        this.onMessage = onMessage;
        this.onError = onError;
        openSocket();
      }
    
      /// 开启WebSocket连接
      void openSocket() {
        // closeSocket();
        _webSocket = WebSocketChannel.connect(Uri.parse(SIGNALSERVERURL));
        print('WebSocket连接成功: $SIGNALSERVERURL');
        // 连接成功,返回WebSocket实例
        _socketStatus = SocketStatus.socketStatusConnected;
        // 连接成功,重置重连计数器
        _reconnectTimes = 0;
        if (_reconnectTimer != null) {
          _reconnectTimer?.cancel();
          _reconnectTimer = null;
        }
        onOpen();
        // 接收消息
        _webSocket.stream.listen((data) => webSocketOnMessage(data),
            onError: webSocketOnError, onDone: webSocketOnDone);
      }
    
      /// WebSocket接收消息回调
      webSocketOnMessage(data) {
        onMessage(data);
      }
    
      /// WebSocket关闭连接回调
      webSocketOnDone() {
        print('webSocketOnDone closed');
        _socketStatus = SocketStatus.socketStatusClosed;
        reconnect();
      }
    
      /// WebSocket连接错误回调
      webSocketOnError(e) {
        WebSocketChannelException ex = e;
        _socketStatus = SocketStatus.socketStatusFailed;
        onError(ex.message);
        closeSocket();
      }
    
      /// 初始化心跳
      void initHeartBeat() {
        destroyHeartBeat();
        _heartBeat =
        Timer.periodic(Duration(milliseconds: _heartTimes), (timer) {
          sentHeart();
        });
      }
    
      /// 心跳
      void sentHeart() {
        sendMessage('{"module": "HEART_CHECK", "message": "请求心跳"}');
      }
    
      /// 销毁心跳
      void destroyHeartBeat() {
        if (_heartBeat != null) {
          _heartBeat?.cancel();
          _heartBeat = null;
        }
      }
    
      /// 关闭WebSocket
      void closeSocket() {
        print('WebSocket连接关闭');
        _webSocket.sink.close();
        destroyHeartBeat();
        _socketStatus = SocketStatus.socketStatusClosed;
      }
    
      /// 发送WebSocket消息
      void sendMessage(message) {
        switch (_socketStatus) {
          case SocketStatus.socketStatusConnected:
            print('发送中:$message');
            _webSocket.sink.add(message);
            break;
          case SocketStatus.socketStatusClosed:
            print('连接已关闭');
            break;
          case SocketStatus.socketStatusFailed:
            print('发送失败');
            break;
          default:
            break;
        }
      }
    
      /// 重连机制
      void reconnect() {
        if (_reconnectTimes < _reconnectCount) {
          _reconnectTimes++;
          _reconnectTimer =
          Timer.periodic(Duration(milliseconds: _heartTimes), (timer) {
            openSocket();
          });
        } else {
          if (_reconnectTimer != null) {
            print('重连次数超过最大次数');
            _reconnectTimer?.cancel();
            _reconnectTimer = null;
          }
          return;
        }
      }
    
      get socketStatus => _socketStatus;
      get webSocketCloseCode => _webSocket.closeCode;
    
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
  • 相关阅读:
    Go学习第四章——程序流程控制
    INI 配置文件
    【Nacos】1.1 从单体架构到微服务
    ES6的class
    uniapp、vue实现滑动拼图验证码
    培训学校的教务管理系统存在的问题有哪些?
    IOC理解
    java毕业设计Steam游戏平台系统mybatis+源码+调试部署+系统+数据库+lw
    Spring Boot与Web开发-快速开发CRUD
    华为校招机试题库2024年(JAVA、Python、C++)
  • 原文地址:https://blog.csdn.net/shelutai/article/details/132576553