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;}@Overridepublic 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"; // 服务端IPprivate 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() {@Overridepublic 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;}@Overridepublic 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的操作。
你会发现断点续传已经实现了
本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。同时我经过多年的收藏目前也算收集到了一套完整的学习资料,包括但不限于:分布式架构、高可扩展、高性能、高并发、Jvm性能调优、Spring,MyBatis,Nginx源码分析,Redis,ActiveMQ、、Mycat、Netty、Kafka、Mysql、Zookeeper、Tomcat、Docker、Dubbo、Nginx等多个知识点高级进阶干货,希望对想成为架构师的朋友有一定的参考和帮助
需要更详细思维导图和以下资料的可以加一下技术交流分享群:“708 701 457”免费获取
转载于:https://blog.51cto.com/14230003/2407115
相关文章:

EJB调用原理分析
EJB调用原理分析 作者:robbin (MSN:robbin_fan AT hotmail DOT com) 版权声明:本文严禁转载,如有转载请求,请和作者联系 一个远程对象至少要包括4个class文件:远程对象;远程对象的接口;实现远程…

Jfinal Generator 不需要生成带某个前缀的表名数组的方法
2019独角兽企业重金招聘Python工程师标准>>> package com.demo.common.model; import javax.sql.DataSource; import com.jfinal.kit.PathKit; import com.jfinal.kit.Prop; import com.jfinal.kit.PropKit; import com.jfinal.plugin.activerecord.generato…

tensorflow 2
import tensorflow as tf import numpy as npdef test1():#create datax_datanp.random.rand(100).astype(np.float32)y_datax_data*0.10.3#create tensorflow structureWeightstf.Variable(tf.random_uniform([1],-1.0,1.0)) #一维,范围[-1,1]biasestf.Variable(tf…

PCB多层线路板打样难点
PCB多层板无论从设计上还是制造上来说,都比单双层板要复杂,一不小心就会遇到一些问题,那在PCB多层线路板打样中我们要规避哪些难点呢? 1、层间对准的难点 由于多层电路板中层数众多,用户对PCB层的校准要求越来越…

GARFIELD@11-07-2004
Vanity Fair转载于:https://www.cnblogs.com/rexhost/archive/2004/11/07/61286.html

python文件读写1
# -*- coding: utf-8 -*-# read txt file def readTextFile(file):f open(file, r)# 尽可能多的读取文件的内容,一般会将整个文件内容都会读取context f.read() print(context)f.close()def readTextFileByLines(file):f open(file, "r")lines f.read…

jfinal框架下使用c3P0连接池连接sql server 2008
2019独角兽企业重金招聘Python工程师标准>>> 闲话少说 进入正题 首先是工程需要的jar包 然后是c3p0的配置文件。我是这样配置的 仅供参考 jdbcDriver com.microsoft.sqlserver.jdbc.SQLServerDriver jdbcUrl jdbc:sqlserver://localhost:7777;databaseNametest us…

mongodb插入文档时不传ObjectId
type BookExt struct {ID bson.ObjectId bson:"_id"Title string bson:"title"SubTitle string bson:"subTitle"Author string bson:"author" } 以上结构体,在通过此结构体对象作为参数传入Insert插入…

[问题]DotNet 项目如何实现在构建时 Build 号自动增加?
[问题]DotNet 项目如何实现在构建时 Build 号自动增加? 继续昨天的问题,今天在Google上找了一下,没有找到很好的方案。目前找到的解决方案有以下几种:1.使用一个地三方的 VS.Net 插件,实现在编译时 Build 号自动增加&a…

编写程序记录文件位置
当我们编写程序是会注意到,首先是配置一些函数的结构体。 所以我们就要找到下面的界面,然后打开FWLB中.c文件下面所对应的.h文件,这样就能查找到相应的结构体。下图为我所找到的中断的结构体、 然后就是查找相对应的中断向量。具体就是打开 还…

mnist数据集保存为图片
#coding: utf-8 from tensorflow.examples.tutorials.mnist import input_data import scipy.misc import os import numpy as np# 读取MNIST数据集。如果不存在会事先下载。 mnist input_data.read_data_sets("MNIST_data/", one_hotTrue)# 我们把原始图片保存在MN…

Python3数据分析与挖掘建模实战
<div>课程地址:http://icourse8.com/Python3_shujufenxi.html</div>复制代码第1章 课程介绍【赠送相关电子书随堂代码】 第2章 数据获取 第3章 单因子探索分析与数据可视化 第4章 多因子探索分析 第5章 预处理理论 第6章 挖掘建模 第7章 模型评估 第8章…

tensorflow生成对抗网络
import tensorflow as tf import numpy as np import os from tensorflow.examples.tutorials.mnist import input_data from matplotlib import pyplot as pltBATCH_SIZE 64 UNITS_SIZE 128 LEARNING_RATE 0.001 EPOCH 300 SMOOTH 0.1print("mnist手写体生成对抗网络…

博客园今天早上是不是出现什么问题了?
下面是我进我的blog后台管理和浏览博客园给出的提示。大约几分钟后恢复正常。转载于:https://www.cnblogs.com/freeyzh/archive/2004/12/01/71269.html

模态框获取id一直不变,都是同一个id值
2019独角兽企业重金招聘Python工程师标准>>> $(.refund-btn).click(function(){//此处必须是$(this),否则$(.refund-btn)重新获取,导致值一直不变var id $(this).attr(data-id);//var id $(.refund-btn).attr(data-id);错误,这样会导致一直…

标准功能模块组件 -- 内部联络单组件,内部邮件组件,提高多人异地协同办公效率...
为什么80%的码农都做不了架构师?>>> 未必什么功能都需要自己开发,我们不会自己开发一个数据库系统,也不会自己开发一个操作系统,同样我们每个功能模块都未必需要自己开发,自己开发最核心的模块,…

Microsoft patterns practices Enterprise Library released
一直关注这个东西,本来订阅了RSS,没想到GotDotNet上面的发布信息给清空了。 上周末发布的,今天才看到,刚刚下载了一个,下载还要求注册,真麻烦,现把地址共享,方便大家。 http://down…

图论之拓扑排序 poj 2367 Genealogical tree
题目链接 http://poj.org/problem?id2367 题意就是给定一系列关系,按这些关系拓扑排序。 #include<cstdio> #include<cstring> #include<queue> #include<vector> #include<algorithm> using namespace std; const int maxn200; int…
算法基础知识科普:8大搜索算法之顺序搜索
基本概念和术语 搜索表(Search Table):是由同一类型的数据元素(或记录)构成的集合。 关键字(Key):是数据元素中某个数据项的值,用它可以标识一个数据元素。若此关键字…

foj2024
为什么80%的码农都做不了架构师?>>> http://acm.fzu.edu.cn/problem.php?pid2024 View Code #include < stdio.h > #include < string .h > #define M 1010 int c[M][M]; int f[M][M]; int min( int a, int b, int c){ int z …

4701年新年快乐!
中华民族传统历法夏历(农历)采用的是干支纪年法,是世界上最古老的历法之一。干支即“六十甲子”,以60年为一循环。它的纪元开始相传可追溯到黄帝轩辕氏时代,按公元计算,第一个“甲子年”应是在公元前2697年…

Win10系列:JavaScript访问文件和文件夹
在实际开发中经常会遇到访问文件的情况,因此学习与文件有关的操作对程序开发很有帮助,关于文件操作的一些基本技术,在前面章节中有专门基于C#语言的详细讲解,本节主要介绍如何使用HTML5和JavaScript开发具有文件操作功能的Windows…
算法基础知识科普:8大搜索算法之二分搜索
昨天介绍了对无序搜素表的顺序搜索方法,今天介绍对有序搜索表的二分搜索方法,“二分”在算法设计中是非常常用的一种思想,除了处理如下普通的搜索外,还用于搜索方程的解等工程领域。但二分法仍然有缺陷,待后面慢慢介绍…

linux之shell脚本学习篇一
为什么80%的码农都做不了架构师?>>> 此文包含脚本服务请求,字符串截取,文件读写内容,打印内容换行。 #!/bin/bash retMsg""; while read LINE do echo "this is text: $LINE"; retMsg/usr/bin/cu…

Win10系列:JavaScript动画2
"重新定位"动画也是Windows动画库中的动画效果。"重新定位"动画的动画效果是指一个或一组元素移动到新的位置时,这些元素不是突然出现在新的位置,而是从一个位置移动到另一个位置。 创建"重新定位"动画可以使用WinJS.UI.A…

[转载]李开复先生给中国学生的第四封信:大学四年应是这样度过
今天,我回复了“开复学生网”开通以来的第1000个问题。关掉电脑后,始终有一封学生来信萦绕在我的脑海里,挥之不去: 开复老师: 就要毕业了。 回头看自己所谓的大学生活, 我想哭,不是因为离别&…
算法基础知识科普:8大搜索算法之插补搜索
二分法的不足在于,对于均匀分布的数据,缩小搜索范围的速度太慢,每次只能缩小原长度的1/2,我们希望缩小范围尽可能的快,即搜索的数据若离左端点近,搜索的区间尽量的靠近左端点,同理搜索的数据若离…

hdu(1596)
为什么80%的码农都做不了架构师?>>> dijkstra 1 #include " iostream " 2 using namespace std; 3 double map[ 1010 ][ 1010 ]; 4 int visit[ 1010 ]; 5 double used[ 1010 ]; 6 int k; 7 double _max 0 ; 8 int i…

使用ADO.NET 的最佳实践(zz)
数据访问:使用 ADO.NET 的最佳实践(ADO.NET 技术文档) 发布日期: 4/1/2004| 更新日期: 4/1/2004 摘要:编写 Microsoft ADO.NET 代码的最佳实践,以及对使用 ADO.NET 中可用对象的开发人员的建议。…
算法基础知识科普:8大搜索算法之二叉搜索树(上)
前几天,我们介绍了在顺序存储结构上构建的搜索算法,如二分搜素,插补搜索等,这种结构适合于静态搜索,但对于动态搜索会涉及到大量记录的移动导致效率的降低。这样我们自然会想是否能够利用链式的存储结构,这…