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. javascript if(xx)

    js判断某个值知否存在或者为空,可以直接用if(xx)过滤. <!DOCTYPE html> <html> <head> <meta charset=&quo ...

  2. 内核添加dts后,device和device_driver的match匹配的变动:通过compatible属性进行匹配【转】

    本文转载自:http://blog.csdn.net/ruanjianruanjianruan/article/details/61622053 内核添加dts后,device和device_driv ...

  3. 缓存框架Ehcache相关

    单点缓存框架   只能针对单个jvm中,缓存容器存放jvm中,每个缓存互不影响  Ehcache gauva chache 内置缓存框架 jvm缓存框架 分布式缓存框架(共享缓存数据)  Redis ...

  4. js实现域名判断后跳转到指定网址

    js实现域名判断后跳转到指定网址,也适用于同一虚拟空间放多个网站: <script>       try           {               if(self.locatio ...

  5. linux初级学习笔记一:linux操作系统及常用命令,及如何获取命令的使用帮助!(视频序号:02_1,2)

    本节学习的命令:ls,cd,type,pwd, printenv, hash, date, clock, man, hwclock, info, cal, echo, printf, file! 本节 ...

  6. codeforces 441B. Valera and Fruits 解题报告

    题目链接:http://codeforces.com/problemset/problem/441/B 题目意思:有 n 棵fruit trees,每课水果树有两个参数描述:水果成熟的时间和这棵树上水 ...

  7. 如何使用BMap.Point传递变量、存储数据?

    在开发中使用到了百度地图进行开发,用于展示企业位置.由于数据量庞大,如果使用marker,将会造成界面卡顿,处理慢的问题. 在查看百度地图API示例是发现了海量点这个东西,还别说对于大数量的点加载起来 ...

  8. C/C++ 编译器优化

    0. gcc -o gcc -o 的优化仍然是机械的,想当然的.只有做到深入理解计算机系统,加深对编程语言的理解,才能写出最优化的代码. Linux下gcc 优化等级的介绍 gcc -o0 ⇒ 不提供 ...

  9. django上课笔记5-FK关联

    一.FK关联 FK关联 url.py里 from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r' ...

  10. 详细讲解:零知识证明 之 ZCash 完整的匿名交易流程

    作者:林冠宏 / 指尖下的幽灵 博客:http://www.cnblogs.com/linguanh/ 掘金:https://juejin.im/user/587f0dfe128fe100570ce2 ...