You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 00:20:39