Flutter 如何在for循环中同步接收Socket返回的响应数据
开发场景要求在for循环中向网络Socket按顺序写入多条数据,每写入一条数据后需要立即拿到对应的响应内容做协议正确性校验,但Dart默认的socket.listen是持续订阅流的监听模式,无法在循环代码块内直接获取单次写入对应的响应。
待调整的原有代码逻辑:
for (var item in list){ socket.add(item.description); // 期望在此处同步读取当前写入对应的响应 // socket.read()? }
可行实现方案
方案1:转换广播流按次等待响应
初始化Socket后先将默认的单订阅流转为支持多次监听的广播流,提前接入自定义的协议拆包转换器,把无边界的TCP字节流拆分为独立的完整响应帧。循环中每次写入数据后,直接await广播流的下一个事件,即可拿到当前写入对应的响应。
示例代码:// 初始化Socket后统一处理流转换,拆包转换器需根据自身协议实现 final frameStream = socket .asBroadcastStream() .transform(FrameSplitter()); // 自定义拆包逻辑,输出逐帧的完整响应 for (var item in list) { socket.add(item.description); // 等待下一帧完整响应 final currentResp = await frameStream.first; // 执行协议校验 checkProtocolError(currentResp); }方案2:用Completer维护请求响应队列
全局仅注册一次Socket监听,在监听回调中统一处理拆包逻辑,同时维护一个先进先出的Completer队列。循环写入数据前,为当前请求创建一个Completer加入队列,写入后await该Completer的结果;监听中每拆出一帧完整响应,就取出队列头部的Completer将响应返回。如果协议支持请求ID标识,也可以用Map结构按请求ID匹配响应,支持服务端乱序返回的场景。
示例代码:final waitQueue = Queue<Completer<List<int>>>(); // 全局唯一的Socket监听,统一处理收包、拆包 socket.listen((rawData) { final frames = splitFramesByProtocol(rawData); for (var frame in frames) { if (waitQueue.isNotEmpty) { waitQueue.removeFirst().complete(frame); } } }); for (var item in list) { final completer = Completer<List<int>>(); waitQueue.add(completer); socket.add(item.description); // 等待当前请求对应的响应 final resp = await completer.future; checkProtocolError(resp); }注意避坑
Dart是单线程事件循环模型,不存在可直接使用的同步阻塞式Socket读取方法,如果强行用同步阻塞逻辑等待数据,会卡住整个事件循环,导致Socket底层IO事件无法分发,最终程序完全卡死,所有读取逻辑都必须用异步await的方式实现。
重要提示:TCP是面向字节流的协议,没有天然的数据包边界,以上所有方案都必须先实现匹配自身业务协议的拆包逻辑,处理粘包、半包问题,否则会出现请求和响应错位匹配的问题。常见拆包规则包括:固定包长、包头携带包总长度字段、指定特殊字节作为包结束标记。
内容的提问来源于stack exchange,提问作者PosF

