JAVA Socket通訊 打造屬於本身的網盤

 近一個月沒敲JAVA代碼了,最近老師佈置了一個寫JAVA網盤的做業,總共花了十幾個小時,總算寫完了,debug真的累,感受本身仍是菜了,沒有那種有一個想法就能立刻用代碼實現的能力。。。。不扯了,下面開始正題。html

功能介紹

  • 支持1個客戶端,1個服務器端。服務器提供網盤空間。
  • 首先運行服務器。服務器運行以後,客戶端運行網盤客戶端。
  • 運行客戶端。用戶可以輸入暱稱。肯定,則鏈接到服務器。鏈接成功,便可出現客戶端面。
  • 能夠在網盤中新建文件夾,刪除空文件夾,重命名文件夾;能夠將本身電腦上某個文件上傳到網盤中的某個文件夾下(支持單文件),能夠刪除單個文件、重命名文件、下載單個文件。
  • 可實現大文件傳輸

總體思路

大概分了這麼幾個類java

服務器端

MainServer:

原來是想作個服務器界面的,但仍是有點懶,就算了,因此這個類如今就用來建立Panserver對象git

public class MainServer {

    private PanServer panServer;//服務器對象


    public static void main(String[] args){
        MainServer ms =new MainServer();
        ms.actionServer();
    }

    // 開啓服務器
    public void actionServer() {
        // 1.要獲得服務器狀態
        if (null == panServer) {
            panServer = new PanServer(8888);
            panServer.start();
        } else if (panServer.isRunning()) {// 己經在運行
            panServer.stopPanServer();
            panServer = null;
        }

    }
}

Panserver:

用於創建服務器SocketServer的類github

package PanServer;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 〈服務器socketserver建立〉
 *
 * @author ITryagain
 * @create 2018/12/5
 * @since 1.0.0
 */

public class PanServer extends Thread {
    private ServerSocket ss;//服務器對象
    private int port;//端口
    private boolean running=false;//服務器是否在運行中

    PanServer(int port){
        this.port=port;
    }

    public void run(){
        setupServer();
    }

    //在指定端口上啓動服務器
    private void setupServer(){
        try{
            ss=new ServerSocket(this.port);
            running=true;
            System.out.println("服務器建立成功:"+this.port);
            while(running){
                Socket client = ss.accept();
                System.out.println("進入了一個客戶機對象:"+client.getRemoteSocketAddress());
                ServerThread ct = new ServerThread(client);
                ct.start();
            }
        }catch(IOException e){
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    /*
     * 查詢服務器是否在運行
     *
     * @return: true爲運行中
     */
    public boolean isRunning() {
        return this.running;
    }

    // 關閉服務器
    public void stopPanServer() {
        this.running = false;
        try {
            ss.close();
        } catch (Exception e) {}
    }
}

else

剩下的三個類就是服務器實現的關鍵了編程

其中最重要的是ServerThread類,該類用於實現與客戶端的通訊,經過接收客戶端的指令進行不一樣的操做,其中函數以下圖所示服務器

首先,咱們在創建鏈接時,須要輸入用戶名,並建立一個文件夾,文件名爲用戶名,所以,咱們須要一個接收一開始發送的用戶名信息,寫在processSocket內機器學習

Socket sc=this.client;
ins=sc.getInputStream();
ous=sc.getOutputStream();
//將輸入流ins封裝爲能夠讀取一行字符串也就是以\r\n結尾的字符串
BufferedReader brd=new BufferedReader(new InputStreamReader(ins));
sendMsg2Me("歡迎您使用!請輸入你的用戶名:");
User_name=brd.readLine();
System.out.println(User_name);

這樣咱們就讀取了用戶名,讀取用戶名後,立刻就能建立文件夾socket

File directory = new File("D:\\"+User_name);
if(!directory.exists()){
       directory.mkdir();
}

而後就進入while循環,不斷從客戶端讀取用戶操做信息ide

String input=brd.readLine();//一行一行的讀取客戶機發來的消息
while(true) {
          System.out.println("服務器收到的是"+input);
          if((!this.upLoad)&&(!this.downLoad)){
                check(input);
          }
          if(this.upLoad){//上傳中
                UpLoad(input);
          }
          if(this.downLoad){//下載中
                DownLoad(input);
          }
          input=brd.readLine();//讀取下一條
          System.out.println(input);
}

這裏我用了三個函數來分別處理三種狀態,其中check函數用來解碼,這裏我給出其中新建文件夾的寫法示例,刪除和重命名與之相似函數

private void check(String input){
        if(input.charAt(0)=='~'){
            String tmp=input.substring(input.indexOf("~")+1,input.indexOf("#"));
            System.out.println(tmp);
            if(tmp.equals("downLoad")){
                this.downLoad=true;
            }else if(tmp.equals("upLoad")){
                this.upLoad=true;
            }else if(tmp.equals("new")){
           //新建文件夾
                System.out.println(input.substring(input.indexOf("#")+1));
                File directory = new File(input.substring(input.indexOf("#")+1));
                if(!directory.exists()){
                    directory.mkdir();
                }
            }else if(tmp.equals("delete")){
                //刪除文件夾
            }else if(tmp.equals("change")){
               //重命名文件夾
            }
        }
    }    

而後剩下的就是UpLoad和DownLoad函數了,這兩個函數分別對應了上傳和下載功能,我一開始把這兩個功能都放在一開始創建的SockerServer裏面了,結果發現文件上傳了以後關閉流時把我線程也關了orz。。。仍是太菜了,這種錯都能寫出來,百度了一番,看到好多人都是再開幾個端口解決的。。。一開始就想到這方法了,但不想這麼幹,總覺的應該還有更好的辦法,可最終仍是決定用這種方法了(真香)。這裏就給出其中一個函數的寫法吧

    private void UpLoad(String input){
        System.out.println("上傳文件");
        UpLoadThread upLoadThread = new UpLoadThread(8889,input);
        upLoadThread.start();
        this.upLoad=false;
    }

既然給了UoLoad的寫法,就順便講講upLoadThread吧

/**
 * 〈服務器接受文件線程〉
 *
 * @author ITryagain
 * @create 2018/12/8
 * @since 1.0.0
 */

public class UpLoadThread extends Thread{
    private ServerSocket UpLoadServer;
    private int port;
    private String input;
    private FileOutputStream fos;

    UpLoadThread(int port,String input){
        this.port=port;
        this.input=input;
    }

    public void run(){
        
    }


    private static DecimalFormat df = null;

    static {
        // 設置數字格式,保留一位有效小數
        df = new DecimalFormat("#0.0");
        df.setRoundingMode(RoundingMode.HALF_UP);
        df.setMinimumFractionDigits(1);
        df.setMaximumFractionDigits(1);
    }

    /**
     * 格式化文件大小
     * @param length
     * @return
     */
    private String getFormatFileSize(long length) {
       
    }
}

大體函數就是這樣的,其中run方法裏面就是文件接收了,(若是發現缺了什麼本身補一補,就一個變量的申明沒加上去)

    try{
            UpLoadServer = new ServerSocket(port);
            socket = UpLoadServer.accept();
            dis = new DataInputStream(socket.getInputStream());
            //文件名和長度
            String fileName = input.substring(input.indexOf("#")+1);
            long fileLength = dis.readLong();
            File file = new File(fileName);
            fos = new FileOutputStream(file);

            //開始接收文件
            byte[] bytes = new byte[1024];
            int length=0;
            while((length = dis.read(bytes, 0, bytes.length)) != -1) {
                fos.write(bytes, 0, length);
                fos.flush();
            }
            System.out.println("======== 文件接收成功 [File Name:" + fileName + "] [Size:" + getFormatFileSize(fileLength) + "] ========");

            try {
                if(fos!=null)
                    fos.close();
                if(dis != null)
                    dis.close();
                if(socket !=null)
                    socket.close();
                if(UpLoadServer!=null)
                    UpLoadServer.close();

            } catch (Exception e) {}

        }catch(IOException e){

        }

而後就是 getFormatFileSize() 函數了,這個函數是用來幹嗎的呢?就是用來轉化一下文件大小單位的,否則到時候一個幾 GB 的文件顯示的就是 *****

******B了,那麼長一串,看着也不舒服。

    private String getFormatFileSize(long length) {
        double size = ((double) length) / (1 << 30);
        if(size >= 1) {
            return df.format(size) + "GB";
        }
        size = ((double) length) / (1 << 20);
        if(size >= 1) {
            return df.format(size) + "MB";
        }
        size = ((double) length) / (1 << 10);
        if(size >= 1) {
            return df.format(size) + "KB";
        }
        return length + "B";
    }

服務器端剩下沒講的代碼其實都差很少,就本身去實現吧

客戶端

客戶端就比較麻煩了,尤爲是這個界面,花了我老半天時間

MainClient:

這個類就是客戶端用於建立界面以及管理線程的類了,界面我是用JTree來實現的,看下函數先吧

public class MainClient extends JFrame implements ActionListener, TreeModelListener {

    private JTree tree;
    private int ServerIP = 8888;
    private JLabel statusLabel;
    private DefaultTreeModel treeModel;
    private String oldNodeName;
    private OutputStream ous;
    private Socket client;
    private String name;
    private String stress = "D:\\";
    private String downLoadStress="D:\\下載\\";

    public static void main(String[] args){
        MainClient mc=new MainClient();
        mc.showLoginUI();
    }

    public void showLoginUI(){

    }

    // 登錄事件處理
    private void loginAction() {

    }

    //顯示操做窗口
    private void showMainUI() {

    }

    @Override
    public void actionPerformed(ActionEvent e) {
        if (e.getActionCommand().equals("新建文件夾")) {

        }
        if (e.getActionCommand().equals("刪除文件夾")) {
        
        }
        if (e.getActionCommand().equals("上傳文件")) {

        }
        if(e.getActionCommand().equals("下載文件")){

        }

    }

    @Override
    /*
     *  修改文件名字
     */
    public void treeNodesChanged(TreeModelEvent e) {

    }

    //選擇上傳文件
    public void chooseSendFile(){

    }

    public void sendFile(File file,String path) throws IOException{

    }

    private String getSendPath(TreePath parentPath){

    }

    //選擇下載文件
    public void chooseDownLoadFile() {

    }
    //下載文件
    public void downloadFile(String path) throws IOException{

    }

    private String getDownLoadPath(TreePath parentPath){

    }

    private void newFile(String path){

    }

    private void deleteFile(String path){

    }

    private void ChangeFileName(String NewName,String path){

    }
    @Override
    public void treeNodesInserted(TreeModelEvent e) {
    }

    @Override
    public void treeNodesRemoved(TreeModelEvent e) {
    }

    @Override
    public void treeStructureChanged(TreeModelEvent e) {
    }

}

 

先來看看界面吧,打開後你可能只能看到三個按鈕,拉動一下框框就能看到第四個了,大小設置有點問題。

 private void showMainUI() {
        JFrame frame=new JFrame("網盤");
        Container contentPane = frame.getContentPane();

        DefaultMutableTreeNode root = new DefaultMutableTreeNode(name);
        tree = new JTree(root);
        tree.addMouseListener(new MyTreeMouseListener(oldNodeName));
        treeModel = (DefaultTreeModel)tree.getModel();
        treeModel.addTreeModelListener(this);
        tree.setEditable(true);
        tree.getCellEditor().addCellEditorListener(new MyTreeCellEditorListener(tree));
        JScrollPane scrollPane = new JScrollPane();
        scrollPane.setViewportView(tree);

        JPanel toolBarPanel = new JPanel();
        JButton b = new JButton("新建文件夾");
        b.addActionListener(this);
        toolBarPanel.add(b);
        b = new JButton("刪除文件夾");
        b.addActionListener(this);
        toolBarPanel.add(b);
        b = new JButton("上傳文件");
        b.addActionListener(this);
        toolBarPanel.add(b);
        b = new JButton("下載文件");
        b.addActionListener(this);
        toolBarPanel.add(b);

        statusLabel = new JLabel("Action");
        contentPane.add(toolBarPanel, BorderLayout.NORTH);
        contentPane.add(scrollPane, BorderLayout.CENTER);
        contentPane.add(statusLabel, BorderLayout.SOUTH);
        frame.pack();
        frame.setVisible(true);
        frame.requestFocus();
        frame.setSize(400, 400);
        frame.setLocationRelativeTo(null);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        tree.setRootVisible(false);
    }

裏面兩監聽類的實現(提醒一下,可能到後面你作重命名文件夾會遇到坑,跟其中一個監聽類有關,提醒一下oldNodeName是用來記錄修改前的文件夾名稱的,想辦法獲取這個變量的值就能很快實現文件夾重命名的功能),還有文件路徑的實現也本身好好想一想

至於界面的其它一些功能實現能夠去看這篇博客(包括兩個監聽類的實現以及 actionPerformed() 和 treeNodesChanged() 等函數的實現)http://www.javashuo.com/article/p-gkueliis-co.html

 而後這裏給出NewFile()函數寫法

    private void newFile(String path){
        String fileName = "~new#"+path+"\r\n";
        System.out.println(fileName);
        try {
            this.ous.write(fileName.getBytes());
            this.ous.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

至於 delete 和 change 的寫法與此相似

前面我給出了服務器上傳文件的類,這裏就給出客戶端上傳文件的類的寫法

先看下 sendFile() 函數

 public void sendFile(File file,String path) throws IOException{
        String fileName = "~upLoad#"+path+"\\"+file.getName()+"\r\n";
        System.out.println(fileName);
        this.ous.write(fileName.getBytes());
        this.ous.flush();
        FileSendThread send_socket = new FileSendThread(8889,file,path);
        send_socket.start();
    }

FileSendThread:

客戶端上傳文件的類

public class FileSendThread extends Thread {

    private Socket fileSendSocket;
    private int port;
    private String path;
    private File file;
    private OutputStream ous;
    private FileInputStream fis;
    private DataOutputStream dos;

    FileSendThread(int port, File file,String path){
        this.port=port;
        this.file=file;
        this.path=path;
    }
    public void run(){
        try{
            fileSendSocket = new Socket("localhost",this.port);
            // 發送: 文件名稱 文件長度
            this.ous = fileSendSocket.getOutputStream();
            dos = new DataOutputStream(this.ous);
            dos.writeLong(file.length());

            //開始傳輸文件
            System.out.println("======開始傳輸文件=======");
            byte[] bytes = new byte[1024];
            int length;
            long progress = 0;
            fis = new FileInputStream(file);
            while((length=fis.read(bytes,0,bytes.length))!=-1){
                dos.write(bytes,0,length);
                dos.flush();
                progress = progress + length;
                System.out.print("| " + (100*progress/file.length()) + "% |");
            }
            System.out.println();
            System.out.println("======== 文件傳輸成功 ========");
        }catch(IOException e1){

        }finally {
            try {
                if (fis != null)
                    fis.close();
                if (dos != null)
                    dos.close();
            }catch(IOException e){
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}

至於下載功能,與上傳相似,把服務器和客戶端上傳類中的部分代碼換一換就行了。

到了這裏大家是否還記得咱們在通訊鏈接創建時須要傳輸用戶名?實現方式以下

登錄框的實現,我這裏算是偷懶了,直接利用了 JOptionPane

    public void showLoginUI(){

        name = JOptionPane.showInputDialog("請輸入用戶名");
        System.out.println(name);
        loginAction();
    }
    // 登錄事件處理
    private void loginAction() {

        try {
            this.client = new Socket("localhost",ServerIP);
            if(loginServer()){
                showMainUI();
            }else{
                JOptionPane.showMessageDialog(null,"登錄失敗","肯定",JOptionPane.WARNING_MESSAGE);
            }
        } catch (IOException e) {
            JOptionPane.showMessageDialog(null,"鏈接失敗","肯定",JOptionPane.WARNING_MESSAGE);
        }

    }
    private boolean loginServer(){
        try{
            this.ous = this.client.getOutputStream();
            String _name=name+"\r\n";
            this.ous.write(_name.getBytes());
            this.ous.flush();
            return true;
        }catch(IOException e){
            return false;
        }
    }

順便介紹幾個方法

文件夾重命名

File directory = new File("D:\a.txt");
directory.renameTo(new File("D:\b.txt"));

這裏實現的是將a.txt重命名爲b.txt,對文件夾也有效

文件夾刪除

 boolean success = (new File("D:\a.txt").delete();
 if(success){
   System.out.println("刪除成功");
 }else{
    ystem.out.println("刪除失敗");
 }

還有String的幾個操做

一、個人編碼

  • 上傳文件  ~upLoad#文件路徑+文件
  • 下載文件  ~downLoad#文件路徑+文件
  • 文件重命名 ~change#文件路徑+原文件名@文件路徑+新文件名
  • 新建文件夾 ~new#文件路徑+文件名
  • 刪除文件夾 ~delete#文件路徑+文件名

2.String中的 subString() 和 indexOf() 函數

總結

到這裏,個人網盤介紹算是完了,由於這是做業而且還沒檢查的緣故,就沒有把全部代碼放出來了,就介紹了下思路,順便給將要寫網盤或者正在寫網盤的大家一點思路,相信有了這些思路,大家能很快地寫完網盤,或者有了寫的思路。過久沒寫代碼果真不行,搞了一個多月的強化學習、機器學習和數據挖掘,差點就不會寫代碼了(面向搜索引擎編程感受還行。。。百度有時很強大,可能只是你沒學會正確的搜索的姿式)


檢查完了,開源初版代碼https://github.com/leo6033/JAVA-Pan

相關文章
相關標籤/搜索