Java斷點續(xù)傳(基于socket與RandomAccessFile的簡單實現(xiàn))
這是一個簡單的C/S架構(gòu),基本實現(xiàn)思路是將服務(wù)器注冊至某個空閑端口用來監(jiān)視并處理每個客戶端的傳輸請求。
客戶端先獲得用戶給予的需傳輸文件與目標路徑,之后根據(jù)該文件實例化RandomAccessFile為只讀,之后客戶端向服務(wù)器發(fā)送需傳輸?shù)?strong style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">文件名文件大小與目標路徑,服務(wù)器沒接收到一個客戶端的請求就會建立一個新的線程去處理它,根據(jù)接收到的文件名到目標路徑中去尋找目標路徑中是否已經(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(); //接收目標路徑 String fileName = dis.readUTF(); //接收文件名 //System.out.println("服務(wù)器:接收文件名"); long fileLength = dis.readLong(); //接收文件長度 //System.out.println("服務(wù)器:接收文件長度"); File directory = new File(targetPath); //目標地址 if(!directory.exists()) { //目標地址文件夾不存在則創(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()){ //如果目標路徑存在且是文件,則獲取文件大小 size = file.length(); } //System.out.println("服務(wù)器:獲的當前已接收長度"); dos.writeLong(size); //向客戶端發(fā)送當前數(shù)據(jù)文件大小 dos.flush(); //System.out.println("服務(wù)器:發(fā)送當前以接收文件長度"); 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()); //為取消按鈕注冊監(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ā)送目標路徑 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對象包含一個記錄指針,用以標識當前讀寫處的位置。 * 當程序創(chuàng)建一個新的RandomAccessFile對象時,該對象的文件記錄指針對于文件頭 r代表讀取 */ dos.flush(); //作用見下方介紹 dos.writeLong(file.length()); //發(fā)送文件長度 //System.out.println("客戶端:發(fā)送文件長度"); dos.flush(); long size = dis.readLong(); //讀取當前已發(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假死的情況,直到文件傳輸完成后才會恢復,所以建議在實例化客戶端時單獨建立一個新線程。
測試代碼:
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ù)奈募?/span> fileChooser.setDialogTitle("選擇要傳輸?shù)奈募?); 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) { //構(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ù)奈募?/p>

4選擇目標路徑

5點擊打開

點擊取消
之后重復2 - 5的操作。

啊我手速慢,問題不大,你會發(fā)現(xiàn)斷點續(xù)傳已經(jīng)實現(xiàn)了。


