首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java Socket 文件下載器實戰:斷點續傳與 Android 客戶端協同

     編輯:Java 相關知識

項目背景

本項目實現基於 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);
        }
    }
}

Android 客戶端實現

Service 後台下載架構

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());
        }
    }
}

AndroidManifest 關鍵配置

<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+ 需前台服務 -->

關鍵技術點深度解析

1. 斷點續傳核心機制

  • 服務端:RandomAccessFile.seek(offset) 或 FileInputStream.skip(offset) 定位文件指針
  • 客戶端:FileOutputStream(file, true) 追加寫入模式
  • 狀態持久化:SharedPreferences 記錄已傳輸字節數,進程重啟自動恢復

2. 內存優化策略

// 8KB 緩衝區平衡內存與系統調用開銷
byte[] buffer = new byte[8192]; 

// 直接寫入文件流,避免全量加載到內存
fos.write(buffer, 0, len); 

3. 併發處理增強

// 服務端採用線程池替換 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

擴展方向

  • 多線程分片下載:文件分段,多 Socket 並行傳輸,提升帶寬利用率
  • TLS 加密傳輸:SSLSocket 替換純文本 Socket,保障數據安全
  • 傳輸壓縮:集成 Zstd/LZ4 壓縮文本類文件,節省帶寬
  • WebSocket 升級:瀏覽器端直接下載,無需客戶端安裝

總結

基於 Socket 的文件下載器通過「雙階段協議 + 斷點持久化 + 流式傳輸」實現了可靠、高效的文件分發能力。核心難點在於異常狀態下的數據一致性保障與 Android 後台運行限制的突破。生產環境建議增加校驗和、限流熔斷、傳輸加密等企業級特性。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved