当前位置:   article > 正文

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

flutter websocket

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工具类:

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

更新dart版本后的代码:

  1. import 'dart:async';
  2. import 'package:web_socket_channel/io.dart';
  3. import 'package:web_socket_channel/web_socket_channel.dart';
  4. import 'package:kkview_kuaichuan/config.dart';
  5. /// WebSocket状态
  6. enum SocketStatus {
  7. socketStatusConnected, // 已连接
  8. socketStatusFailed, // 失败
  9. socketStatusClosed, // 连接关闭
  10. }
  11. class WebSocketUtility {
  12. /// 单例对象
  13. static final WebSocketUtility _socket = WebSocketUtility._internal();
  14. /// 内部构造方法,可避免外部暴露构造函数,进行实例化
  15. WebSocketUtility._internal();
  16. /// 获取单例内部方法
  17. factory WebSocketUtility() {
  18. return _socket;
  19. }
  20. late WebSocketChannel _webSocket; // WebSocket
  21. SocketStatus? _socketStatus; // socket状态
  22. Timer? _heartBeat; // 心跳定时器
  23. final int _heartTimes = 30000; // 心跳间隔(毫秒)
  24. final int _reconnectCount = 2; // 重连次数,默认60次
  25. int _reconnectTimes = 0; // 重连计数器
  26. Timer? _reconnectTimer; // 重连定时器
  27. late Function onError; // 连接错误回调
  28. late Function onOpen; // 连接开启回调
  29. late Function onMessage; // 接收消息回调
  30. /// 初始化WebSocket
  31. void initWebSocket({required Function onOpen, required Function onMessage, required Function onError}) {
  32. this.onOpen = onOpen;
  33. this.onMessage = onMessage;
  34. this.onError = onError;
  35. openSocket();
  36. }
  37. /// 开启WebSocket连接
  38. void openSocket() {
  39. // closeSocket();
  40. _webSocket = WebSocketChannel.connect(Uri.parse(SIGNALSERVERURL));
  41. print('WebSocket连接成功: $SIGNALSERVERURL');
  42. // 连接成功,返回WebSocket实例
  43. _socketStatus = SocketStatus.socketStatusConnected;
  44. // 连接成功,重置重连计数器
  45. _reconnectTimes = 0;
  46. if (_reconnectTimer != null) {
  47. _reconnectTimer?.cancel();
  48. _reconnectTimer = null;
  49. }
  50. onOpen();
  51. // 接收消息
  52. _webSocket.stream.listen((data) => webSocketOnMessage(data),
  53. onError: webSocketOnError, onDone: webSocketOnDone);
  54. }
  55. /// WebSocket接收消息回调
  56. webSocketOnMessage(data) {
  57. onMessage(data);
  58. }
  59. /// WebSocket关闭连接回调
  60. webSocketOnDone() {
  61. print('webSocketOnDone closed');
  62. _socketStatus = SocketStatus.socketStatusClosed;
  63. reconnect();
  64. }
  65. /// WebSocket连接错误回调
  66. webSocketOnError(e) {
  67. WebSocketChannelException ex = e;
  68. _socketStatus = SocketStatus.socketStatusFailed;
  69. onError(ex.message);
  70. closeSocket();
  71. }
  72. /// 初始化心跳
  73. void initHeartBeat() {
  74. destroyHeartBeat();
  75. _heartBeat =
  76. Timer.periodic(Duration(milliseconds: _heartTimes), (timer) {
  77. sentHeart();
  78. });
  79. }
  80. /// 心跳
  81. void sentHeart() {
  82. sendMessage('{"module": "HEART_CHECK", "message": "请求心跳"}');
  83. }
  84. /// 销毁心跳
  85. void destroyHeartBeat() {
  86. if (_heartBeat != null) {
  87. _heartBeat?.cancel();
  88. _heartBeat = null;
  89. }
  90. }
  91. /// 关闭WebSocket
  92. void closeSocket() {
  93. print('WebSocket连接关闭');
  94. _webSocket.sink.close();
  95. destroyHeartBeat();
  96. _socketStatus = SocketStatus.socketStatusClosed;
  97. }
  98. /// 发送WebSocket消息
  99. void sendMessage(message) {
  100. switch (_socketStatus) {
  101. case SocketStatus.socketStatusConnected:
  102. print('发送中:$message');
  103. _webSocket.sink.add(message);
  104. break;
  105. case SocketStatus.socketStatusClosed:
  106. print('连接已关闭');
  107. break;
  108. case SocketStatus.socketStatusFailed:
  109. print('发送失败');
  110. break;
  111. default:
  112. break;
  113. }
  114. }
  115. /// 重连机制
  116. void reconnect() {
  117. if (_reconnectTimes < _reconnectCount) {
  118. _reconnectTimes++;
  119. _reconnectTimer =
  120. Timer.periodic(Duration(milliseconds: _heartTimes), (timer) {
  121. openSocket();
  122. });
  123. } else {
  124. if (_reconnectTimer != null) {
  125. print('重连次数超过最大次数');
  126. _reconnectTimer?.cancel();
  127. _reconnectTimer = null;
  128. }
  129. return;
  130. }
  131. }
  132. get socketStatus => _socketStatus;
  133. get webSocketCloseCode => _webSocket.closeCode;
  134. }

使用方法

  1. import 'package:my_app/utils/web_socket_utility.dart';
  2. WebSocketUtility().initWebSocket(onOpen: () {
  3. WebSocketUtility().initHeartBeat();
  4. }, onMessage: (data) {
  5. print(data);
  6. }, onError: (e) {
  7. print(e);
  8. });

Flutter上线项目实战——即时通讯Protobuf

一、应用背景:

Protobuf是google 的一种数据交换的格式,它独立于语言,独立于平台。

优点:

  • json优点就是较XML格式更加小巧,传输效率较xml提高了很多,可读性还不错。
  • xml优点就是可读性强,解析方便。
  • protobuf优点就是传输效率快(据说在数据量大的时候,传输效率比xml和json快10-20倍),序列化后体积相比Json和XML很小,支持跨平台多语言,消息格式升级和兼容性还不错,序列化反序列化速度很快。

缺点:

  • json缺点就是传输效率也不是特别高(比xml快,但比protobuf要慢很多)。
  • xml缺点就是效率不高,资源消耗过大。
  • protobuf缺点就是使用不太方便。


在一个需要大量的数据传输的场景中,如果数据量很大,那么选择protobuf可以明显的减少数据量,减少网络IO,从而减少网络传输所消耗的时间。考虑到作为一个主打社交的产品,消息数据量会非常大,同时为了节约流量,所以采用protobuf是一个不错的选择。

二、使用

1.引入protobuf库
pubspec.yaml

  1. ...
  2. protobuf: 1.0.1


2.编写proto文件
socket.message.proto

  1. syntax = "proto3";
  2. package socket;
  3. // 发送聊天信息
  4. message Message {
  5.   string eventId = 1;
  6.   string from = 2;
  7.   string to = 3;
  8.   string createAt = 4;
  9.   string type = 5;
  10.   string body = 6;
  11. }
  12. // 收到聊天消息
  13. message AckMessage {
  14.   string eventId = 1;
  15. }


3.生成proto相关Model
Terminal

protoc --dart_out=. socket.message.proto


4.编码、发消息
a.准备protobuf对象

  1. Message message = Message();
  2. message.eventId = '####';
  3. message.type = 'text';
  4. message.body = 'hello world';

b.ProtobufUtil编码

  1. const MESSAGE_HEADER_LEN = 2;
  2. /// 数据编码
  3. static List<int> encode(int type, var content) {
  4.     ByteData data = ByteData(MESSAGE_HEADER_LEN);
  5.     data.setUint16(0, type, Endian.little);
  6.     List<int> msg = data.buffer.asUint8List() + content.writeToBuffer().buffer.asUint8List();
  7.     return msg;
  8. }


c.发消息

  1. /// 发送
  2. sendSocket(int type, var content) async {
  3.     IOWebSocketChannel channel = await SocketService.getInstance().getChannel();
  4.     if (channel == null) return;
  5.     List<int> msg = ProtobufUtil.encode(type, content);
  6.     channel.sink.add(msg);
  7. }
  8. sendSocket(11, message)


5.收消息、解码
a.解码
  

  1.   /// 数据解码
  2.   static DecodedMsg decode(data) {
  3.     Int8List int8Data = Int8List.fromList(data);
  4.     Int8List contentTypeInt8Data = int8Data.sublist(0, MESSAGE_HEADER_LEN);
  5.     Int8List contentInt8Data = int8Data.sublist(MESSAGE_HEADER_LEN, int8Data.length);
  6.     int contentType = contentTypeInt8Data.elementAt(0);
  7.     GeneratedMessage content;
  8.     switch (contentType) {
  9.       case 10:
  10.         content = AckMessage.fromBuffer(contentInt8Data);
  11.         break;
  12.       case 11:
  13.         content = Message.fromBuffer(contentInt8Data);
  14.         break;
  15.     }
  16.     DecodedMsg decodedMsg;
  17.     if (contentType != null && content != null) {
  18.       decodedMsg = DecodedMsg(
  19.         contentType: contentType,
  20.         content: content,
  21.       );
  22.     }
  23.     return decodedMsg;
  24.   }


b.收消息

  1.   channel.stream.listen((data) {
  2.     DecodedMsg msg = ProtobufUtil.decode(data);
  3.   }

flutter——socket填坑

背景

突然有用户反馈,页面卡死,无法操作。这便是全部信息,让排查问题。排查过程是很困难的,直接说结论:前同事socket使用错误,导致内存占用过大,任何事件都得不到响应。

代码

环境

flutter : 2.10.4
dart : 2.16.2
socket_io_client : 0.9.12

原代码

  1. class SocketIoUtil {
  2. static bool retryConnect = false;
  3. static var messDate;
  4. static Future socketIo() async {
  5. // print("创建isolate");
  6. retryConnect = true;
  7. onConnect();
  8. }
  9. static Future dispose() async {
  10. retryConnect = false;
  11. socket?.disconnect();
  12. socket = null;
  13. messDate = null;
  14. return null;
  15. }
  16. static Future onConnect() async {
  17. print("socket:onConnect");
  18. // 随便写的,具体连接是有逻辑判断的
  19. String connectUrl="http://www.xxx.com:1414";
  20. socket = IO.io(
  21. connectUrl, IO.OptionBuilder().setTransports(['websocket']).build());
  22. socket.on(
  23. "message",
  24. (data) => {
  25. onMessage(data.toString()),
  26. });
  27. socket.onDisconnect((data) => {
  28. print("socket:连接断开"),
  29. _retryConnectSocketIo(),
  30. });
  31. socket.onConnect((data) => {
  32. print("socket:连接成功"),
  33. });
  34. socket.onConnectError((data) => {
  35. print("socket:连接出错"),
  36. _retryConnectSocketIo(),
  37. });
  38. }
  39. static onMessage(String string) {
  40. // do something
  41. }
  42. static _retryConnectSocketIo() {
  43. if (retryConnect) {
  44. print("socket:开启重新连接");
  45. Future.delayed(Duration(seconds: 10), () {
  46. onConnect();
  47. });
  48. }
  49. }
  50. }

分析

大概逻辑就是开启一个socket,连接成功则对接收到的消息进行业务处理,否则10s后重试连接。
看似没啥问题,但实测后打印日志如下:


问题解决

1.1 解决过度重试

从原代码可以看出在连接失败后会调用_retryConnectSocketIo方法,而该方法会在延迟10s后调用 onConnect 方法,但日志中显示在这延迟的10s中又多调用了3次 连接出错 ,这样在下一个10s后就会总共调用 4个onConnect 方法,而每个onConnect又会调用4次 连接出错,那么再过10s就会有4*4个 onConnect被调用。这样每个10s就会有4倍的socket连接,最终导致内存占用过大,项目卡死。

然而这些多余的连接出错不是项目触发的,因此怀疑创建的socket自身具有失败重试的功能。因此对代码进行如下修改:

  1.     ...
  2.  static Future onConnect() async {
  3.      print("socket:onConnect");
  4.       // 随便写的,具体连接是有逻辑判断的
  5.       String connectUrl="http://www.xxx.com:1414";
  6.     socket = IO.io(
  7.           connectUrl,
  8.           IO.OptionBuilder().setTransports(['websocket']).disableReconnection().build());
  9.     ...

本以为问题得到解决,结果神奇的一幕发生了,看日志

  1. 2022-07-14 21:20:30.785 13742-13791/com.acewill.kvs_operation I/flutter: socket:onConnect
  2. 2022-07-14 21:20:30.914 13742-13791/com.acewill.kvs_operation I/flutter: socket:连接出错
  3. 2022-07-14 21:20:30.914 13742-13791/com.acewill.kvs_operation I/flutter: socket:开启重新连接
  4. 2022-07-14 21:20:40.924 13742-13791/com.acewill.kvs_operation I/flutter: socket:onConnect

后面就没日志了,onConnect 后面没有再打印 连接出错,也就是说再次运行至onConnect中创建的socket没有自动连接。

1.2 解决不自动重连

socket_io_client中的socket是自动连接的,而上面修改后的代码第二次进入就不再连接,抱着试一试的想法打印了下socket的hashcode:

  1. 2022-07-14 21:42:36.112 16057-16129/com.acewill.kvs_operation I/flutter: socket:onConnect
  2. 2022-07-14 21:42:36.192 16057-16129/com.acewill.kvs_operation I/flutter: socket:hashcode_726189657
  3. 2022-07-14 21:42:36.242 16057-16129/com.acewill.kvs_operation I/flutter: socket:连接出错
  4. 2022-07-14 21:42:36.243 16057-16129/com.acewill.kvs_operation I/flutter: socket:开启重新连接
  5. 2022-07-14 21:42:46.246 16057-16129/com.acewill.kvs_operation I/flutter: socket:onConnect
  6. 2022-07-14 21:42:46.247 16057-16129/com.acewill.kvs_operation I/flutter: socket:hashcode_726189657
  7. ...

竟然完全一致,说明虽然socket是在onConnect中创建的但依旧是原来的对象。那么这样就解释的通了:
第一次onConnect创建socket会调用自动连接,当再次进入onConnect后由于之前已经执行过了自动连接,因此这次什么都不做。
为什么socket会是同一个呢,明明是在onConnect中重新创建的?看下socket的创建代码:

  1. // socket_io_client.dart
  2. Socket io(uri, [opts]) => _lookup(uri, opts);
  3. Socket _lookup(uri, opts) {
  4. ...
  5. if (newConnection) {
  6. io = Manager(uri: uri, options: opts);
  7. } else {
  8. io = cache[id] ??= Manager(uri: uri, options: opts);
  9. }
  10. ...
  11. // 这个方法实际是调用Manager.socket()方法
  12. return io.socket(parsed.path.isEmpty ? '/' : parsed.path, opts);
  13. }
  14. // manager.dart
  15. Map<String, Socket> nsps;
  16. // socket_io_client传入的nsp是socket连接的地址+端口号
  17. Socket socket(String nsp, Map opts) {
  18. var socket = nsps[nsp];
  19. }

从上面代码可以看出当地址+端口号不变时,通过IO.io得到的是同一个socket。
原因找到了,解决方案就简单了,只需要将自动连接改为手动触发就好了,代码如下:

  1. ...
  2. static Future onConnect() async {
  3. print("socket:onConnect");
  4. // 随便写的,具体连接是有逻辑判断的
  5. String connectUrl="http://www.xxx.com:1414";
  6. socket = IO.io(connectUrl,
  7. IO.OptionBuilder().setTransports(['websocket'])
  8. .disableReconnection().disableAutoConnect().build());
  9. ...
  10. socket.connect();
  11. ...


再试一次:

  1. 2022-07-14 22:14:34.384 17786-17877/com.acewill.kvs_operation I/flutter: socket:onConnect
  2. 2022-07-14 22:14:34.489 17786-17877/com.acewill.kvs_operation I/flutter: socket:连接出错
  3. 2022-07-14 22:14:34.490 17786-17877/com.acewill.kvs_operation I/flutter: socket:开启重新连接
  4. 2022-07-14 22:14:44.493 17786-17877/com.acewill.kvs_operation I/flutter: socket:onConnect
  5. 2022-07-14 22:14:44.539 17786-17877/com.acewill.kvs_operation I/flutter: socket:连接出错
  6. 2022-07-14 22:14:44.540 17786-17877/com.acewill.kvs_operation I/flutter: socket:开启重新连接
  7. 2022-07-14 22:14:44.540 17786-17877/com.acewill.kvs_operation I/flutter: socket:连接出错
  8. 2022-07-14 22:14:44.541 17786-17877/com.acewill.kvs_operation I/flutter: socket:开启重新连接
  9. 2022-07-14 22:14:54.543 17786-17877/com.acewill.kvs_operation I/flutter: socket:onConnect
  10. 2022-07-14 22:14:54.553 17786-17877/com.acewill.kvs_operation I/flutter: socket:onConnect
  11. 2022-07-14 22:14:54.574 17786-17877/com.acewill.kvs_operation I/flutter: socket:连接出错
  12. 2022-07-14 22:14:54.575 17786-17877/com.acewill.kvs_operation I/flutter: socket:开启重新连接
  13. 2022-07-14 22:14:54.576 17786-17877/com.acewill.kvs_operation I/flutter: socket:连接出错
  14. 2022-07-14 22:14:54.577 17786-17877/com.acewill.kvs_operation I/flutter: socket:开启重新连接
  15. 2022-07-14 22:14:54.577 17786-17877/com.acewill.kvs_operation I/flutter: socket:连接出错
  16. 2022-07-14 22:14:54.578 17786-17877/com.acewill.kvs_operation I/flutter: socket:开启重新连接
  17. 2022-07-14 22:14:54.579 17786-17877/com.acewill.kvs_operation I/flutter: socket:连接出错
  18. 2022-07-14 22:14:54.579 17786-17877/com.acewill.kvs_operation I/flutter: socket:开启重新连接
  19. ...

居然还不行!!!
连接出错 几个字的打印频率是1、2、4…呈2的指数增长,这又该怎么解决呢?

1.3 再次解决过度重试

虽然上面依旧存在过度重试,但整体的重试时间点比较集中,似乎是有些代码在onConnect中重复执行了,逐行排查也只有socket.onConnectError这个代码重复执行了,看下内部实现:

  1. // darty.dart
  2. void onConnectError(EventHandler handler) {
  3.     on('connect_error', handler);
  4. }
  5. void on(String event, EventHandler handler) {
  6.     this._events.putIfAbsent(event, () => new List<EventHandler>());
  7.     // 罪魁祸首,这里是add,导致了重复添加
  8.     this._events[event].add(handler);
  9. }

重新整理代码:

  1. class SocketIoUtil {
  2. static bool retryConnect = false;
  3. static var messDate;
  4. static Future socketIo() async {
  5. // print("创建isolate");
  6. retryConnect = true;
  7. onConnect();
  8. }
  9. static IO.Socket createSocket(String url) {
  10. var option = IO.OptionBuilder()
  11. .setTransports(['websocket'])
  12. .disableReconnection()
  13. .disableAutoConnect()
  14. .build();
  15. IO.Socket socket = IO.io(url, option);
  16. socket.on(
  17. "message",
  18. (data) => {
  19. onMessage(data.toString()),
  20. });
  21. socket.onDisconnect((data) => {
  22. print("连接断开 "),
  23. EventBus().emit(Event.eventNet, '服务连接断开'),
  24. _retryConnectSocketIo(),
  25. });
  26. socket.onConnect((data) => {
  27. print("socketIo连接成功"),
  28. socket.emit("join_group", ["refreshwake"]), // 触发join_group 事件 将加入分组
  29. EventBus().emit(Event.eventNet, '网络状态良好'),
  30. });
  31. socket.onConnectError((data) => {
  32. print("socket:连接出错"),
  33. _retryConnectSocketIo(),
  34. });
  35. return socket;
  36. }
  37. static Future dispose() async {
  38. retryConnect = false;
  39. socket?.disconnect();
  40. socket = null;
  41. messDate = null;
  42. return null;
  43. }
  44. static Future onConnect() async {
  45. print("socket:onConnect");
  46. // 随便写的,具体连接是有逻辑判断的
  47. String connectUrl="http://www.xxx.com:1414";
  48. if (socket != null) {
  49. if (socket.io.uri != connectUrl) {
  50. dispose();
  51. socket = createSocket(connectUrl);
  52. }
  53. } else {
  54. socket = createSocket(connectUrl);
  55. }
  56. socket.connect();
  57. }
  58. static onMessage(String string) {
  59. // do something
  60. }
  61. static _retryConnectSocketIo() {
  62. if (retryConnect) {
  63. print("socket:开启重新连接");
  64. Future.delayed(Duration(seconds: 10), () {
  65. onConnect();
  66. });
  67. }
  68. }
  69. }


上面代码运行正常,至此终于把这个坑填完。

总结

  1. socket默认会自动重连
  2. 当地址+端口号相同时,得到的是同一个socket
  3. socket的监听的实现是add而不是set
     
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/268653
推荐阅读
相关标签
  

闽ICP备14008679号