天天看點

一個簡單的檔案分享工具

### 一個簡單的檔案分享

GitHub 位址:https://github.com/SmythAsen/ShareFilesSystem

在過去的一個星期,我在做一個基于TCP/IP傳輸層協定的檔案分享工具,這個工具的主要功能就是類似于将要分享檔案的分享端作為Ftp檔案伺服器,而下載下傳檔案方則為用戶端。而在此過程中,服務端需要

說明:編寫此程式目的在于學習TCP/IP傳輸協定相關知識,http協定及ftp協定的底層實作原理。并未打算應用此程式,是以沒有說明界面設計,主要注重功能的實作,同時寫本文也是為了記錄和總結這學習的過程,更是為了鍛煉自己的寫作能力,歡迎大家指出我的不足與錯誤。也歡迎大家來github提出你們的想法。

一、目前進度

  由于比較忙,項目的整體進度比較慢,一個星期下來隻實作了用戶端的基本功能,是以這一次記錄也主要記錄用戶端的工作以及原理。

二、工具主要設計思想

一個簡單的檔案分享工具

- 服務端:

 1.服務端需要共享一個檔案夾,并将檔案清單發送給每一個連接配接上來的用戶端。

 2.需要接受到用戶端發送過來的下載下傳檔案的指令

 3.根據檔案下載下傳指令向用戶端傳輸指定的檔案以及檔案的大小。

- 用戶端:

 1.用戶端需要輸入服務端ip以及端口号來與其進行連接配接。

 2.接受用戶端發送過來的檔案清單

 3.将輸入的檔案下載下傳指令發送給服務端

 4.接收服務端傳送過來的檔案并儲存到到指定目錄

三、用戶端實作原理

  • 首先用戶端需要連接配接上伺服器,非常簡單。
  //連接配接到伺服器
    public void connect() throws UnknownHostException, IOException{
                socket = new Socket(ip, port);
                isconnect = true;
    }
           

注意,這裡的ip和端口都是來自用戶端界面的。 同時我們需要将錯誤抛給調用他的界面,一點出現任何問題讓界面進行處理。

  • 其次用戶端需要接受來自服務端的檔案清單
//擷取從伺服器傳輸過來的檔案名
    @SuppressWarnings("unchecked")
    public String getFilesName() {
        String filesName = "";
        if(isconnect){
            ObjectInputStream ois = null;
            try {
                ois = new ObjectInputStream(socket.getInputStream());
                //從伺服器取得檔案清單
                files = (TreeMap<Integer, String>) ois.readObject();
                Set<Integer> ids = files.keySet();
                for (Object id : ids) {
                    String s = id + ":" +files.get(id)+"\n";
                    filesName += s;
                }
            } catch (IOException | ClassNotFoundException e) {
                e.printStackTrace();
            }
            return filesName;
        }
        return "未接收到内容";
    }
           

這裡需要注意的是服務端我是用TreeMap将檔案名以及對應的指令傳輸過來的。是以需要使用ObjectInputStream來接收檔案清單并将接收類型強轉為TreeMap類型。最後将接收的檔案id和檔案名拼成一個字元串傳回個調用此方法的用戶端界面。

  • 接下來,就是将界面輸入的需要下載下傳的檔案指令發送給服務端了。
/**
     * 檢驗用戶端界面發過來的指令是否正确,  
     如果正确則向伺服器發送下載下傳指令并向用戶端傳回tru,  
     如果不正确則向客服的界面傳回false
     * @param id
     * @param dir
     * @return
     */
    public boolean sendComm(int id, String dir) {
        //判斷界面傳過來的指令是否存在
        boolean isCommExist = false;
        for (int i  : files.keySet()) {
            if(i == id){
                isCommExist = true;
            }
        }
        if(isCommExist){
            PrintStream ps;
            try {
                //發送下載下傳指令
                ps = new PrintStream(socket.getOutputStream());
                ps.println(String.valueOf(id));
                //設定下載下傳檔案
                this.dir = dir;
                this.filename = files.get(id);
            } catch (IOException e) {
                return false;
            }
            return true;
        }
        return false;
    }
           

在這裡我們首先要檢驗界面要我們發送的指令是否在,如果不存在則要求重新輸入,如果存在則向服務端發送該指令,并向界面給的路徑設定為我們即将儲存檔案的路徑。

  • 最後就是下載下傳檔案了
//下載下傳檔案
    private void download(String dir,String filename) throws IOException {
        BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File(dir,filename)));
        ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
        //取得所下載下傳檔案大小
        fileSize = ois.readLong();
        System.out.println("cc:檔案下載下傳的大小為"+fileSize);
        byte[] b = new byte[];
        int len = ;
        while((len = bis.read(b)) != -){
            bos.write(b, , len);
            currentFileSize += len;
        }
        bos.close();
    }
           

這裡,我們需要分别準備服務端的輸入流和用戶端的輸出流,在接收檔案的同時将其儲存到指定的路徑下。當然為了在用戶端試試顯示下載下傳的情況,這裡也需要先獲得服務端傳輸過來的檔案大小并将其傳回給界面,并将目前下載下傳進度傳回給界面。

#### 四、用戶端判斷邏輯

- 連接配接判斷邏輯

// 檢驗輸入資訊
            if (!tf_ip.getText().trim().equals("") && !tf_port.getText().trim().equals("")) { // 保證輸入框有内容
                if (tf_port.getText().trim().matches("^\\d*$")) {// 判斷端口輸入框内容是否為數字
                    ip = tf_ip.getText().trim(); // 将輸入的ip位址傳輸到處理程式
                    port = Integer.parseInt(tf_port.getText().trim());// 将輸入的接口傳入到處理程式
                        try {
                            // 連接配接伺服器
                            cc = new ClientCore(ip, port);
                            cc.connect();
                            isconnected = true;
                        } catch (UnknownHostException e) {
                            isconnected = false;
                        } catch (IOException e) {
                            isconnected = false;
                        }
                }
                if (isconnected) {
                    label_isconnect.setText("連接配接成功!");
                    label_isconnect.setForeground(Color.GREEN);
                    // 連接配接成功後将伺服器傳輸過來的内容展示在用戶端上面
                    filesName = cc.getFilesName();// 擷取檔案清單
                    filelist.setText("伺服器檔案:\n" + filesName);// 展示檔案清單
                } else {
                    connectErr(label_isconnect, "IP位址或端口号錯誤!");
                }
            } else {
                connectErr(label_isconnect, "請輸入IP位址或端口号!");
            }
           

1.我們要判斷輸入框時候有内容,如果沒有這提示輸入ip或端口。

2.我們還要判斷端口号裡的内容是否為數字,如果為數字則将其強轉型為int類型并嘗試連接配接服務端。期間出現任何問題則判斷為連接配接不成功。

3.根據标志isconnected來判斷是否連接配接成功,并提示相應的操作。

  • 下載下傳判斷邏輯
if (isconnected) {
                // 檢驗選擇的指令是否存在,路徑是否正确
                int comm = ;
                String dir = null;
                // 首先檢驗時候有輸入id和路徑
                if (!"".equals(tf_id.getText().trim()) && !"".equals(tf_savedir.getText().trim())) {
                    // 将驗證工作交給類ClientCore處理
                    if (tf_id.getText().trim().matches("^\\d*$")) { // 判斷檔案id是否為數字
                        comm = Integer.parseInt(tf_id.getText().trim());
                        dir = tf_savedir.getText().trim();
                        // 如果通過CilentCore的驗證,則下載下傳該檔案,下載下傳檔案邏輯交給ClientCore
                        if (cc.sendComm(comm, dir)) { // 判斷指令是否正确
                            try {
                                cc.startDownload();
                            } catch (IOException e) {
                                // TODO 處理下載下傳錯誤邏輯...
                                e.printStackTrace();
                            }
                            // 監控下載下傳進度,此處應該判斷,當檔案下載下傳遇到錯誤是如何處理//TODO
                            btn_download.setEnabled(false);
                            setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
                            task = new DownloadTask(ClientFrame.this, cc, btn_download, pr, isdownload);
                            task.addPropertyChangeListener(this);
                            task.execute();
                        }
                    } else {
                        downloadErr("請輸入正确指令");
                    }
                } else {
                    downloadErr("請輸入下載下傳指令或下載下傳路徑");
                }
            } else {
                downloadErr("請先連接配接伺服器");
            }
        }
           

1.在下載下傳之前,我們首先要檢查一下是否已經連接配接上服務端了,如果沒有連接配接則提示先連接配接服務端再下載下傳。

2.我們同樣也需要判斷輸入框内是否有内容,如果沒有則提示相應的資訊。如果有,這裡我們同樣要判斷指令為數字才能将其發送給處理核心的類。如果一切都沒有問題則開始下載下傳。

在開始下載下傳的同時需要開啟一個線程來監控下載下傳的進度。下面是監控下載下傳進度的代碼。

downloadTask.java

package com.asen.client;

import java.awt.Color;
import java.awt.Toolkit;
import java.text.SimpleDateFormat;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JProgressBar;
import javax.swing.SwingWorker;

/**
 * 為用戶端下載下傳提供的委托事件,用于更新進度條
 * @author Asen
 */
public class DownloadTask extends SwingWorker<Void, Void> {

    private JFrame frame;
    private JProgressBar pr;
    private JLabel isdownload;
    private JButton btn_download;
    private ClientCore cc; // 客服端邏輯處理類

    public DownloadTask(JFrame frame, ClientCore cc, JButton btn_download, JProgressBar pr, JLabel isdownload) {
        this.frame = frame;
        this.btn_download = btn_download;
        this.pr = pr;
        this.isdownload = isdownload;
        this.cc = cc;
    }

    /*
     * 主要任務,當現場啟動後,會在背景運作
     */
    @Override
    public Void doInBackground() {

        int progress = ; 
//      // 初始化進度條
        setProgress();
        //讓程式監控進度的程式休眠0.5ms,保證拿到檔案的總大小
        try {
            Thread.sleep();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //獲得進度條的最大值
        long total = cc.getFileSize();
        pr.setMinimum();
        pr.setMaximum((int) total);
        String tip = "";
        while (progress < total) {
            progress = cc.getCurrentFileSize();
            pr.setValue(progress);
            tip = "正在下載下傳:"+String.valueOf(progress/(*))+"MB/"+String.valueOf(total/(*))+"MB";
            pr.setString(tip);
            isdownload.setText("正在下載下傳..");
            isdownload.setForeground(Color.ORANGE);
            System.out.println("dt:目前下載下傳大小------>" + progress);
        }
        return null;
    }

    @Override
    public void done() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyy-MM-dd HH:mm:dd");
        System.out.println(sdf.format(System.currentTimeMillis())+":下載下傳完成!");
        isdownload.setText("下載下傳完成");
        pr.setString("下載下傳完成,檔案大小:"+cc.getFileSize()/(*)+"MB");
        isdownload.setForeground(Color.GREEN);
        Toolkit.getDefaultToolkit().beep(); //下載下傳完成後發出提示音
        btn_download.setEnabled(true);
        frame.setCursor(null); // 關閉滑鼠等待狀态
    }
}
           
以上基本就是該項目的所有核心代碼了,當然這裡界面的代碼我就沒有放進來了,因為有點多,而且還沒多大用處,如果需要詳細了解該項目的話,請點選最頂端的github位址檢視項目源碼,我會将後期的完善以及更新也送出上去。如果想了解我更多可以關注下面的公衆号哦。
一個簡單的檔案分享工具
 

繼續閱讀