檔案斷點續傳實現
阿新 • • 發佈:2020-12-09
直接上程式碼了
服務端:
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 extendsThread{ 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/:-)"); } }