您好,登錄后才能下訂單哦!
這是一個簡單的C/S架構(gòu),基本實現(xiàn)思路是將服務(wù)器注冊至某個空閑端口用來監(jiān)視并處理每個客戶端的傳輸請求。
客戶端先獲得用戶給予的需傳輸文件與目標(biāo)路徑,之后根據(jù)該文件實例化RandomAccessFile為只讀,之后客戶端向服務(wù)器發(fā)送需傳輸?shù)奈募募笮∨c目標(biāo)路徑,服務(wù)器沒接收到一個客戶端的請求就會建立一個新的線程去處理它,根據(jù)接收到的文件名到目標(biāo)路徑中去尋找目標(biāo)路徑中是否已經(jīng)有該文件名的.temp臨時文件(如果沒有就創(chuàng)建它),之后服務(wù)器會將文件已經(jīng)傳輸?shù)拇笮。ㄅR時文件大小)返回給客戶端(例如臨時文件剛剛建立返回的便是0),客戶端會將剛剛建立的RandomAccessFile對象的文件指針指向服務(wù)器返回的位置,之后以1kb為一組向服務(wù)器傳輸需傳輸文件的內(nèi)容數(shù)據(jù),服務(wù)器則接收數(shù)據(jù)并將其寫入臨時文件中,并根據(jù)現(xiàn)有數(shù)據(jù)畫出進度條。在文件傳輸完畢后客戶端會將臨時文件重命名為最初接收到的文件名。
服務(wù)器代碼:
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; // 服務(wù)端端口
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(); //接收目標(biāo)路徑
String fileName = dis.readUTF(); //接收文件名
//System.out.println("服務(wù)器:接收文件名");
long fileLength = dis.readLong(); //接收文件長度
//System.out.println("服務(wù)器:接收文件長度");
File directory = new File(targetPath); //目標(biāo)地址
if(!directory.exists()) { //目標(biāo)地址文件夾不存在則創(chuàng)建該文件夾
directory.mkdir();
}
File file = new File(directory.getAbsolutePath() + File.separatorChar + fileName + ".temp"); //建立臨時數(shù)據(jù)文件.temp
//System.out.println("服務(wù)器:加載temp文件");
rad = new RandomAccessFile(directory.getAbsolutePath() + File.separatorChar + fileName + ".temp", "rw");
long size = 0;
if(file.exists() && file.isFile()){ //如果目標(biāo)路徑存在且是文件,則獲取文件大小
size = file.length();
}
//System.out.println("服務(wù)器:獲的當(dāng)前已接收長度");
dos.writeLong(size); //向客戶端發(fā)送當(dāng)前數(shù)據(jù)文件大小
dos.flush();
//System.out.println("服務(wù)器:發(fā)送當(dāng)前以接收文件長度");
int barSize = (int)(fileLength / 1024); //進度條當(dāng)前進度
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); //進度條當(dāng)前值
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());
//為取消按鈕注冊監(jiān)聽器
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
rad.seek(size); //移動文件指針
//System.out.println("服務(wù)器:文件定位完成");
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("服務(wù)器:臨時文件重命名完成");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try { //關(guān)閉資源
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{ //取消按鈕監(jiān)聽器
public void actionPerformed(ActionEvent e){
try {
//System.out.println("服務(wù)器:接收取消");
if(dis != null)
dis.close();
if(dos != null)
dos.close();
if(rad != null)
rad.close();
frame.dispose();
socket.close();
JOptionPane.showMessageDialog(frame, "已取消接收,連接關(guān)閉!", "提示:", JOptionPane.INFORMATION_MESSAGE);
label.setText(" 取消接收,連接關(guān)閉");
} 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"; // 服務(wù)端IP
private static final int SERVER_PORT = 8899; // 服務(wù)端端口
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("客戶端:成功連接服務(wù)端");
}
public void sendFile(String filePath, String targetPath) throws Exception {
try {
File file = new File(filePath);
if(file.exists()) {
dos = new DataOutputStream(client.getOutputStream()); //發(fā)送信息 getOutputStream方法會返回一個java.io.OutputStream對象
dis = new DataInputStream(client.getInputStream()); //接收遠程對象發(fā)送來的信息 getInputStream方法會返回一個java.io.InputStream對象
dos.writeUTF(targetPath); //發(fā)送目標(biāo)路徑
dos.writeUTF(file.getName()); //發(fā)送文件名
//System.out.println("客戶端:發(fā)送文件名");
rad = new RandomAccessFile(file.getPath(), "r");
/*
* RandomAccessFile是Java輸入輸出流體系中功能最豐富的文件內(nèi)容訪問類,既可以讀取文件內(nèi)容,也可以向文件輸出數(shù)據(jù)。
* 與普通的輸入/輸出流不同的是,RandomAccessFile支持跳到文件任意位置讀寫數(shù)據(jù),RandomAccessFile對象包含一個記錄指針,用以標(biāo)識當(dāng)前讀寫處的位置。
* 當(dāng)程序創(chuàng)建一個新的RandomAccessFile對象時,該對象的文件記錄指針對于文件頭 r代表讀取
*/
dos.flush(); //作用見下方介紹
dos.writeLong(file.length()); //發(fā)送文件長度
//System.out.println("客戶端:發(fā)送文件長度");
dos.flush();
long size = dis.readLong(); //讀取當(dāng)前已發(fā)送文件長度
//System.out.println("客戶端:開始傳輸文件 ");
int length = 0;
byte[] bytes = new byte[1024]; //每1kb發(fā)送一次
if (size < rad.length()) {
rad.seek(size);
//System.out.println("客戶端:文件定位完成");
//移動文件指針
while((length = rad.read(bytes)) > 0){
dos.write(bytes, 0, length);
dos.flush();
//每1kb清空一次緩沖區(qū)
//為了避免每讀入一個字節(jié)都寫一次,java的輸流有了緩沖區(qū),讀入數(shù)據(jù)時會首先將數(shù)據(jù)讀入緩沖區(qū),等緩沖區(qū)滿后或執(zhí)行flush或close時一次性進行寫入操作
}
}
//System.out.println("客戶端:文件傳輸成功 ");
}
} catch (Exception e) {
e.printStackTrace();
} finally { //關(guān)閉資源
if(dos != null)
dos.close();
if(dis != null)
dis.close();
if(rad != null)
rad.close();
client.close();
}
}
class cancelActionListener implements ActionListener{ //關(guān)閉按鈕監(jiān)聽器
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) {
}
}
}
}
傳輸文件是一個耗時操作,若直接實例化客戶端對服務(wù)器發(fā)送數(shù)據(jù)會造成UI假死的情況,直到文件傳輸完成后才會恢復(fù),所以建議在實例化客戶端時單獨建立一個新線程。
測試代碼:
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用來選擇要傳輸?shù)奈募? fileChooser.setDialogTitle("選擇要傳輸?shù)奈募?);
int stFile = fileChooser.showOpenDialog(null);
if(stFile == fileChooser.APPROVE_OPTION){ //選擇了文件
JFileChooser targetPathChooser = new JFileChooser(); //targetPathChooser用來選擇目標(biāo)路徑
targetPathChooser.setDialogTitle("選擇目標(biāo)路徑");
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; //需復(fù)制文件路徑
private String targetP; //目標(biāo)路徑
public NewClient(String fileP, String targetP) { //構(gòu)造函數(shù)
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(); // 啟動服務(wù)端
server.load();
} catch (Exception e) {
e.printStackTrace();
}
}
}
演示:
1運行MainFame
2點擊傳輸文件
3選擇要傳輸?shù)奈募?/strong>
4選擇目標(biāo)路徑
5點擊打開
點擊取消
之后重復(fù)2 - 5的操作。
你會發(fā)現(xiàn)斷點續(xù)傳已經(jīng)實現(xiàn)了
本文的重點是你有沒有收獲與成長,其余的都不重要,希望讀者們能謹記這一點。同時我經(jīng)過多年的收藏目前也算收集到了一套完整的學(xué)習(xí)資料,包括但不限于:分布式架構(gòu)、高可擴展、高性能、高并發(fā)、Jvm性能調(diào)優(yōu)、Spring,MyBatis,Nginx源碼分析,Redis,ActiveMQ、、Mycat、Netty、Kafka、Mysql、Zookeeper、Tomcat、Docker、Dubbo、Nginx等多個知識點高級進階干貨,希望對想成為架構(gòu)師的朋友有一定的參考和幫助
需要更詳細思維導(dǎo)圖和以下資料的可以加一下技術(shù)交流分享群:“708 701 457”免費獲取
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。