从自建Rust服务器向Android应用下载APK时出现流结束错误
问题分析与解决思路
核心问题定位
目前APK下载到95%左右中断,结合代码来看,大概率是Rust服务器未确保缓冲区数据全部发送就关闭连接,或是Android端读取逻辑的边界处理存在问题。
Rust 服务器端排查与修复
1. 强制刷新发送缓冲区
你当前用write_all(&stream)发送响应,但write_all仅保证字节写入内核缓冲区,不确保数据已发送到网络。必须在write_all后调用flush(),把缓冲区数据强制推送到网络:
let http_stream = HttpResponseBuilder { status : "200 OK".to_string(), headers : vec![], body : String::new() }.build_with_bytes(match fs::read(apk::get_apk_path()) { Ok(k_) => k_, Err(e_) => { error_response(stream, hkslib::error::format_error("handle_get_apk", e_.to_string())); return; } }); // 写入数据后强制刷新缓冲区 if let Err(e) = http_stream.write_all(&stream) { error!("Failed to write response: {}", e); } // 关键:确保所有数据发送到客户端 if let Err(e) = stream.flush() { error!("Failed to flush stream: {}", e); }
2. 改用流式文件发送(避免内存过载)
现在用fs::read把整个APK读进内存,大文件会占用大量内存,还可能导致发送时缓冲区压力过大。改成流式读取发送更稳定:
use std::io::{BufReader, Read, Write}; fn handle_get_apk(mut stream: TcpStream) { let apk_path = apk::get_apk_path(); let file = match fs::File::open(&apk_path) { Ok(f) => f, Err(e) => { error_response(stream, hkslib::error::format_error("handle_get_apk", e.to_string())); return; } }; let file_size = file.metadata().unwrap().len(); // 构建并发送HTTP响应头 let headers = format!( "HTTP/1.1 200 OK\r\nAccept-Ranges: bytes\r\nContent-Length: {}\r\n\r\n", file_size ); if let Err(e) = stream.write_all(headers.as_bytes()) { error!("Failed to write headers: {}", e); return; } stream.flush().unwrap(); // 分块读取并发送文件 let mut reader = BufReader::new(file); let mut buffer = [0; 4096]; loop { match reader.read(&mut buffer) { Ok(0) => break, // 文件读取完毕 Ok(n) => { if let Err(e) = stream.write_all(&buffer[0..n]) { error!("Failed to write file chunk: {}", e); break; } stream.flush().unwrap(); // 每块发送后立即刷新 } Err(e) => { error!("Failed to read file: {}", e); break; } } } }
3. 检查连接生命周期
确认服务器线程在处理完请求后,不要立即关闭TcpStream。TCP本身会有FIN握手,但必须确保数据全部发送完成后再释放连接。
Android 客户端排查与修复
1. 修正读取逻辑的边界判断
当前while(!channel.isClosedForRead)可能在最后一块数据未读完时提前退出,改成按文件总长度校验读取:
val totalLength = response.contentLength() ?: -1 var receivedLength = 0L val channel = response.bodyAsChannel() // 按总长度循环读取,直到接收完整 while (receivedLength < totalLength || totalLength == -1) { try { val bytes = channel.readByteArray(4096) if (bytes.isEmpty()) break localFile.appendBytes(bytes) receivedLength += bytes.size _log("Received $receivedLength bytes from $totalLength") } catch (e: Exception) { _log("Failed to read data... $e") e.printStackTrace() break } } // 校验文件完整性,不匹配则删除 if (totalLength != -1 && receivedLength != totalLength) { _log("Download incomplete: expected $totalLength, got $receivedLength") localFile.delete() }
2. 移除不必要的runBlocking嵌套
GlobalScope.launch(Dispatchers.IO)里再套runBlocking会阻塞线程,改用async/await逻辑更合理:
fun getFile(destination: String, client: HttpClient, headers: ArrayList<Pair<String, String>>?) { GlobalScope.launch(Dispatchers.IO) { log("sending request to $destination") val localFile = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "app.apk") try { client.prepareGet(destination) { headers?.forEach { (key, value) -> header(key, value) } }.execute { response -> if (response.status.value >= 400) { println(response.toString()) return@execute } // 上面修正后的读取逻辑放在这里 val totalLength = response.contentLength() ?: -1 var receivedLength = 0L val channel = response.bodyAsChannel() while (receivedLength < totalLength || totalLength == -1) { val bytes = channel.readByteArray(4096) if (bytes.isEmpty()) break localFile.appendBytes(bytes) receivedLength += bytes.size _log("Received $receivedLength bytes from $totalLength") } if (totalLength != -1 && receivedLength != totalLength) { _log("Download failed: incomplete file") localFile.delete() } else { _log("Download completed successfully") } } } catch (e: Exception) { _log("Request failed: $e") e.printStackTrace() } } }
额外验证步骤
- 用
curl直接从服务器下载APK:curl http://你的服务器IP/apk路径 -o test.apk,对比原文件大小和MD5值,确认服务器是否能发送完整文件。 - 在Rust服务器打印发送的总字节数,和原APK大小对比,确认是否发送了全部数据。
- 检查Android端是否有后台进程(如内存清理)中断下载,或存储权限是否正常。
内容的提问来源于stack exchange,提问作者HomeyKrogerSage
相关产品推荐
相关产品推荐

