Java断点续传(基于socket与RandomAccessFile的简单实现)

Java断点续传(基于socket与RandomAccessFile的简单实现)

  这是一个简单的C/S架构,基本实现思路是将服务器注册至某个空闲端口用来监视并处理每个客户端的传输请求。

  客户端先获得用户给予的需传输文件与目标路径,之后根据该文件实例化RandomAccessFile为只读,之后客户端向服务器发送需传输的文件名文件大小与目标路径,服务器没接收到一个客户端的请求就会建立一个新的线程去处理它,根据接收到的文件名到目标路径中去寻找目标路径中是否已经有该文件名的.temp临时文件(如果没有就创建它),之后服务器会将文件已经传输的大小(临时文件大小)返回给客户端(例如临时文件刚刚建立返回的便是0),客户端会将刚刚建立的RandomAccessFile对象的文件指针指向服务器返回的位置,之后以1kb为一组向服务器传输需传输文件的内容数据,服务器则接收数据并将其写入临时文件中,并根据现有数据画出进度条。在文件传输完毕后客户端会将临时文件重命名为最初接收到的文件名。

  服务器代码:

import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.ServerSocket;
import java.net.Socket;

import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
 
public class FileTransferServer extends ServerSocket {
 
    private static final int SERVER_PORT = 8899; // 服务端端口
 
    public FileTransferServer() throws Exception {
        super(SERVER_PORT);
    }
 
    public void load() throws Exception {
        while (true) {
            // server尝试接收其他Socket的连接请求,server的accept方法是阻塞式的
            Socket socket = this.accept();
           
            // 每接收到一个Socket就建立一个新的线程来处理它
            new Thread(new Task(socket)).start();
        }
    }
     //处理客户端传输过来的文件线程类
    class Task implements Runnable {
 
        private Socket socket;
        private DataInputStream dis;
        private DataOutputStream dos;
        private RandomAccessFile rad;
        private JFrame frame;    //用来显示进度条
        private Container contentPanel;
        private JProgressBar progressbar;
        private JLabel label;
            
        public Task(Socket socket) {
            frame = new JFrame("文件传输");
            this.socket = socket;
        }
 
        @Override
        public void run() {
            try {
                dis = new DataInputStream(socket.getInputStream());
                dos = new DataOutputStream(socket.getOutputStream());
                String targetPath = dis.readUTF();    //接收目标路径
                String fileName = dis.readUTF();    //接收文件名
                //System.out.println("服务器:接收文件名");
                long fileLength = dis.readLong();    //接收文件长度
                //System.out.println("服务器:接收文件长度");
                File directory = new File(targetPath);    //目标地址
                if(!directory.exists()) {    //目标地址文件夹不存在则创建该文件夹
                    directory.mkdir();
                }
                File file = new File(directory.getAbsolutePath() + File.separatorChar + fileName + ".temp");    //建立临时数据文件.temp
                //System.out.println("服务器:加载temp文件");
                rad = new RandomAccessFile(directory.getAbsolutePath() + File.separatorChar + fileName + ".temp", "rw");
                long size = 0;
                if(file.exists() && file.isFile()){    //如果目标路径存在且是文件,则获取文件大小
                    size = file.length();
                }
                //System.out.println("服务器:获的当前已接收长度");
                dos.writeLong(size);    //向客户端发送当前数据文件大小
                dos.flush();
                //System.out.println("服务器:发送当前以接收文件长度");
                int barSize = (int)(fileLength / 1024);    //进度条当前进度
                int barOffset = (int)(size / 1024);        //进度条总长
                frame.setSize(300,120); //传输界面
                contentPanel = frame.getContentPane();
                contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS));
                progressbar = new JProgressBar();    //进度条
                label = new JLabel(fileName + " 接收中");
                contentPanel.add(label);
                progressbar.setOrientation(JProgressBar.HORIZONTAL);    //进度条为水平
                progressbar.setMinimum(0);    //进度条最小值
                progressbar.setMaximum(barSize);    //进度条最大值
                progressbar.setValue(barOffset);    //进度条当前值
                progressbar.setStringPainted(true); //显示进度条信息
                progressbar.setPreferredSize(new Dimension(150, 20));    //进度条大小
                progressbar.setBorderPainted(true);    //为进度条绘制边框
                progressbar.setBackground(Color.pink);    //进度条颜色为骚粉
                JButton cancel = new JButton("取消");    //取消按钮
                JPanel barPanel = new JPanel();
                barPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
                barPanel.add(progressbar);
                barPanel.add(cancel);
                contentPanel.add(barPanel);
                cancel.addActionListener(new cancelActionListener());
                //为取消按钮注册监听器
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setVisible(true);
                rad.seek(size);    //移动文件指针
                //System.out.println("服务器:文件定位完成");
                int length;
                byte[] bytes=new byte[1024];
                while((length = dis.read(bytes, 0, bytes.length)) != -1){
                    rad.write(bytes,0, length);    //写入文件
                    progressbar.setValue(++barOffset);    //更新进度条(由于进度条每个单位代表大小为1kb,所以太小的文件就显示不出啦)
                }
                if (barOffset >= barSize) {    //传输完成后的重命名
                    if(rad != null)
                         rad.close();
                    if(!file.renameTo(new File(directory.getAbsolutePath() + File.separatorChar + fileName))) {
                        file.delete();
                        //防御性处理删除临时文件
                    }
                    //System.out.println("服务器:临时文件重命名完成");
                }        
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {    //关闭资源
                    if(rad != null)
                         rad.close();
                    if(dis != null)
                        dis.close();
                    if(dos != null)
                        dos.close();
                    frame.dispose();
                    socket.close();
                } catch (Exception e) {}
            }
        }
        class cancelActionListener implements ActionListener{    //取消按钮监听器
            public void actionPerformed(ActionEvent e){
                try {
                    //System.out.println("服务器:接收取消");
                    if(dis != null)
                        dis.close();
                    if(dos != null)
                        dos.close();
                    if(rad != null)
                        rad.close();
                    frame.dispose();
                    socket.close();
                    JOptionPane.showMessageDialog(frame, "已取消接收,连接关闭!", "提示:", JOptionPane.INFORMATION_MESSAGE);    
                    label.setText(" 取消接收,连接关闭");
                } catch (IOException e1) {
                    
                }
            }
        }
    }  
}

  客户端代码:

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.Socket; 

public class FileTransferClient extends Socket {
 
    private static final String SERVER_IP = "127.0.0.1"; // 服务端IP
    private static final int SERVER_PORT = 8899; // 服务端端口
    private Socket client;
    private DataOutputStream dos;
    private DataInputStream dis;  
    private RandomAccessFile rad;  

    public FileTransferClient() throws Exception {
        super(SERVER_IP, SERVER_PORT);
        this.client = this;
        //System.out.println("客户端:成功连接服务端");
    }
    
    public void sendFile(String filePath, String targetPath) throws Exception {
        try {
            File file = new File(filePath);
            
            if(file.exists()) {
                dos = new DataOutputStream(client.getOutputStream());     //发送信息 getOutputStream方法会返回一个java.io.OutputStream对象
                dis = new DataInputStream(client.getInputStream());    //接收远程对象发送来的信息  getInputStream方法会返回一个java.io.InputStream对象
                dos.writeUTF(targetPath); //发送目标路径
                dos.writeUTF(file.getName()); //发送文件名
                //System.out.println("客户端:发送文件名");
                rad = new RandomAccessFile(file.getPath(), "r");
                /*
                 * RandomAccessFile是Java输入输出流体系中功能最丰富的文件内容访问类,既可以读取文件内容,也可以向文件输出数据。
                 * 与普通的输入/输出流不同的是,RandomAccessFile支持跳到文件任意位置读写数据,RandomAccessFile对象包含一个记录指针,用以标识当前读写处的位置。
                 * 当程序创建一个新的RandomAccessFile对象时,该对象的文件记录指针对于文件头 r代表读取
                 */
                dos.flush();    //作用见下方介绍
                dos.writeLong(file.length()); //发送文件长度
                //System.out.println("客户端:发送文件长度");
                dos.flush();
                long size = dis.readLong();    //读取当前已发送文件长度
                //System.out.println("客户端:开始传输文件 ");
                int length = 0;
                byte[] bytes = new byte[1024];    //每1kb发送一次
                if (size < rad.length()) {
                    rad.seek(size);
                    //System.out.println("客户端:文件定位完成");
                    //移动文件指针
                    while((length = rad.read(bytes)) > 0){
                        dos.write(bytes, 0, length);                            
                        dos.flush();
                        //每1kb清空一次缓冲区
                        //为了避免每读入一个字节都写一次,java的输流有了缓冲区,读入数据时会首先将数据读入缓冲区,等缓冲区满后或执行flush或close时一次性进行写入操作
                    }
                }
                //System.out.println("客户端:文件传输成功 ");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {    //关闭资源
            if(dos != null)
                dos.close();
            if(dis != null)
                dis.close();
            if(rad != null)
                rad.close();
            client.close();
        }
        
    }
 
    class cancelActionListener implements ActionListener{    //关闭按钮监听器
        public void actionPerformed(ActionEvent e3){
            try {
                //System.out.println("客户端:文件传输取消");
                if(dis != null)
                    dis.close();
                if(dos != null)
                    dos.close();
                if(rad != null)
                    rad.close();
                client.close();
            } catch (IOException e1) {
                
            }
        }
    }   
}

  传输文件是一个耗时操作,若直接实例化客户端对服务器发送数据会造成UI假死的情况,直到文件传输完成后才会恢复,所以建议在实例化客户端时单独建立一个新线程。

  测试代码:

import javax.swing.JFrame;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.ActionEvent;

public class MainFrame extends JFrame{
    public MainFrame() {
        this.setSize(1280, 768);
        getContentPane().setLayout(null);
        
        JButton btnNewButton = new JButton("传输文件");    //点击按钮进行文件传输
        btnNewButton.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                // TODO 自动生成的方法存根
                super.mouseClicked(e);
                JFileChooser fileChooser = new JFileChooser();    //fileChooser用来选择要传输的文件
                fileChooser.setDialogTitle("选择要传输的文件");
                int stFile = fileChooser.showOpenDialog(null);
                if(stFile == fileChooser.APPROVE_OPTION){    //选择了文件
                    JFileChooser targetPathChooser = new JFileChooser();    //targetPathChooser用来选择目标路径
                    targetPathChooser.setDialogTitle("选择目标路径");
                    targetPathChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);    //只能选择路径
                    int stPath = targetPathChooser.showOpenDialog(null);
                    if(stPath == targetPathChooser.APPROVE_OPTION) {    //选择了路径
                        //新建一个线程实例化客户端
                        new Thread(new NewClient( fileChooser.getSelectedFile().getPath(), targetPathChooser.getSelectedFile().getPath())).start();
                    }
                }
            }
        });
        btnNewButton.setBounds(526, 264, 237, 126);
        getContentPane().add(btnNewButton);
    }
    class NewClient implements Runnable {    //用于实例化客户端的线程
        private String fileP;    //需复制文件路径
        private String targetP;    //目标路径
        public NewClient(String fileP, String targetP) {    //构造函数
            this.fileP = fileP;
            this.targetP = targetP;
        }
        @Override
        public void run() {
            // TODO 自动生成的方法存根
            try {
                @SuppressWarnings("resource")
                FileTransferClient ftc = new FileTransferClient();
                //实例化客户端
                ftc.sendFile(fileP, targetP);
            } catch (Exception e1) {
                // TODO 自动生成的 catch 块
                e1.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
        // TODO 自动生成的方法存根
        MainFrame mainFrame = new MainFrame();
        mainFrame.setVisible(true);
        mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        try {
             @SuppressWarnings("resource")
            FileTransferServer server = new FileTransferServer(); // 启动服务端
             server.load();
        } catch (Exception e) {
              e.printStackTrace();
        }
    }
}

  演示:

  1运行MainFame

  2点击传输文件

  3选择要传输的文件

  4选择目标路径

  5点击打开

  点击取消

  之后重复2 - 5的操作。

  啊我手速慢,问题不大,你会发现断点续传已经实现了。

转载于:https://www.cnblogs.com/suvvm/p/10839543.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Java实现Socket断点续传的代码可以使用JavaSocket类来实现,并可以使用InputStream和OutputStream类来读取和写入Socket的数据。此外,可以使用RandomAccessFile实现断点续传功能,具体实现代码如下: ``` InputStream in = socket.getInputStream(); OutputStream out = socket.getOutputStream(); RandomAccessFile raf = new RandomAccessFile(fileName, "rw"); // 设置文件偏移量 raf.seek(processedBytes); byte[] buf = new byte[1024]; int len = 0; while ((len = in.read(buf)) != -1) { out.write(buf, 0, len); raf.write(buf, 0, len); processedBytes += len; } raf.close(); in.close(); out.close(); socket.close(); ``` ### 回答2: 要实现Javasocket断点续传功能,可以按照以下步骤进行: 1. 建立服务器端和客户端的连接,并创建输入输出流。服务器端需要记录客户端上传文件的断点位置。 2. 客户端需要读取要上传的文件,然后将文件字节分割成多个小块。可以使用FileInputStream读取文件,然后使用BufferedInputStream增加缓冲区。 3. 客户端将各个小块的文件字节通过socket连接发送给服务器端。服务器端接收到数据后,根据客户端传输的块索引和字节信息,将每个小块的文件字节写入到服务器的目标文件中。可以使用FileOutputStream和BufferedOutputStream来写入文件。 4. 客户端在传输过程中如果中断,可以通过记录已传输的字节数或块索引位置等信息,下次继续从该位置开始传输。服务器端则需要根据记录的信息,在继续写入文件前进行校验和处理。 5. 客户端和服务器端之间可以通过一个特定的信号或命令来指示传输是否结束。当传输结束时,客户端可以关闭连接,服务器端也可以完成文件的接收。 需要注意的是,断点续传功能的实现还涉及到传输速度的控制、数据校验、异常处理等问题,具体实现时需要根据实际情况进行调整。同时,在多线程环境下实现断点续传功能可能会更复杂,需要考虑线程同步等问题。 综上所述,以上是一个简单Java实现socket断点续传功能的步骤。具体的代码实现可以根据需求和实际情况进行调整和扩展。 ### 回答3: Java实现Socket断点续传的代码如下所示: ```java import java.io.*; import java.net.ServerSocket; import java.net.Socket; public class FileServer { public static void main(String[] args) { try { ServerSocket serverSocket = new ServerSocket(9999); System.out.println("等待客户端连接..."); Socket socket = serverSocket.accept(); System.out.println("客户端连接成功!"); DataInputStream dis = new DataInputStream(socket.getInputStream()); String fileName = dis.readUTF(); long fileLength = dis.readLong(); File directory = new File("D:/temp"); if (!directory.exists()) { directory.mkdir(); } File file = new File(directory.getAbsolutePath() + File.separatorChar + fileName); FileOutputStream fos = new FileOutputStream(file); byte[] bytes = new byte[1024]; int length; long received = 0; while ((length = dis.read(bytes, 0, bytes.length)) != -1) { fos.write(bytes, 0, length); fos.flush(); received += length; System.out.println("已接收:" + received + "/" + fileLength); } System.out.println("文件接收完成!"); fos.close(); dis.close(); socket.close(); serverSocket.close(); } catch (IOException e) { e.printStackTrace(); } } } ``` ```java import java.io.*; import java.net.Socket; public class FileClient { public static void main(String[] args) { try { Socket socket = new Socket("localhost", 9999); File file = new File("D:/temp/test.txt"); DataOutputStream dos = new DataOutputStream(socket.getOutputStream()); dos.writeUTF(file.getName()); dos.flush(); dos.writeLong(file.length()); dos.flush(); FileInputStream fis = new FileInputStream(file); byte[] bytes = new byte[1024]; int length; long sent = 0; while ((length = fis.read(bytes, 0, bytes.length)) != -1) { dos.write(bytes, 0, length); dos.flush(); sent += length; System.out.println("已发送:" + sent + "/" + file.length()); } System.out.println("文件发送完成!"); dos.close(); fis.close(); socket.close(); } catch (IOException e) { e.printStackTrace(); } } } ``` 上述代码实现了一个简单Socket断点续传功能。FileServer是服务器端的代码,FileClient是客户端的代码。服务器端负责监听客户端的连接,接收文件并保存到指定目录。客户端读取指定文件,并将文件发送至服务器端。通过读取和写入字节流的方式实现文件传输。在传输过程中,打印已接收和已发送的字节数,以及文件总大小,以便显示进度信息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值