文件断点续传实现 (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/:-)");
    }
}

 

posted on 2020-12-09 18:43  hi-gdl  阅读(248)  评论(0编辑  收藏  举报

导航