本項目實現基於 TCP Socket 的文件傳輸系統,包含 Java 服務端與 Android 客戶端,支持文件大小預傳、斷點續傳、進度回調等企業級功能。適用於局域網大文件傳輸、設備固件升級等場景。
┌─────────────┐ TCP Socket ┌─────────────┐
│ Android │ ◄─────────────────► │ Java PC │
│ 客戶端 │ 9888 端口 │ 服務端 │
└─────────────┘ └─────────────┘
│ │
▼ ▼
進度回調 UI 文件流操作
SharedPreferences 持久化 隨機訪問文件
採用「查詢大小 → 確認傳輸」兩階段協議,天然支持斷點續傳:
public class FileServer implements Runnable {
private static final String SERVER_IP = "192.168.155.1";
private static final int PORT = 9888;
private File transferFile = new File("E:/firmware.bin"); // 待傳輸文件
@Override
public void run() {
try (ServerSocket server = new ServerSocket(PORT)) {
System.out.println("Server started on " + PORT);
while (true) {
Socket client = server.accept();
new Thread(() -> handleClient(client)).start(); // 多線程並發
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void handleClient(Socket client) {
try (BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(client.getOutputStream())), true);
BufferedOutputStream bos = new BufferedOutputStream(client.getOutputStream())) {
String cmd = in.readLine();
if ("file_size".equals(cmd)) {
// 階段1:返回文件大小
long fileSize = transferFile.length();
out.println(fileSize);
System.out.println("File size sent: " + fileSize);
}
else if (cmd.startsWith("ok")) {
// 階段2:斷點續傳核心邏輯
int offset = Integer.parseInt(cmd.substring(2)); // 已傳輸字節數
try (FileInputStream fis = new FileInputStream(transferFile);
BufferedInputStream bis = new BufferedInputStream(fis)) {
fis.skip(offset); // 跳過已傳輸部分
byte[] buffer = new byte[8192];
int len;
while ((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
bos.flush();
System.out.println("Transfer complete, offset: " + offset);
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
closeQuietly(client);
}
}
}
public class DownloadService extends Service {
private static final String SERVER_IP = "192.168.155.1";
private static final int PORT = 9888;
private Thread downloadThread;
private boolean isStop = false;
// 進度回調 Handler
public static Handler progressHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
if (msg.what == 0x0001) {
String progress = msg.getData().getString("value");
// 更新 UI:textView.setText(progress);
}
}
};
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
downloadThread = new Thread(this::downloadFile);
downloadThread.start();
return START_STICKY; // 服務被殺後自動重啟
}
private void downloadFile() {
try {
// 1. 獲取文件大小
long fileSize = getFileSize();
if (fileSize <= 0) return;
// 2. 讀取斷點記錄
SharedPreferences sp = getSharedPreferences("download", MODE_PRIVATE);
int downloaded = sp.getInt("offset", 0);
// 3. 斷點續傳下載
try (Socket socket = new Socket(SERVER_IP, PORT);
PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
FileOutputStream fos = new FileOutputStream(getSaveFile(), true)) { // 追加模式
out.println("ok" + downloaded); // 發送斷點位置
out.flush();
byte[] buffer = new byte[8192];
int len;
while ((len = bis.read(buffer)) != -1 && !isStop) {
fos.write(buffer, 0, len);
downloaded += len;
// 持久化斷點
sp.edit().putInt("offset", downloaded).apply();
// 進度回調
int progress = (int) (downloaded * 100.0f / fileSize);
sendProgress(progress + "%");
if (downloaded >= fileSize) {
sp.edit().putInt("offset", 0).apply(); // 重置斷點
break;
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
private long getFileSize() throws IOException {
try (Socket socket = new Socket(SERVER_IP, PORT);
PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
out.println("file_size");
out.flush();
return Long.parseLong(in.readLine());
}
}
}
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<service android:name=".DownloadService"
android:foregroundServiceType="dataSync" />
<!-- Android 10+ 需前台服務 -->
RandomAccessFile.seek(offset) 或 FileInputStream.skip(offset) 定位文件指針FileOutputStream(file, true) 追加寫入模式// 8KB 緩衝區平衡內存與系統調用開銷
byte[] buffer = new byte[8192];
// 直接寫入文件流,避免全量加載到內存
fos.write(buffer, 0, len);
// 服務端採用線程池替換 new Thread()
ExecutorService pool = Executors.newFixedThreadPool(50);
pool.execute(() -> handleClient(client));
// 設置 Socket 選項優化傳輸
socket.setSendBufferSize(64 * 1024); // 64KB 發送緩衝區
socket.setReceiveBufferSize(64 * 1024);
socket.setTcpNoDelay(true); // 禁用 Nagle 算法,降低延遲
| 問題 | 原因 | 解決方案 |
|---|---|---|
| 傳輸中斷文件損壞 | 未正確關閉流 | try-with-resources 確保流關閉;校驗文件 MD5 |
| 大文件傳輸 OOM | 一次性讀取全文件 | 流式分塊讀寫;設置合理緩衝區大小 |
| Android 8+ 後台限制 | Service 被系統殺死 | 前台服務 + WakeLock + START_STICKY |
| 局域網 IP 變動 | DHCP 分配地址變化 | 服務發現機制(mDNS/SSDP)或固定靜態 IP |
SSLSocket 替換純文本 Socket,保障數據安全基於 Socket 的文件下載器通過「雙階段協議 + 斷點持久化 + 流式傳輸」實現了可靠、高效的文件分發能力。核心難點在於異常狀態下的數據一致性保障與 Android 後台運行限制的突破。生產環境建議增加校驗和、限流熔斷、傳輸加密等企業級特性。