一個簡單的文件分享工具

一個簡單的文件分享工具

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

在過去的一個星期,我在做一個基于TCP/IP傳輸層協(xié)議的文件分享工具狰闪,這個工具的主要功能就是類似于將要分享文件的分享端作為Ftp文件服務(wù)器,而下載文件方則為客戶端迹鹅。而在此過程中织阳,服務(wù)端需要提供給客戶端ip地址和端口號站削。
說明:編寫此程序目的在于學(xué)習(xí)TCP/IP傳輸協(xié)議相關(guān)知識些侍,http協(xié)議及ftp協(xié)議的底層實(shí)現(xiàn)原理攘蔽。并未打算應(yīng)用此程序,所以沒有說明界面設(shè)計呐粘,主要注重功能的實(shí)現(xiàn)满俗,同時寫本文也是為了記錄和總結(jié)這學(xué)習(xí)的過程,更是為了鍛煉自己的寫作能力作岖,歡迎大家指出我的不足與錯誤唆垃。也歡迎大家來github提出你們的想法。

一痘儡、當(dāng)前進(jìn)度

由于比較忙辕万,項(xiàng)目的整體進(jìn)度比較慢,一個星期下來只實(shí)現(xiàn)了客戶端的基本功能沉删,所以這一次記錄也主要記錄客戶端的工作以及原理渐尿。

二、工具主要設(shè)計思想

  • 服務(wù)端:
     1.服務(wù)端需要共享一個文件夾矾瑰,并將文件列表發(fā)送給每一個連接上來的客戶端砖茸。
     2.需要接受到客戶端發(fā)送過來的下載文件的指令
     3.根據(jù)文件下載指令向客戶端傳輸指定的文件以及文件的大小。
  • 客戶端:
     1.客戶端需要輸入服務(wù)端ip以及端口號來與其進(jìn)行連接殴穴。
     2.接受客戶端發(fā)送過來的文件列表
     3.將輸入的文件下載指令發(fā)送給服務(wù)端
     4.接收服務(wù)端傳送過來的文件并保存到到指定目錄

三凉夯、客戶端實(shí)現(xiàn)原理

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

注意劲够,這里的ip和端口都是來自客戶端界面的。 同時我們需要將錯誤拋給調(diào)用他的界面休傍,一點(diǎn)出現(xiàn)任何問題讓界面進(jìn)行處理征绎。

  • 其次客戶端需要接受來自服務(wù)端的文件列表
//獲取從服務(wù)器傳輸過來的文件名
    @SuppressWarnings("unchecked")
    public String getFilesName() {
        String filesName = "";
        if(isconnect){
            ObjectInputStream ois = null;
            try {
                ois = new ObjectInputStream(socket.getInputStream());
                //從服務(wù)器取得文件列表
                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 "未接收到內(nèi)容";
    }

這里需要注意的是服務(wù)端我是用TreeMap將文件名以及對應(yīng)的指令傳輸過來的。所以需要使用ObjectInputStream來接收文件列表并將接收類型強(qiáng)轉(zhuǎn)為TreeMap類型磨取。最后將接收的文件id和文件名拼成一個字符串返回個調(diào)用此方法的客戶端界面炒瘸。

  • 接下來淤堵,就是將界面輸入的需要下載的文件指令發(fā)送給服務(wù)端了。
    /**
     * 檢驗(yàn)客戶端界面發(fā)過來的指令是否正確顷扩,  
     如果正確則向服務(wù)器發(fā)送下載指令并向客戶端返回tru拐邪,  
     如果不正確則向客服的界面返回false
     * @param id
     * @param dir
     * @return
     */
    public boolean sendComm(int id, String dir) {
        //判斷界面?zhèn)鬟^來的指令是否存在
        boolean isCommExist = false;
        for (int i  : files.keySet()) {
            if(i == id){
                isCommExist = true;
            }
        }
        if(isCommExist){
            PrintStream ps;
            try {
                //發(fā)送下載指令
                ps = new PrintStream(socket.getOutputStream());
                ps.println(String.valueOf(id));
                //設(shè)置下載文件
                this.dir = dir;
                this.filename = files.get(id);
            } catch (IOException e) {
                return false;
            }
            return true;
        }
        return false;
    }

在這里我們首先要檢驗(yàn)界面要我們發(fā)送的指令是否在,如果不存在則要求重新輸入隘截,如果存在則向服務(wù)端發(fā)送該指令扎阶,并向界面給的路徑設(shè)置為我們即將保存文件的路徑。

  • 最后就是下載文件了
  //下載文件
    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[1024];
        int len = 0;
        while((len = bis.read(b)) != -1){
            bos.write(b, 0, len);
            currentFileSize += len;
        }
        bos.close();
    }

這里婶芭,我們需要分別準(zhǔn)備服務(wù)端的輸入流和客戶端的輸出流东臀,在接收文件的同時將其保存到指定的路徑下。當(dāng)然為了在客戶端試試顯示下載的情況犀农,這里也需要先獲得服務(wù)端傳輸過來的文件大小并將其返回給界面惰赋,并將當(dāng)前下載進(jìn)度返回給界面。

四呵哨、客戶端判斷邏輯

  • 連接判斷邏輯
  // 檢驗(yàn)輸入信息
            if (!tf_ip.getText().trim().equals("") && !tf_port.getText().trim().equals("")) { // 保證輸入框有內(nèi)容
                if (tf_port.getText().trim().matches("^\\d*$")) {// 判斷端口輸入框內(nèi)容是否為數(shù)字
                    ip = tf_ip.getText().trim(); // 將輸入的ip地址傳輸?shù)教幚沓绦?                    port = Integer.parseInt(tf_port.getText().trim());// 將輸入的接口傳入到處理程序
                        try {
                            // 連接服務(wù)器
                            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);
                    // 連接成功后將服務(wù)器傳輸過來的內(nèi)容展示在客戶端上面
                    filesName = cc.getFilesName();// 獲取文件列表
                    filelist.setText("服務(wù)器文件:\n" + filesName);// 展示文件列表
                } else {
                    connectErr(label_isconnect, "IP地址或端口號錯誤!");
                }
            } else {
                connectErr(label_isconnect, "請輸入IP地址或端口號孟害!");
            }

1.我們要判斷輸入框時候有內(nèi)容拒炎,如果沒有這提示輸入ip或端口。
2.我們還要判斷端口號里的內(nèi)容是否為數(shù)字挨务,如果為數(shù)字則將其強(qiáng)轉(zhuǎn)型為int類型并嘗試連接服務(wù)端击你。期間出現(xiàn)任何問題則判斷為連接不成功。
3.根據(jù)標(biāo)志isconnected來判斷是否連接成功谎柄,并提示相應(yīng)的操作丁侄。

  • 下載判斷邏輯
  if (isconnected) {
                // 檢驗(yàn)選擇的指令是否存在,路徑是否正確
                int comm = 0;
                String dir = null;
                // 首先檢驗(yàn)時候有輸入id和路徑
                if (!"".equals(tf_id.getText().trim()) && !"".equals(tf_savedir.getText().trim())) {
                    // 將驗(yàn)證工作交給類ClientCore處理
                    if (tf_id.getText().trim().matches("^\\d*$")) { // 判斷文件id是否為數(shù)字
                        comm = Integer.parseInt(tf_id.getText().trim());
                        dir = tf_savedir.getText().trim();
                        // 如果通過CilentCore的驗(yàn)證朝巫,則下載該文件,下載文件邏輯交給ClientCore
                        if (cc.sendComm(comm, dir)) { // 判斷指令是否正確
                            try {
                                cc.startDownload();
                            } catch (IOException e) {
                                // TODO 處理下載錯誤邏輯...
                                e.printStackTrace();
                            }
                            // 監(jiān)控下載進(jìn)度绒障,此處應(yīng)該判斷,當(dāng)文件下載遇到錯誤是如何處理//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("請先連接服務(wù)器");
            }
        }

1.在下載之前捍歪,我們首先要檢查一下是否已經(jīng)連接上服務(wù)端了户辱,如果沒有連接則提示先連接服務(wù)端再下載。
2.我們同樣也需要判斷輸入框內(nèi)是否有內(nèi)容糙臼,如果沒有則提示相應(yīng)的信息庐镐。如果有,這里我們同樣要判斷指令為數(shù)字才能將其發(fā)送給處理核心的類变逃。如果一切都沒有問題則開始下載必逆。
在開始下載的同時需要開啟一個線程來監(jiān)控下載的進(jìn)度。下面是監(jiān)控下載進(jìn)度的代碼。
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;

/**
 * 為客戶端下載提供的委托事件名眉,用于更新進(jìn)度條
 * @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;
    }

    /*
     * 主要任務(wù)粟矿,當(dāng)現(xiàn)場啟動后,會在后臺運(yùn)行
     */
    @Override
    public Void doInBackground() {

        int progress = 0; 
//      // 初始化進(jìn)度條
        setProgress(0);
        //讓程序監(jiān)控進(jìn)度的程序休眠0.5ms损拢,保證拿到文件的總大小
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //獲得進(jìn)度條的最大值
        long total = cc.getFileSize();
        pr.setMinimum(0);
        pr.setMaximum((int) total);
        String tip = "";
        while (progress < total) {
            progress = cc.getCurrentFileSize();
            pr.setValue(progress);
            tip = "正在下載:"+String.valueOf(progress/(1024*1024))+"MB/"+String.valueOf(total/(1024*1024))+"MB";
            pr.setString(tip);
            isdownload.setText("正在下載..");
            isdownload.setForeground(Color.ORANGE);
            System.out.println("dt:當(dāng)前下載大小------>" + 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()/(1024*1024)+"MB");
        isdownload.setForeground(Color.GREEN);
        Toolkit.getDefaultToolkit().beep(); //下載完成后發(fā)出提示音
        btn_download.setEnabled(true);
        frame.setCursor(null); // 關(guān)閉鼠標(biāo)等待狀態(tài)
    }
}

以上基本就是該項(xiàng)目的所有核心代碼了,當(dāng)然這里界面的代碼我就沒有放進(jìn)來了福压,因?yàn)橛悬c(diǎn)多掏秩,而且還沒多大用處,如果需要詳細(xì)了解該項(xiàng)目的話荆姆,請點(diǎn)擊最頂端的github地址查看項(xiàng)目源碼蒙幻,我會將后期的完善以及升級也提交上去。如果想了解我更多可以點(diǎn)擊下方原文鏈接關(guān)注下面的公眾號哦胆筒。
原文鏈接

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末邮破,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子仆救,更是在濱河造成了極大的恐慌抒和,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件派桩,死亡現(xiàn)場離奇詭異,居然都是意外死亡蚌斩,警方通過查閱死者的電腦和手機(jī)铆惑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來送膳,“玉大人员魏,你說我怎么就攤上這事〉” “怎么了撕阎?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長碌补。 經(jīng)常有香客問我虏束,道長,這世上最難降的妖魔是什么厦章? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任镇匀,我火速辦了婚禮,結(jié)果婚禮上袜啃,老公的妹妹穿的比我還像新娘汗侵。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布晰韵。 她就那樣靜靜地躺著发乔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪雪猪。 梳的紋絲不亂的頭發(fā)上栏尚,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天,我揣著相機(jī)與錄音浪蹂,去河邊找鬼抵栈。 笑死,一個胖子當(dāng)著我的面吹牛坤次,可吹牛的內(nèi)容都是我干的古劲。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼缰猴,長吁一口氣:“原來是場噩夢啊……” “哼产艾!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起滑绒,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤闷堡,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后疑故,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體杠览,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年纵势,在試婚紗的時候發(fā)現(xiàn)自己被綠了踱阿。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡钦铁,死狀恐怖软舌,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情牛曹,我是刑警寧澤佛点,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站黎比,受9級特大地震影響超营,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜阅虫,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一糟描、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧书妻,春花似錦船响、人聲如沸躬拢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽聊闯。三九已至,卻和暖如春米诉,著一層夾襖步出監(jiān)牢的瞬間菱蔬,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工史侣, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拴泌,地道東北人。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓惊橱,卻偏偏與公主長得像蚪腐,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子税朴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容