1. 程式人生 > 實用技巧 >檔案斷點續傳實現

檔案斷點續傳實現

直接上程式碼了

服務端:

package com.sunyard.vst.upload;

import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @標題 Socket服務
 * @作者 gdl
 *
 */
public class UploadServer extends
Thread{ private static Logger LOG = LoggerFactory.getLogger(UploadServer.class); /** * 上傳埠 */ private int socketPort ; /** * 執行緒池數量 * (不是單個CPU執行緒池大小,是總數) */ private int poolSize ; /** * Socket服務 */ private ServerSocket serverSocket ;
private boolean running = true; /** * 執行緒池 */ private ExecutorService executorService; public UploadServer(int socketPort,int poolSize) { this.socketPort = socketPort; this.poolSize = poolSize; try { serverSocket = new ServerSocket(socketPort); executorService
=Executors.newFixedThreadPool(poolSize); }catch(Exception e) { LOG.error("建構函式(初始化)異常",e); } } public void run() { LOG.info("服務已經啟動..."); while(running) { Socket socket = null; try { socket = serverSocket.accept(); executorService.execute(new UploadHandler(socket)); }catch(Exception e) { LOG.info(e.getMessage(),e); } try { LOG.info("一個Socket已經連線!"); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } /** * 停止服務 */ public void Stop() { this.running = false; } }

Socket處理類

package com.sunyard.vst.upload;

import java.io.Closeable;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.net.Socket;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sunyard.vst.utils.MD5Util;

/**
 * @title Socket處理類
 * @author gdl
 *
 */
public class UploadHandler implements Runnable{

    private static Logger LOG = LoggerFactory.getLogger(UploadHandler.class);
    
    /**
     * Socket
     */
    private Socket socket ;
    private String dir = "E:\\temp\\files" ;
    private int buffer_size = 1024*64 ;//每次傳輸檔案長度
    
    private InputStream input = null;
    private OutputStream output = null;
    private DataInputStream in = null;
    private DataOutputStream out = null;
    private RandomAccessFile raf = null;
    
    public UploadHandler(Socket socket) {
        LOG.info("連線成功");
        this.socket = socket ;
    }
    
    /**
     * 1. 首先獲取檔案的md5,檔案大小,檔名稱
     * 2. 返回前端檔案是否存在,檔案大小,檔名
     * 
     */
    public void run() {
        try {
            input = socket.getInputStream();
            output = socket.getOutputStream();
            in = new DataInputStream(input);
            out = new DataOutputStream(output);
            
            //第一次互動
            int len = in.readInt();
            byte[] buffer = new byte[len];
            in.read(buffer);
            String msg = new String(buffer,UploadConstant.CHARSET);
            LOG.info("MSG:"+msg);
            String[] strs = msg.split("\\|");
            String filemd5 = strs[0] ;
            long filelength = Long.valueOf(strs[1]);
            String filename = strs[2];
            
            msg = analyse(filemd5, filelength, filename);
            
            buffer = msg.getBytes(UploadConstant.CHARSET);
            out.writeInt(buffer.length);
            out.write(buffer);
            out.flush();
            
            
            //第二次互動 (僅當02開頭時繼續上傳)
            if(msg.startsWith(UploadConstant.E02)) {
                File tempfile = new File(dir,filename+".tmp");
                if(!tempfile.exists()) {
                    tempfile.createNewFile();
                }
                raf=new RandomAccessFile(tempfile, "rw");
                long offset = tempfile.length() ;
                raf.seek(offset);
                int length;
                byte[] buf=new byte[buffer_size];
                while((length=in.read(buf, 0, buf.length))!=-1){
                    raf.write(buf,0,length);
                    offset += length;
                    //響應上傳進度
                    if(offset == filelength) {
                        //上傳結束
                        break;
                    }
                }
                try {
                    close(raf);
                    raf = null;
                }finally {
                    msg = response(filemd5, filename, tempfile);
                    
                    buffer = msg.getBytes(UploadConstant.CHARSET);
                    out.writeInt(buffer.length);
                    out.write(buffer);
                    out.flush();
                }
            }
        } catch(Exception e) {
            LOG.error(e.getMessage(),e);
        } finally {
            close(raf);
            close();
        }
                
    }

    /**
     * 上傳結束後響應報文
     * @param filemd5
     * @param filename
     * @param tempfile
     * @return
     */
    private String response(String filemd5, String filename, File tempfile) {
        String msg;
        String md5 = MD5Util.getMD5(tempfile);
        if(md5.equalsIgnoreCase(filemd5)) {
            File target = new File(dir,filename);
            LOG.info("開始重新命名");
            tempfile.renameTo(target);//重新命名
            if(target.exists()) {
                LOG.info("重新命名成功");
                msg = UploadConstant.E06;
            }else {
                LOG.info("重新命名失敗");
                msg = UploadConstant.E04;
            }
            
        }else {
            LOG.info("上傳成功,但是MD5和上傳前不同");
            msg = UploadConstant.E05;
        }
        return msg;
    }

    /**
     * 分析
     * @param filemd5
     * @param filelength
     * @param filename
     * @return 
     *   00 檔案大小為0
     *   01 檔案存在,不需要重新上傳
     *   02 臨時檔案存在,豎線後是檔案大小
     *   03 檔案存在,但是md5不同
     * @throws IOException
     */
    private String analyse(String filemd5, long filelength, String filename) throws IOException {
        String msg;
        //根據md5,檔名等資訊獲取檔案是否存在
        File file = new File(dir,filename);
        if(file.exists()) {
            String md5 = MD5Util.getMD5(file);
            if(md5.equalsIgnoreCase(filemd5)) {
                LOG.info("上傳檔案已經存在且MD5相同,不需要重新上傳");
                msg = UploadConstant.E01 ;
            }else {
                LOG.info("上傳檔案已經存在,但是MD5不同");
                msg = UploadConstant.E03 ;
            }
        }else {
            if(0 == filelength) {//上傳檔案大小為零
                file.createNewFile();
                LOG.info("上傳檔案大小為0");
                msg = UploadConstant.E00 ;
            }else {
                file = new File(dir,filename+".tmp");//未上傳完整的臨時檔案
                if(file.exists()) {
                    long len = file.length();
                    LOG.info("上傳檔案不存在,但是臨時檔案存在,檔案大小:"+len);
                    msg = UploadConstant.E02+"|"+buffer_size+"|"+len ;
                }else {
                    LOG.info("上傳檔案不存在");
                    msg = UploadConstant.E02+"|"+buffer_size+"|0" ;
                }
            }
        }
        return msg;
    }
    
    private void close() {
        close(out);
        close(in);
        close(output);
        close(input);
    }
    
    private void close(Closeable stream) {
        if(null != stream) {
            try {
                stream.close();
            } catch (Exception e) {
                LOG.error("關閉{}發生異常",stream.getClass().getSimpleName(),e);
            }
        }
    }

}

常量類

package com.sunyard.vst.upload;

/**
 * 
 *   00 檔案大小為0
 *   01 檔案存在,不需要重新上傳
 *   02 臨時檔案存在,豎線後是檔案大小
 *   03 檔案存在,但是md5不同
 *   
 *   04 上傳後重命名失敗
 *   05 上傳後MD5不一致
 *   06 上傳成功,MD5一致,重新命名正常
 * @author gdl
 *
 */
public class UploadConstant {

    public static final String E00 = "00";//檔案大小為0
    public static final String E01 = "01";//檔案存在,不需要重新上傳
    public static final String E02 = "02";//臨時檔案存在,豎線後是檔案大小
    public static final String E03 = "03";//檔案存在,但是md5不同
    
    public static final String E04 = "04";//上傳後重命名失敗
    public static final String E05 = "05";//上傳後MD5不一致
    public static final String E06 = "06";//上傳成功,MD5一致,重新命名正常
    
    public static final String CHARSET = "gbk";//文字編碼
    
}

工具類(獲取檔案MD5):

package com.sunyard.vst.utils;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.MessageDigest;

import org.apache.commons.codec.binary.Hex;

public class MD5Util {

    /**
     * 獲取一個檔案的md5值(可處理大檔案)
     * @return md5 value
     */
    public static String getMD5(File file) {
        FileInputStream fileInputStream = null;
        try {
            MessageDigest MD5 = MessageDigest.getInstance("MD5");
            fileInputStream = new FileInputStream(file);
            byte[] buffer = new byte[8192];
            int length;
            while ((length = fileInputStream.read(buffer)) != -1) {
                MD5.update(buffer, 0, length);
            }
            return new String(Hex.encodeHex(MD5.digest()));
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            try {
                if (fileInputStream != null){
                    fileInputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
}

客戶端:

package com.sunyard.vst.test.client;

import java.io.Closeable;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.net.Socket;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sunyard.vst.utils.MD5Util;

public class UploadClient extends Thread{
    
    private static Logger LOG = LoggerFactory.getLogger(UploadClient.class);

    
    DataInputStream in = null;
    DataOutputStream out = null;
    
    File file ;
    String ip;
    int port;
    
    public UploadClient(String filepath, String ip, int port) {
        this.file = new File(filepath);
        this.ip = ip;
        this.port = port ;
    }
    
    public UploadClient(File file, String ip, int port) {
        this.file = file;
        this.ip = ip;
        this.port = port ;
    }

    public static UploadClient upload(String filepath, String ip, int port) throws FileNotFoundException {
        File file = new File(filepath);
        if(!file.exists()) {
            throw new FileNotFoundException("上傳檔案不存在");
        }
        UploadClient instance = new UploadClient(file,ip,port) ;
        instance.start();
        return instance ;
    }
    
    public void run() {
        execute();
    }
    
    /**
     * 執行檔案上傳
     * @param filepath
     * @param ip 
     * @param port
     */
    public void execute() {
        String filename = file.getName();
        String filemd5 = MD5Util.getMD5(file);
        long filelength = file.length();
        String msg = filemd5 + "|" + filelength + "|" + filename;
        Socket socket = null;
        OutputStream output = null;
        InputStream input = null ;
        RandomAccessFile raf = null;
        try {
            socket = new Socket(ip,port);
            socket.setSoTimeout(30000);//設定超時時間:30秒
            output = socket.getOutputStream();
            input = socket.getInputStream();
            out = new DataOutputStream(output);
            in = new DataInputStream(input);
            
            //傳送文字 (第一次互動)
            byte[] buffer = msg.getBytes("gbk");
            int len = buffer.length ;
            out.writeInt(len);
            out.write(buffer);
            out.flush();
            //收到響應
            len = in.readInt();
            buffer = new byte[len];
            in.read(buffer);
            msg = new String(buffer,"gbk");
            LOG.info("response : "+msg);
            /* 
             * 收到報文後第二階段
             *   00 檔案大小為0
             *   01 檔案存在,不需要重新上傳
             *   02 臨時檔案存在,豎線後是檔案大小
             *   03 檔案存在,但是md5不同
             */
            if(msg.startsWith("02")) {
                
                LOG.info("開始上傳");
                String[] strs = msg.split("\\|");
                int size = Integer.valueOf(strs[1]);
                long offset = Long.valueOf(strs[2]);
                raf = new RandomAccessFile(file,"r");
                raf.seek(offset);
                int length = 0;
                byte[] buf = new byte[size];
                while((length=raf.read(buf))>0){
                    out.write(buf,0,length);                            
                    out.flush();
                }
                
                LOG.info("上傳結束");
                len = in.readInt();
                buffer = new byte[len];
                in.read(buffer);
                msg = new String(buffer,"gbk");
                LOG.info("上傳後響應標示:"+msg);
            }else {
                //TODO ...
                LOG.info("未處理標示:"+msg);
            }
            
        } catch (Exception e) {
            LOG.error(e.getMessage(),e);
        } finally {
            close(raf);
            close(out);
            close(in);
            close(input);
            close(output);
        }
        
        
    }
    
    private void close(Closeable stream) {
        if(null != stream) {
            try {
                stream.close();
            } catch (Exception e) {
                LOG.error("關閉{}發生異常",stream.getClass().getSimpleName(),e);
            }
        }
    }
 
}

測試類:

package com.sunyard.vst.test.client;

import org.junit.Test;

import com.sunyard.vst.upload.UploadServer;

/**
 * @title 檔案上傳單元測試
 * @author gdl
 * @date 2020-12-9 10:35:07
 */
public class UploadTest {

    /**
     * 啟動服務端
     * @param args
     */
    public static void main(String[] args) {
        System.out.println("開始測試(服務端)");
        new Thread(new UploadServer(8889,20)).start();
        System.out.println("end/:-)");
    }
    
    /**
     * 啟動客戶端(單元測試啟動)
     */
    @Test
    public void startClient() {
        System.out.println("開始測試(客戶端)");
        new UploadClient("E:/1.jpg","127.0.0.1",8889).execute();
        System.out.println("end/:-)");
    }
}