flutter——socket填坑

7 篇文章 0 订阅
3 篇文章 0 订阅

又是填坑的一天

背景

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

原代码

  • 环境
    flutter : 2.10.4
    dart : 2.16.2
    socket_io_client : 0.9.12
  • 原代码
class SocketIoUtil {
  static bool retryConnect = false;
  static var messDate;

  static Future socketIo() async {
    // print("创建isolate");
    retryConnect = true;
    onConnect();
  }

  static Future dispose() async {
    retryConnect = false;
    socket?.disconnect();
    socket = null;
    messDate = null;
    return null;
  }

  static Future onConnect() async {
	 print("socket:onConnect");
  	// 随便写的,具体连接是有逻辑判断的
  	String connectUrl="http://www.xxx.com:1414";
    socket = IO.io(
          connectUrl, IO.OptionBuilder().setTransports(['websocket']).build());
          
    socket.on(
          "message",
          (data) => {
                onMessage(data.toString()),
              });

    socket.onDisconnect((data) => {
            print("socket:连接断开"),
            _retryConnectSocketIo(),
          });
    socket.onConnect((data) => {
            print("socket:连接成功"),
          });
    socket.onConnectError((data) => {
            print("socket:连接出错"),
            _retryConnectSocketIo(),
          });
  }

  static onMessage(String string) {
    // do something
  }

  static _retryConnectSocketIo() {
    if (retryConnect) {
      print("socket:开启重新连接");
      Future.delayed(Duration(seconds: 10), () {
        onConnect();
      });
    }
  }
}

分析

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

问题解决

1.1 解决过度重试

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

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

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

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

2022-07-14 21:20:30.785 13742-13791/com.acewill.kvs_operation I/flutter: socket:onConnect
2022-07-14 21:20:30.914 13742-13791/com.acewill.kvs_operation I/flutter: socket:连接出错
2022-07-14 21:20:30.914 13742-13791/com.acewill.kvs_operation I/flutter: socket:开启重新连接
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:

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

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

// socket_io_client.dart
Socket io(uri, [opts]) => _lookup(uri, opts);

Socket _lookup(uri, opts) {
	...
	if (newConnection) {
		io = Manager(uri: uri, options: opts);
	} else {
		io = cache[id] ??= Manager(uri: uri, options: opts);
	}
	...
	// 这个方法实际是调用Manager.socket()方法
	return io.socket(parsed.path.isEmpty ? '/' : parsed.path, opts);
}

// manager.dart

Map<String, Socket> nsps;

// socket_io_client传入的nsp是socket连接的地址+端口号
Socket socket(String nsp, Map opts) {
	var socket = nsps[nsp];
}

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

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

再试一次:

2022-07-14 22:14:34.384 17786-17877/com.acewill.kvs_operation I/flutter: socket:onConnect
2022-07-14 22:14:34.489 17786-17877/com.acewill.kvs_operation I/flutter: socket:连接出错
2022-07-14 22:14:34.490 17786-17877/com.acewill.kvs_operation I/flutter: socket:开启重新连接
2022-07-14 22:14:44.493 17786-17877/com.acewill.kvs_operation I/flutter: socket:onConnect
2022-07-14 22:14:44.539 17786-17877/com.acewill.kvs_operation I/flutter: socket:连接出错
2022-07-14 22:14:44.540 17786-17877/com.acewill.kvs_operation I/flutter: socket:开启重新连接
2022-07-14 22:14:44.540 17786-17877/com.acewill.kvs_operation I/flutter: socket:连接出错
2022-07-14 22:14:44.541 17786-17877/com.acewill.kvs_operation I/flutter: socket:开启重新连接
2022-07-14 22:14:54.543 17786-17877/com.acewill.kvs_operation I/flutter: socket:onConnect
2022-07-14 22:14:54.553 17786-17877/com.acewill.kvs_operation I/flutter: socket:onConnect
2022-07-14 22:14:54.574 17786-17877/com.acewill.kvs_operation I/flutter: socket:连接出错
2022-07-14 22:14:54.575 17786-17877/com.acewill.kvs_operation I/flutter: socket:开启重新连接
2022-07-14 22:14:54.576 17786-17877/com.acewill.kvs_operation I/flutter: socket:连接出错
2022-07-14 22:14:54.577 17786-17877/com.acewill.kvs_operation I/flutter: socket:开启重新连接
2022-07-14 22:14:54.577 17786-17877/com.acewill.kvs_operation I/flutter: socket:连接出错
2022-07-14 22:14:54.578 17786-17877/com.acewill.kvs_operation I/flutter: socket:开启重新连接
2022-07-14 22:14:54.579 17786-17877/com.acewill.kvs_operation I/flutter: socket:连接出错
2022-07-14 22:14:54.579 17786-17877/com.acewill.kvs_operation I/flutter: socket:开启重新连接
...

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

1.3 再次解决过度重试

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

// darty.dart
void onConnectError(EventHandler handler) {
	on('connect_error', handler);
}

void on(String event, EventHandler handler) {
	this._events.putIfAbsent(event, () => new List<EventHandler>());
	// 罪魁祸首,这里是add,导致了重复添加
	this._events[event].add(handler);
}

重新整理代码:

class SocketIoUtil {
  static bool retryConnect = false;
  static var messDate;

  static Future socketIo() async {
    // print("创建isolate");
    retryConnect = true;
    onConnect();
  }

static IO.Socket createSocket(String url) {
    var option = IO.OptionBuilder()
        .setTransports(['websocket'])
        .disableReconnection()
        .disableAutoConnect()
        .build();
    IO.Socket socket = IO.io(url, option);
    socket.on(
        "message",
        (data) => {
              onMessage(data.toString()),
            });

    socket.onDisconnect((data) => {
          print("连接断开 "),
          EventBus().emit(Event.eventNet, '服务连接断开'),
          _retryConnectSocketIo(),
        });
    socket.onConnect((data) => {
          print("socketIo连接成功"),
          socket.emit("join_group", ["refreshwake"]), // 触发join_group 事件 将加入分组
          EventBus().emit(Event.eventNet, '网络状态良好'),
        });
    socket.onConnectError((data) => {
          print("socket:连接出错"),
          _retryConnectSocketIo(),
        });
    return socket;
  }

  static Future dispose() async {
    retryConnect = false;
    socket?.disconnect();
    socket = null;
    messDate = null;
    return null;
  }

  static Future onConnect() async {
	 print("socket:onConnect");
  	// 随便写的,具体连接是有逻辑判断的
  	String connectUrl="http://www.xxx.com:1414";
    if (socket != null) {
        if (socket.io.uri != connectUrl) {
          dispose();
          socket = createSocket(connectUrl);
        }
	} else {
        socket = createSocket(connectUrl);
	}
	socket.connect();
  }

  static onMessage(String string) {
    // do something
  }

  static _retryConnectSocketIo() {
    if (retryConnect) {
      print("socket:开启重新连接");
      Future.delayed(Duration(seconds: 10), () {
        onConnect();
      });
    }
  }
}

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

总结

1. socket默认会自动重连
2. 当地址+端口号相同时,得到的是同一个socket
3. socket的监听的实现是add而不是set

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
Flutter是一款跨平台的移动应用开发框架,它允许开发者使用一套代码同时在iOS和Android平台上构建高性能的应用程序。而Socket则是一种用于实现网络通信的编程接口,能够在不同设备之间建立连接并传输数据。 Flutter提供了一个socket库,可以在Flutter应用中实现socket通信。开发者可以使用这个库来创建Socket连接、发送和接收数据。使用Socket可以实现多种功能,比如实时通信、传输文件等。 在Flutter中,可以使用Dart语言自带的Socket库或者第三方插件来实现socket通信。Dart自带的Socket库提供了Socket类和ServerSocket类,开发者可以使用这些类来实现客户端和服务器端的socket连接。同时,也可以使用pub.dev等平台上的第三方插件,来快速构建socket通信功能。 实现socket通信的步骤一般包括以下几个方面: 1. 创建Socket连接:使用Socket类的构造函数,传入服务器的IP地址和端口号,创建一个Socket连接。 2. 发送数据:使用Socket对象的write函数,向服务器发送数据。 3. 接收数据:使用Socket对象的listen函数,监听服务器发送的数据,并进行相应处理。 4. 关闭连接:使用Socket对象的close函数,关闭Socket连接。 总之,Flutter提供了方便易用的socket库,可以使用Dart语言自带的Socket类或第三方插件来实现socket通信。通过socket通信,开发者可以实现跨平台的实时通信、文件传输等功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

得食猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值