Flutter从TCP流读取固定字节,实现含文件名的文件接收
Flutter TCP Socket 文件接收:读取指定字节文件名 + 性能优化
问题描述
我有一段从TCP Socket接收文件的Flutter代码(目前速度稍慢,欢迎提出优化建议)。我需要在数据包的前X字节中传输文件名,请问如何读取指定数量的字节?另外,我在Kotlin中曾通过将文件名放在前512字节中传输,截断空字节后获取字符串。
原代码如下:
Future<void> handleClient(Socket client) async { final directory = await getApplicationDocumentsDirectory(); if (!await directory.exists()) { await directory.create(); } String appDocPath = directory.path; var file = File('$appDocPath/1_received.mp4'); if (file.existsSync()) { file.deleteSync(); } client.listen((data) { file.writeAsBytesSync(data, mode: FileMode.append); }, onDone: () async { await client.close(); service.invoke( 'new_video_received', { "file": file.path, }, ); }, onError: (error) { print("error $error"); }); } ServerSocket.bind(InternetAddress.anyIPv4, dataPort.value) .then((ServerSocket server) { server.listen(handleClient); });
解决方案
1. 读取前X字节解析文件名
TCP是流式传输,第一帧数据可能不足指定长度(比如512字节),需要先缓存数据直到收集足够字节再解析文件名。实现代码如下:
import 'dart:convert'; import 'dart:io'; Future<void> handleClient(Socket client) async { final directory = await getApplicationDocumentsDirectory(); if (!await directory.exists()) { await directory.create(); } final String appDocPath = directory.path; // 和Kotlin端保持一致,文件名占用前512字节 const int fileNameByteLength = 512; final List<int> fileNameBuffer = []; late File targetFile; IOSink? fileWriter; bool hasParsedFileName = false; try { // 流式遍历Socket接收的数据 await for (final chunk in client) { if (!hasParsedFileName) { fileNameBuffer.addAll(chunk); // 缓存字节足够时解析文件名 if (fileNameBuffer.length >= fileNameByteLength) { // 截取前512字节,解码并去除空字符 final fileNameBytes = fileNameBuffer.sublist(0, fileNameByteLength); String fileName = utf8.decode(fileNameBytes) .replaceAll(RegExp(r'\x00'), '') .trim(); // 创建目标文件并初始化写入流 targetFile = File('$appDocPath/$fileName'); if (await targetFile.exists()) { await targetFile.delete(); } fileWriter = targetFile.openWrite(mode: FileMode.write); hasParsedFileName = true; // 将当前chunk中剩余的字节写入文件 if (fileNameBuffer.length > fileNameByteLength) { final remainingBytes = fileNameBuffer.sublist(fileNameByteLength); await fileWriter.add(remainingBytes); } } } else { // 直接写入文件内容 await fileWriter?.add(chunk); } } } catch (e) { print("传输或写入错误: $e"); } finally { // 确保资源被正确关闭 await fileWriter?.close(); await client.close(); } // 通知新视频接收完成 if (hasParsedFileName) { service.invoke( 'new_video_received', {"file": targetFile.path}, ); } } // 启动服务器(改用async/await更清晰) Future<void> startFileServer() async { final ServerSocket server = await ServerSocket.bind(InternetAddress.anyIPv4, dataPort.value); server.listen(handleClient); }
2. 性能优化建议
针对原代码速度慢的问题,可从以下几点优化:
- 替换同步写为异步写:原代码用
writeAsBytesSync同步写入会阻塞Dart事件循环,改用File.openWrite()获取IOSink异步写入,避免阻塞,提升吞吐量。 - 复用文件句柄:原代码每次接收数据都重复打开/关闭文件,频繁IO操作拖慢速度,用
IOSink保持文件打开状态,直到传输完成再关闭。 - 流式处理逻辑:用
await for遍历Socket数据流,比listen更易控制顺序逻辑(先解析文件名再写文件),代码更易维护。 - 调整Socket参数:开启TCP_NODELAY减少延迟,或调整缓冲区大小:
client.setOption(SocketOption.tcpNoDelay, true); client.setOption(SocketOption.sendBufferSize, 65536); // 设置64KB发送缓冲区 client.setOption(SocketOption.receiveBufferSize, 65536); // 设置64KB接收缓冲区 - 增强错误处理:在文件写入和资源关闭环节增加异常捕获,避免异常导致资源泄漏或程序崩溃。
内容的提问来源于stack exchange,提问作者blacky99
相关产品推荐
相关产品推荐

