Flutter WebSocket传输大Base64图片时JSON解析失败问题
Flutter WebSocket大Base64图片解析失败问题
问题概述
在Flutter WebSocket开发场景中,客户端与服务器建立连接后发送Base64编码的图片列表,服务器端无法解析大尺寸图片:
- 使用Isolate解析时,10-20KB的图片可正常解析
- 图片超过100KB时,服务器端频繁出现解析错误
服务器端错误日志
flutter: Error in parsing : FormatException: Unexpected character (at character 1) bgQpQ27iPvAA4GOfWrl69Q5fsNuJDi2WxtUAG8IAACZKivnBUT9KovtKJCVvSssK2phGwISmZJT... ^ flutter: Error in parsing : FormatException: Unexpected character (at character 1) LjSgtxK1BSQoEbukA8iDxSN8/uStme7zUz/8gf1qCtUcYYQlS5CXECRwCoGO3+k079g0anRr65e... ^ flutter: Error in parsing : FormatException: Unexpected character (at character 1) RjltlneHVEEpAKP7gcYM8HFCgoR4hbUUAEGDCZ5ic1YXV9ksMstttuBGU5KRzE0oypsOy44otpk... ^ flutter: Error in parsing : FormatException: Unexpected character (at character 1) eGI0gVu4h5xaVAiEbs8YI95qivFtKdUEpTAngdSZp63cm4UNwy2QevUf4pO7IS9KUxBI9571bRS... ^ flutter: Error in parsing : FormatException: Unexpected character (at character 1) KUMDn1qYtLBd5ejULhbbaQ3IbjccA8wadc+IL9VtbMWraLZktuKhIlQ27hE/LtVVf4ubtaoz4eB... ^ flutter: Error in parsing : FormatException: Unexpected character (at character 1) +LYH3VAjvW1kV4ZFDLZBkGQe9O2zduhRK8kZEnpS0oTIIweKMgpJk8elJ2UqtF6ldssEJIjil3b...
解析用Isolate代码
class ServerLogsParser { final Uint8List buffer; String get encodedString => String.fromCharCodes(buffer); ServerLogsParser({required this.buffer}); Future<ServerLogs?> parseInBackground() async { final p = ReceivePort(); await Isolate.spawn(_decodeAndParseJson, p.sendPort); final res = await p.first; return res is ServerLogs ? res : null; } Future<void> _decodeAndParseJson(SendPort p) async { try { final jsonData = jsonDecode(encodedString); Isolate.exit(p, ServerLogs.fromMap(jsonData)); } catch (e) { print("Error in parsing : $e"); Isolate.exit(p, null); } } }
服务器端连接处理代码
void handleConnection(Socket socket, Emitter<WebSocketState> emit) async { printWarning("Connection from Client: ${socket.remoteAddress}"); socket.listen((event) async { final message = String.fromCharCodes(event); printSuccess("text from client: ${message}"); final serverLogs = await ServerLogsParser(buffer: event).parseInBackground(); if (serverLogs != null) { add(UpdateLogsEvent(serverLogs: serverLogs)); print(serverLogs); if (serverLogs.action == ActionEnum.login) { add( HandleLogin( email: serverLogs.data['email'], password: serverLogs.data['password'], socket: socket, ), ); } } }, onError: (error) { printError(error); clients.remove(socket); socket.close(); }, onDone: () { printWarning("Server left"); clients.remove(socket); socket.close(); }); }
问题根源与解决方案
核心原因
Socket的listen回调中,event是分片接收的二进制数据:当数据量较大(如100KB以上)时,WebSocket会将数据拆分成多个帧发送,服务器端单次event只能拿到部分数据。直接用这部分不完整的数据转字符串并解析JSON,必然出现格式错误。小尺寸图片数据量小,能一次性完整接收,所以解析正常。
解决方案
1. 拼接完整数据+标记边界
服务器端维护每个Socket的接收缓冲区,将分片数据拼接,直到检测到完整数据的边界标记(如换行符)后再解析。客户端发送数据时需添加该标记。
修改后的服务器端代码
void handleConnection(Socket socket, Emitter<WebSocketState> emit) async { printWarning("Connection from Client: ${socket.remoteAddress}"); final buffer = BytesBuilder(); // 为每个连接维护独立缓冲区 socket.listen((event) async { buffer.add(event); final fullData = buffer.takeBytes(); final message = String.fromCharCodes(fullData); // 检测客户端约定的完整数据标记(此处以换行符为例) if (message.endsWith('\n')) { final trimmedMsg = message.trim(); printSuccess("text from client: $trimmedMsg"); try { final serverLogs = await ServerLogsParser(buffer: Uint8List.fromList(trimmedMsg.codeUnits)).parseInBackground(); if (serverLogs != null) { add(UpdateLogsEvent(serverLogs: serverLogs)); print(serverLogs); if (serverLogs.action == ActionEnum.login) { add( HandleLogin( email: serverLogs.data['email'], password: serverLogs.data['password'], socket: socket, ), ); } } } catch (e) { printError("Parse error: $e"); } } else { // 数据未完整,重新放回缓冲区 buffer.add(fullData); } }, onError: (error) { printError(error); clients.remove(socket); socket.close(); }, onDone: () { printWarning("Server left"); clients.remove(socket); socket.close(); }); }
客户端发送代码示例
// 客户端发送JSON时添加换行符作为完整标记 final jsonStr = jsonEncode({'images': base64ImageList}); webSocket.add('$jsonStr\n');
2. 额外优化建议
- Isolate复用:当前每次解析都创建新Isolate,频繁销毁会损耗性能,可改为Isolate池复用机制。
- 减少Base64开销:大图片的Base64编码会比原数据大30%,可直接发送二进制图片数据,降低传输体积和编码解码成本。
内容的提问来源于stack exchange,提问作者Agnel Selvan
相关产品推荐
相关产品推荐

