Java - Use the MD5 checksum to confirm that the downloaded file is correct

本篇主旨在於client端下載server端的檔案後,進行MD5 checksum的檢查,確認檔案是否無誤!
程式撰寫如下:
1. 建立Server.java,等待Client端連線,連線上之後呈列目前可供下載的檔案
2. 建立Client.java,與Server端連線後,選擇檔案後進行下載
3. 建立Main.java,代表程式入口,可輸入相關參數選擇要執行哪一種模式(Server or Client)

#Server.java

package service;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;

import main.Main;

public class Server {
	private ServerSocket server;
	private File sharePath;
	
	public Server(int port, File sharePath) throws IOException {
		this.sharePath = sharePath;
		this.server = new ServerSocket(port);
	}
	
	public void startService() {
		while(!server.isClosed()) {
		    try {
	               System.out.println("Waiting for the Client connection...");
	               Socket socket = server.accept();
	               String client = socket.getInetAddress().getHostAddress();
	               System.out.println("Connection received from: "+client);
	            
	               ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
	               ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
	            
	               File listFiles[] = sharePath.listFiles();
	               oos.writeObject("The files that can be downloaded are as follows:");
	               int index = 0;
	               oos.writeObject(listFiles.length);
	               for(File f : listFiles) {
	                  oos.writeObject("("+(++index)+")"+f.getName());
	               }
	            
	               while (!socket.isClosed()) {
	                  System.out.println("Waiting for the "+client+" to send option id");
	            	  Object selection = ois.readObject();
	                
	                  if(selection instanceof Integer) {
	                     int sel = (int) selection;
	                     System.out.println("Client send option id is "+sel);
	                	
	                     if(sel == 0) {
	                        socket.close();
	                	break;
	                     }
	                     if(sel > listFiles.length) {
	                        System.out.println("File non-exist!!");
	                	oos.writeLong(-1L);
	                	oos.flush();
	                	continue;
	                     }
	                	
	                     File sendFile = listFiles[sel - 1];
	                     oos.writeLong(sendFile.length());
	                	
	                     oos.writeObject(sendFile.getName());
	                	
	                     String md5 = Main.getCheckSumByMD5(sendFile.getAbsolutePath());
	                     oos.writeObject(md5);
	                	
	                     InputStream is = new FileInputStream(sendFile);
                	     int read;
	                     byte[] buf = new byte[1024];
	                     while((read = is.read(buf, 0, buf.length)) != -1) {
	                        oos.write(buf, 0, read);
	                	oos.flush();
	                     }
	                	
	                     is.close();
	                     System.out.println("File "+sendFile.getName()+" transfer completed!");
	                     System.out.println("===========================");
	                  }
	               }
	               System.out.println("Client "+client+" disconnected!\n");
	               ois.close();
	               oos.close();
	            }catch (IOException | ClassNotFoundException e) {
	               e.printStackTrace();
	               terminateService();
	            }
		}
	}
	
	public void terminateService() {
		try {
	           this.server.close();
		   System.out.println("Shutdown server");
		} catch (IOException e) {
		   // TODO Auto-generated catch block
		   e.printStackTrace();
		}
	}
}

client連上後,server傳送檔案清單,當收到項目的編號後,依序將檔案size(bytes)、名稱、md5 checksum傳給client,接下來就是data部分,buffer設定1024 bytes分批傳送! 傳送完畢後等待client下一個項目編號!
PS. 在這邊只示範傳送檔案,若是目錄不在測試範圍內

#Client.java

package service;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;

import main.Main;

public class Client {
	private File dPath;
	
	public Client(File downloadPath) {
		this.dPath = downloadPath;
	}
	
	public void startService() {
		InetAddress host;
		Scanner input = new Scanner(System.in);
		try {
		   host = InetAddress.getLocalHost();
		   Socket socket = new Socket(host.getHostName(), 5880);

		   ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
                   ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
            
                   System.out.println(ois.readObject());
                   int num = (int) ois.readObject();
                   int i = 0;
                   while(i++ < num) {
            	      System.out.println(ois.readObject());
                   }
            
                   while(!socket.isClosed()) {
	              System.out.println("Waiting to send the option id [1 ~ "+num+"] to download file from server...");
	              System.out.println("Note. Terminate connection please input option 0");
	              int option = input.nextInt();
	            
	              oos.writeObject(option);
	              if(option == 0) {
	                 socket.close();
	            	 System.exit(-1);
	              }
	            
	              System.out.println("Send option id is "+option);
	            
	              long totalByte = ois.readLong();
	              if(totalByte == -1L) {
	                  System.out.println("Download file non-exist!!");
	            	  System.out.println("===========================");
	            	  continue;
	              }
	              System.out.println("Download file total size is "+totalByte+" bytes");
	            
	              String filename = (String) ois.readObject();
	              String checksum = (String) ois.readObject();
	            
	              String path = dPath.getAbsolutePath() + File.separator + filename;
	              System.out.println("Download to "+path);
	            
	              byte[] buffer = new byte[1024];
	              long cur = 0L;
	              FileOutputStream fos = new FileOutputStream(path);
	              while(cur < totalByte) {
	                 int len = ois.read(buffer);
	            	 cur += len;
	            	 fos.write(buffer, 0, len);
	            	 double percent = ((double)cur / totalByte) * 100;
	            	 System.out.println("Download current size = "+cur + " bytes, percent = "+percent+"%");
	              }
	              System.out.println("Download completed!!\n");
	              fos.close();
	        	
	              System.out.println("Check if the file is correct>>>");
	              String md5 = Main.getCheckSumByMD5(path);
	              System.out.println("Transferred file checksum => "+checksum);
	              System.out.println("Downloaded file checksum => "+md5);
	              if(md5.equals(checksum)) {
	                 System.out.println("Yes. It is corrent!");
	              }else {
	        	 System.out.println("No. It is incorrent!");
	              }
	              System.out.println("===========================");
                   }
                   input.close();
                   ois.close();
                   oos.close();
		} catch (UnknownHostException e) {
		   // TODO Auto-generated catch block
		   e.printStackTrace();
		} catch (IOException e) {
		   // TODO Auto-generated catch block
		   e.printStackTrace();
		} catch (ClassNotFoundException e) {
		   // TODO Auto-generated catch block
		   e.printStackTrace();
		}
	}
}

client端連上server後,收到檔案清單,傳送項目編號後,收到檔案大小判斷是否存在,開始接收data後,計算當下下載進度,完畢後計算檔案md5 checksum,與server端的checksum進行比對,確認是否檔案傳輸正確!

#Main.java

package main;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;

import org.apache.commons.codec.digest.DigestUtils;

import service.Client;
import service.Server;

public class Main {
	private static final int PORT = 5880;
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		if(args.length == 0) {
			System.out.println("<<File Transfer Service>>"
					+ "\nStart client mode -> java -jar ftransfer.jar client [target path]"
					+ "\nStart server mode -> java -jar ftransfer.jar server [share path]");
		}else {
			if(args.length == 1) {
				System.out.println("Please specify path");
			}else {
				File path = new File(args[1]);
				if(!path.exists()) {
					System.out.println("The path non-exist");
				}else {
					String mode = args[0];
					if(mode.equals("server")) {
						Server server = null;
						try {
						    server = new Server(PORT, path);
						    server.startService();
						} catch (IOException e) {
						    // TODO Auto-generated catch block
						    e.printStackTrace();
						} finally {
						    if(server != null) {
						        server.terminateService();
						    }
						}
					}else if(mode.equals("client")) {
						Client client = new Client(path);
						client.startService();
					}
				}
			}
		}	
	}
	
	public static String getCheckSumByMD5(String filepath) throws IOException {
		try (InputStream is = Files.newInputStream(Paths.get(filepath))) {
		    return DigestUtils.md5Hex(is);
		}
	}

}

依據主程式輸入的參數來決定開啟server or client mode

最後DEMO如下:
1. 透過eclipse輸出成可執行的jar檔FileTransfer.jar進行示範
2. 執行java -jar FileTransfer.jar (展現提示)
3. 啟動server,指定分享路徑D:\server
4. 啟動client,指定下載存放目錄E:\
5. server端輸出client端資訊,並且等待項目編號的回應
6. client端下載項目1
7. server端顯示收到需傳送項目1的檔案,傳送完畢後等待下一個項目要求
8. client端輸入無效id & terminate server connection
9. server端顯示檔案不存在 & terminate client connection
上面的步驟對照下面的圖示如下

留言