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的操作。

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

Java断点续传(基于socket与RandomAccessFile的简单实现)的更多相关文章

  1. java基础之Socket编程概述以及简单案例

    概述: 用来实现网络互连的 不同的计算机上 运行的程序间 可以进行数据交互  也就是用来在不同的电脑间, 进行数据传输. 三大要素: IP地址: 设备(电脑,手机,ipad)在网络中的唯一标识. 组成 ...

  2. C#基于Socket的简单聊天室实践

    序:实现一个基于Socket的简易的聊天室,实现的思路如下: 程序的结构:多个客户端+一个服务端,客户端都是向服务端发送消息,然后服务端转发给所有的客户端,这样形成一个简单的聊天室功能. 实现的细节: ...

  3. Java基于Socket文件传输示例(转)

    最近需要进行网络传输大文件,于是对基于socket的文件传输作了一个初步的了解.在一位网友提供的程序基础上,俺进行了一些加工,采用了缓冲输入/输出流来包装输出流,再采用数据输入/输出输出流进行包装,加 ...

  4. Java基于Socket文件传输示例

    http://www.blogjava.net/sterning/archive/2007/10/13/152508.html 最近需要进行网络传输大文件,于是对基于socket的文件传输作了一个初步 ...

  5. java网络编程socket解析

    转载:http://www.blogjava.net/landon/archive/2013/07/02/401137.html Java网络编程精解笔记2:Socket详解 Socket用法详解 在 ...

  6. 读懂Java中的Socket编程

    Socket,又称为套接字,Socket是计算机网络通信的基本的技术之一.如今大多数基于网络的软件,如浏览器,即时通讯工具甚至是P2P下载都是基于Socket实现的.本文会介绍一下基于TCP/IP的S ...

  7. 读懂Java中的Socket编程(转)

    Socket,又称为套接字,Socket是计算机网络通信的基本的技术之一.如今大多数基于网络的软件,如浏览器,即时通讯工具甚至是P2P下载都是基于Socket实现的.本文会介绍一下基于TCP/IP的S ...

  8. c#编写的基于Socket的异步通信系统

    c#编写的基于Socket的异步通信系统 SanNiuSignal是一个基于异步socket的完全免费DLL:它里面封装了Client,Server以及UDP:有了这个DLL:用户不用去关心心跳:粘包 ...

  9. java中的socket编程

    Socket,又称为套接字,Socket是计算机网络通信的基本的技术之一.如今大多数基于网络的软件,如浏览器,即时通讯工具甚至是P2P下载都是基于Socket实现的.本文会介绍一下基于TCP/IP的S ...

随机推荐

  1. springboot如何使用外部tomcat容器

    一般情况spring-boot-starter-web是自带tomcat(即springboot内嵌tomcat),所以打包直接生成jar包,用java -jar命令就可以启动. 但,有时我们希望用w ...

  2. Linux 内核源码中likely()和unlikely()【转】

    本文转载自:http://blog.csdn.net/tigerjibo/article/details/8279183 ikely()与unlikely()在2.6内核中,随处可见,那为什么要用它们 ...

  3. python中的编码和解码

    计算机中常见的编码方式有多种,英文一般是ascii编码,其他有unicode,utf-8,gbk,utf-16等编码. 常见编码方式: ASCII编码:ASCII是早期的编码,包含英文字母.数字和 ...

  4. 安装YCM出现:YouCompleteMe unavailable no module named frozendict或者 YouCompleteMe unavailable no module named future

    参考博文:http://blog.sina.com.cn/s/blog_8f70642d0102wo57.html 原因就是你或者没用Vundle安装,或者Vundle由于网速太慢下载到一半不能把安装 ...

  5. CF_576D_Flights for Regular Customers_矩阵乘法+倍增floyd+bitset+bfs

    CF_576D_Flights for Regular Customers_矩阵乘法+倍增floyd+bitset https://www.luogu.org/problemnew/show/CF57 ...

  6. Spss22安装与破解教程

    Spss22安装与破解教程 Spss22安装与破解教程 1.下载安装包 可以去IBM官网.人大论坛等网站下载,全部文件应包括spss22安装包(含32位及64位)和破解文件,这里提供一个64位的百度网 ...

  7. Educational Codeforces Round 24 CF 818 A-G 补题

    6月快要结束了 期末也过去大半了 马上就是大三狗了 取消了小学期后20周的学期真心长, 看着各种北方的学校都放假嗨皮了,我们这个在北回归线的学校,还在忍受酷暑. 过年的时候下定决心要拿块ACM的牌子, ...

  8. 6-13 Hog特征1

    Hog特征与Haar特征有点不同,Hog特征是直接经过模板计算得到的

  9. 记一次OutOfMemory定位过程

    背景 最近有个项目部署到了AWS,部署方案是ECS+Docker+Java Launch type CPU Units Memory FARGATE 1024 4G 运行后发现程序表现不符合预期--每 ...

  10. bzoj2064: 分裂(状压dp)

    Description 背景: 和久必分,分久必和... 题目描述: 中国历史上上分分和和次数非常多..通读中国历史的WJMZBMR表示毫无压力. 同时经常搞OI的他把这个变成了一个数学模型. 假设中 ...