文件断点续传实现 (1 -- java实现)
java服务端和java客户端的代码
( C# 客户端参考 文件断点续传实现 ( 2-- C# 客户端) )
服务端:
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/:-)"); } }