Dart/Flutter如何为WebSocket、HttpClient、Socket模拟弱网断网
Dart/Flutter 模拟弱网、断网场景方案说明
你提出的装饰器代理Socket方案可行性
这个思路完全可行,属于可控性很高的自定义实现方案,核心逻辑没有硬伤,但实际写的时候要注意几个容易漏的点:
- 不要只给
add方法加延迟:Socket是全双工通信通道,只加上行发送的延迟,只能模拟上传慢,下行接收数据、连接建立握手、连接关闭的流程都要加对应延迟,才和真实弱网表现一致。 - 断网场景不要靠超长延迟模拟:模拟无网络时,直接在对应逻辑里抛出
SocketException、提前关闭流触发错误回调即可,不要等延迟走完再调用真实Socket的方法。 - 不用硬抠所有接口的手动实现:
Socket本身继承了Stream、IOSink,带的方法和属性很多,手动全写很容易漏,除了你要修改的核心收发方法,剩下的方法可以直接代理到真实Socket实例,嫌麻烦甚至可以用noSuchMethod做兜底,避免出现未实现的运行时错误。 - HttpClient、WebSocket不需要单独写装饰器:这俩上层API底层都是走Socket通信,只要能把你自定义的
SlowSocket注入到客户端的创建链路里,上层的HTTP请求、WebSocket连接会自动走你模拟的弱网逻辑,不用重复写代理代码。
更省成本的实现选择
如果不想从零写Socket代理,也可以根据自己的场景选更简单的方案:
- 只需要模拟普通HTTP接口的弱网:直接用
HttpOverrides全局重写HttpClient创建逻辑就行,在自定义的HttpClient里给请求发送、响应接收加延迟、抛异常,不需要碰Socket层,几十行代码就能覆盖大部分应用层接口测试需求。 - 需要覆盖Socket、WebSocket全场景,不想改业务代码:可以在测试环境配本地代理,把所有设备/模拟器的网络请求指向本地代理服务,在代理层统一配置延迟、丢包、带宽限制、断网规则,这种方式对业务代码零侵入,还能模拟随机抖动、高丢包这些自己写装饰器很难还原的真实弱网特征。
- 只用于单元测试:直接给网络层依赖传Mock实例就行,不需要走真实网络连接,在Mock类里自定义返回延迟、错误逻辑,实现成本最低。
自定义SlowSocket核心逻辑参考
import 'dart:async'; import 'dart:io'; class SlowSocket implements Socket { final Socket _realSocket; // 模拟网络延迟,单位毫秒 final int networkDelayMs; // 模拟网络错误概率,取值0-1 final double errorRate; SlowSocket( this._realSocket, { this.networkDelayMs = 1500, this.errorRate = 0, }); // 统一的网络模拟逻辑 Future<T> _applyNetworkRule<T>(Future<T> Function() realAction) async { await Future.delayed(Duration(milliseconds: networkDelayMs)); // 按概率抛出网络异常模拟断网/丢包 if (errorRate > 0 && DateTime.now().microsecondsSinceEpoch % 1000 < errorRate * 1000) { throw const SocketException('Network unreachable'); } return realAction(); } @override void add(List<int> data) { _applyNetworkRule(() async { _realSocket.add(data); }); } @override StreamSubscription<List<int>> listen( void Function(List<int> event)? onData, { Function? onError, void Function()? onDone, bool? cancelOnError, }) { // 下行数据同样套网络规则 return _realSocket .asyncMap((dataChunk) async { await Future.delayed(Duration(milliseconds: networkDelayMs)); if (errorRate > 0 && DateTime.now().microsecondsSinceEpoch % 1000 < errorRate * 1000) { throw const SocketException('Network receive error'); } return dataChunk; }) .listen( onData, onError: onError, onDone: onDone, cancelOnError: cancelOnError, ); } // 其余Socket接口方法直接代理真实实例即可,IDE可一键生成,也可用noSuchMethod兜底 @override noSuchMethod(Invocation invocation) => Function.apply( _realSocket.noSuchMethod, [invocation], ); }
内容的提问来源于stack exchange,提问作者ch271828n
相关产品推荐
相关产品推荐

