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):一旦某片失败,重传成本高
分片上传的流程,我画了一张图,你看一眼就明白了:
代码实现上,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请求头。流程是这样的:
- 手机端先发一个HEAD请求,获取文件总大小
- 检查本地已接收的文件大小(比如已收到3MB)
- 发GET请求,带上
Range: bytes=3145728- - 车机端返回
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 整体架构
最后,我把整个文件传输的架构梳理一下。你看这张图,基本涵盖了所有关键模块:
嗯,这一章的内容差不多就这些。文件传输看起来是个小功能,但要做好,涉及的知识点其实不少。协议选型、分片策略、断点续传、加密保护,每一个环节都有细节要注意。我在实际项目中踩过的坑,基本都写在上面了,希望能帮你少走弯路。
公众号:蓝海资料掘金营,微信deep3321