### 一個簡單的檔案分享
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位址檢視項目源碼,我會将後期的完善以及更新也送出上去。如果想了解我更多可以關注下面的公衆号哦。![]()
一個簡單的檔案分享工具