17、车机-手机文件传输:FTP/HTTP文件服务、大文件分片传输、断点续传、加密传输

车机与手机之间的文件传输,说实话,是互联功能里最容易被低估的环节。很多人觉得不就是传个文件嘛,蓝牙或者数据线一插就完事了。但实际做起来,坑多得很。

我记得第一次做车机文件传输时,用户反馈说传一部2GB的行车记录仪视频,传到一半断了,又得从头来。用户气得不行,我也很无奈。后来我才意识到,车机环境跟手机之间不一样——网络不稳定、存储空间有限、传输链路复杂。所以这一章,我重点聊聊怎么把文件传输这件事做稳、做快、做安全。

核心要点:车机-手机文件传输不是简单的文件拷贝,而是一个涉及协议选型、分片策略、断点续传、加密保护的系统工程。

17.1 传输协议选型:FTP vs HTTP

选协议,是第一步。我个人习惯先看场景再定方案。

FTP(文件传输协议),老牌协议,简单直接。车机上开一个FTP服务端,手机当客户端连上去,上传下载都行。优点是实现简单,很多嵌入式系统原生支持。缺点也很明显——明文传输、被动模式在NAT环境下容易出问题、没有加密。

HTTP/HTTPS,现在更主流。车机上启动一个轻量级HTTP服务器(比如用NanoHTTPD或者自建Netty),手机通过RESTful API上传下载。好处是天然支持TLS加密、容易做鉴权、分片上传也方便。

我个人的建议是:

  • 如果只是传一些小文件(照片、联系人),FTP够用,但一定要配合加密层
  • 如果是大文件(视频、地图数据包),优先选HTTP,分片和续传更好做
特性 FTP HTTP
实现复杂度
加密支持 需FTPS/SFTP HTTPS原生支持
分片上传 不原生支持 通过Content-Range支持
断点续传 需自定义 HTTP Range头原生支持
车机资源占用 中等

17.2 大文件分片传输

车机的Wi-Fi模块,说实话,性能参差不齐。有些车机的Wi-Fi芯片还是2.4GHz单频,传输速率也就几十Mbps。传一个几百MB的文件,一旦遇到信号干扰或者车机CPU负载高,很容易超时断开。

所以,大文件必须分片。我一般把文件切成1MB~4MB的块,每块独立上传。为什么是这个范围?

  • 太小了(比如256KB):HTTP请求次数太多,握手开销大
  • 太大了(比如16MB):一旦某片失败,重传成本高

分片上传的流程,我画了一张图,你看一眼就明白了:

大文件分片上传流程 手机端(客户端) 车机端(服务端) ① 文件分片(1MB/片) ② 每片计算MD5 ③ 上传分片+MD5 ④ 校验MD5 ⑤ 合并分片为完整文件 每片独立上传,失败只重传该片,不重传整个文件 车机端收到所有分片后,按顺序合并并校验整体MD5

代码实现上,Android端用OkHttp的MultipartBody上传分片,车机端用Java的RandomAccessFile写入。核心逻辑是这样的:

// 手机端:分片上传
private void uploadChunk(File file, int chunkIndex, int chunkSize) {
    long start = chunkIndex * chunkSize;
    long end = Math.min(start + chunkSize, file.length());
    int actualSize = (int)(end - start);
    
    byte[] buffer = new byte[actualSize];
    RandomAccessFile raf = new RandomAccessFile(file, "r");
    raf.seek(start);
    raf.read(buffer);
    raf.close();
    
    // 计算MD5
    String md5 = MD5Utils.calculate(buffer);
    
    // 上传
    RequestBody requestBody = new MultipartBody.Builder()
        .setType(MultipartBody.FORM)
        .addFormDataPart("chunk", String.valueOf(chunkIndex))
        .addFormDataPart("totalChunks", String.valueOf(totalChunks))
        .addFormDataPart("md5", md5)
        .addFormDataPart("file", "chunk_" + chunkIndex, 
            RequestBody.create(MediaType.parse("application/octet-stream"), buffer))
        .build();
    
    // 发送请求...
}

我的经验:分片大小不要写死。我一般会根据车机Wi-Fi的信号强度动态调整——信号好时用4MB,信号差时降到512KB。这样能平衡速度和稳定性。

17.3 断点续传

断点续传,说白了就是让传输能从断掉的地方接着传,而不是从头再来。这个功能在车机场景下特别重要——你想想看,车开进隧道信号断了,出隧道后能接着传,体验就好很多。

HTTP协议本身就支持断点续传,靠的是Range请求头。流程是这样的:

  1. 手机端先发一个HEAD请求,获取文件总大小
  2. 检查本地已接收的文件大小(比如已收到3MB)
  3. 发GET请求,带上 Range: bytes=3145728-
  4. 车机端返回 206 Partial Content,从指定位置开始传输

但这里有个坑——车机端的文件系统可能不支持随机写入。我曾经遇到过一个问题:车机用FAT32格式的U盘存储,断点续传时写入位置不对,导致文件损坏。

避坑指南:车机端接收分片时,一定要用RandomAccessFile的seek()方法定位到正确位置再写入。不要用FileOutputStream追加写入,因为如果分片乱序到达,追加写入会导致数据错乱。

分片上传场景下的断点续传,我一般这样处理:

// 车机端:接收分片并写入
public void receiveChunk(int chunkIndex, byte[] data, String md5) {
    // 校验MD5
    String calcMd5 = MD5Utils.calculate(data);
    if (!calcMd5.equals(md5)) {
        // 校验失败,要求重传
        return;
    }
    
    // 写入文件
    RandomAccessFile raf = new RandomAccessFile(tempFile, "rw");
    raf.seek(chunkIndex * CHUNK_SIZE);
    raf.write(data);
    raf.close();
    
    // 记录已接收的分片
    receivedChunks.add(chunkIndex);
    
    // 检查是否所有分片都收齐了
    if (receivedChunks.size() == totalChunks) {
        mergeFile();
    }
}

17.4 加密传输

车机跟手机之间传文件,有些数据很敏感——比如行车记录仪的视频、车辆诊断信息、用户的通讯录。这些数据如果明文传输,被中间人截获就麻烦了。

加密方案我一般分两层:

  • 传输层加密:直接用HTTPS,TLS 1.3。这是最省事的方案,证书管理好就行
  • 应用层加密:对文件内容本身加密,即使传输链路被破解,数据也是安全的

应用层加密,我推荐用AES-256-GCM。为什么选GCM模式?因为它同时提供加密和完整性校验,能检测出数据是否被篡改。

// 手机端:加密分片
public byte[] encryptChunk(byte[] plainData, SecretKey key) {
    Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
    byte[] iv = new byte[12]; // GCM推荐12字节IV
    SecureRandom.getInstanceStrong().nextBytes(iv);
    
    GCMParameterSpec spec = new GCMParameterSpec(128, iv);
    cipher.init(Cipher.ENCRYPT_MODE, key, spec);
    
    byte[] cipherData = cipher.doFinal(plainData);
    
    // 将IV和密文一起发送
    byte[] result = new byte[iv.length + cipherData.length];
    System.arraycopy(iv, 0, result, 0, iv.length);
    System.arraycopy(cipherData, 0, result, iv.length, cipherData.length);
    
    return result;
}

密钥怎么管理?我个人的做法是:手机和车机首次配对时,通过蓝牙安全通道交换一个临时密钥。之后每次传输前,用这个临时密钥派生出一个会话密钥。会话密钥用完即弃,这样即使某个会话被破解,也不会影响其他传输。

17.5 整体架构

最后,我把整个文件传输的架构梳理一下。你看这张图,基本涵盖了所有关键模块:

车机-手机文件传输整体架构 手机端 文件分片模块 加密模块 断点续传管理 MD5校验 HTTP客户端 密钥管理 车机端 HTTP服务端 解密模块 分片合并 完整性校验 文件存储管理 断点记录 传输链路:HTTPS(TLS 1.3)+ 应用层AES-256-GCM双重加密

嗯,这一章的内容差不多就这些。文件传输看起来是个小功能,但要做好,涉及的知识点其实不少。协议选型、分片策略、断点续传、加密保护,每一个环节都有细节要注意。我在实际项目中踩过的坑,基本都写在上面了,希望能帮你少走弯路。


公众号:蓝海资料掘金营,微信deep3321