您好,登錄后才能下訂單哦!
這是一個簡單的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的操作。
你會發現斷點續傳已經實現了
本文的重點是你有沒有收獲與成長,其余的都不重要,希望讀者們能謹記這一點。同時我經過多年的收藏目前也算收集到了一套完整的學習資料,包括但不限于:分布式架構、高可擴展、高性能、高并發、Jvm性能調優、Spring,MyBatis,Nginx源碼分析,Redis,ActiveMQ、、Mycat、Netty、Kafka、Mysql、Zookeeper、Tomcat、Docker、Dubbo、Nginx等多個知識點高級進階干貨,希望對想成為架構師的朋友有一定的參考和幫助
需要更詳細思維導圖和以下資料的可以加一下技術交流分享群:“708 701 457”免費獲取
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。