Java學習日誌(二十四): 文件上傳,瀏覽器訪問服務器端(B/S)的代碼實現

JavaEE學習日誌持續更新----> 必看!JavaEE學習路線(文章總彙)

文件上傳

原理

文件上傳原理:

  1. 客戶端使用本地字節輸入流,讀取本地文件
  2. 客戶端使用網絡字節輸出流,把讀取到的圖片上傳到服務器
  3. 服務器使用網絡字節輸入流,讀取客戶端上傳的文件
  4. 服務器使用本地字節輸出流,把客戶端上傳的文件保存到服務器的硬盤上
  5. 服務器使用網絡字節輸出流,給客戶端回寫上傳成功
  6. 客戶端使用網絡字節輸入流,讀取服務器回寫的上傳成功

注意

  • 客戶端/服務器和本地的文件進行讀寫,必須使用自己創建的流
  • 客戶端和服務器之間進行交互,必須使用Socket提供的網絡流

文件上傳的原理:

文件複製,客戶端本地-->複製-->服務器-->複製-->服務器硬盤

明確:
在這裏插入圖片描述

文件上傳的客戶端

文件上傳的客戶端:讀取本地的文件,上傳到服務器;讀取服務器回寫的數據
數據源:D:\1.jpg
目的地:服務器

實現步驟:

  1. 創建本地字節輸入流FileInputStream對象,構造方法中綁定要讀取的數據源
  2. 創建客戶端Socket對象,構造方法中綁定服務器的IP地址和端口號
  3. 使用Socket對象中的方法getOutputStream,獲取網絡字節輸出流OutputStream對象
  4. 使用本地字節輸入流FileInputStream對象中的方法read,讀取本地要上傳的文件
  5. 使用網絡字節輸出流OutputStream對象中的方法write,把讀取的文件寫到服務器
  6. 使用Socket對象中的方法getInputStream,獲取網絡字節輸入流InputStream對象
  7. 使用網絡字節輸入流InputStream對象的方法read,讀取服務器回寫的數據
  8. 釋放資源

代碼示例:客戶端

public class TCPClient {
    public static void main(String[] args) throws IOException {
        //1.創建本地字節輸入流FileInputStream對象,構造方法中綁定要讀取的數據源
        FileInputStream fis = new FileInputStream("D:\\1.jpg");
        //2.創建客戶端Socket對象,構造方法中綁定服務器的IP地址和端口號
        Socket socket = new Socket("127.0.0.1", 9999);
        //3.使用Socket對象中的方法getOutputStream,獲取網絡字節輸出流OutputStream對象
        OutputStream os = socket.getOutputStream();
        //4.使用本地字節輸入流FileInputStream對象中的方法read,讀取本地要上傳的文件
        byte[] bytes = new byte[1024];
        int len = 0;
        while ((len = fis.read(bytes)) != -1) {
            //5.使用網絡字節輸出流OutputStream對象中的方法write,把讀取的文件寫到服務器
            os.write(bytes, 0, len);
        }
        //6.使用Socket對象中的方法getInputStream,獲取網絡字節輸入流InputStream對象
        InputStream is = socket.getInputStream();
        //7.使用網絡字節輸入流InputStream對象的方法read,讀取服務器回寫的數據
        while ((len = is.read(bytes)) != -1) {
            System.out.println(new String(bytes, 0, len));
        }
        //8.釋放資源
        fis.close();
        socket.close();

    }
}

文件上傳的服務器端

文件上傳的服務器端:讀取客戶端上傳的文件,保存服務器的硬盤,給客戶端回寫“上傳成功”
數據源:客戶端上傳的文件
目的地:服務器的硬盤 D:\upload\1.jpg

實現步驟:

  1. 判斷D盤是否有upload文件夾,沒有則創建
  2. 創建服務器ServerSocket對象,構造方法和系統要指定的端口號
  3. 使用ServerSocket對象accept獲取到請求的客戶端Socket對象
  4. 使用Socket對象中的方法getInputStream,獲取網絡字節輸入流InputStream對象
  5. 創建本地字節輸出流FileOutputStream對象,構造方法綁定輸出的目的地
  6. 使用網絡字節輸入流InputStream對象中的方法read讀取客戶端上傳的文件
  7. 使用本地字節輸出流FileOutputStream對象中的方法write,把客戶端上傳的文件保存到服務器的硬盤上
  8. 使用Socket對象中的方法getOutputStream,獲取網絡字節輸出流OutputStream對象
  9. 使用write方法,給客戶端回寫“上傳成功”
  10. 釋放資源

代碼示例:

public class TCPServer {
    public static void main(String[] args) throws IOException {
        //1.判斷D盤是否有upload文件夾,沒有則創建
        File file = new File("D:\\upload");
        if(!file.exists()){
            file.mkdirs();
        }
        //2.創建服務器ServerSocket對象,構造方法和系統要指定的端口號
        ServerSocket server = new ServerSocket(9999);
        //3.使用ServerSocket對象accept獲取到請求的客戶端Socket對象
        Socket socket = server.accept();
        //4.使用Socket對象中的方法getInputStream,獲取網絡字節輸入流InputStream對象
        InputStream is = socket.getInputStream();
        //5.創建本地字節輸出流FileOutputStream對象,構造方法綁定輸出的目的地
        FileOutputStream fos = new FileOutputStream(file+"\\1.jpg");
        //6.使用網絡字節輸入流InputStream對象中的方法read讀取客戶端上傳的文件
        byte[] bytes = new byte[1024];
        int len = 0;
        while((len = is.read(bytes))!=-1){
            //7.使用本地字節輸出流FileOutputStream對象中的方法write,把客戶端上傳的文件保存到服務器的硬盤上
            fos.write(bytes,0,len);
        }
        //8.使用Socket對象中的方法getOutputStream,獲取網絡字節輸出流OutputStream對象
        //9.使用write方法,給客戶端回寫“上傳成功”
        socket.getOutputStream().write("上傳成功".getBytes());
        //10.釋放資源
        fos.close();
        socket.close();
        server.close();
    }
}

文件上傳的阻塞問題

原因:
在這裏插入圖片描述

修改客戶端程序:在文件上傳完成之後,給服務器添加一個結束標記

使用Socket類中的方法:
void shutdownOutput() 禁用此套接字的輸出流。對於TCP套接字,將發送任何先前寫入的數據,然後發送TCP的正常連接終止序列。(結束標記)

代碼示例:修改後的客戶端程序

public class TCPClient {
    public static void main(String[] args) throws IOException {
        //1.創建本地字節輸入流FileInputStream對象,構造方法中綁定要讀取的數據源
        FileInputStream fis = new FileInputStream("D:\\1.jpg");
        //2.創建客戶端Socket對象,構造方法中綁定服務器的IP地址和端口號
        Socket socket = new Socket("127.0.0.1", 9999);
        //3.使用Socket對象中的方法getOutputStream,獲取網絡字節輸出流OutputStream對象
        OutputStream os = socket.getOutputStream();
        //4.使用本地字節輸入流FileInputStream對象中的方法read,讀取本地要上傳的文件
        byte[] bytes = new byte[1024];
        int len = 0;
        while ((len = fis.read(bytes)) != -1) {
            //5.使用網絡字節輸出流OutputStream對象中的方法write,把讀取的文件寫到服務器
            os.write(bytes, 0, len);
        }
        /*
            上傳文件完成之後,給服務器寫一個結束標記
            void shutdownOutput() 禁用此套接字的輸出流。
            對於TCP套接字,將發送任何先前寫入的數據,然後發送TCP的正常連接終止序列。(結束標記)
         */
        socket.shutdownOutput();
        //6.使用Socket對象中的方法getInputStream,獲取網絡字節輸入流InputStream對象
        InputStream is = socket.getInputStream();
        //7.使用網絡字節輸入流InputStream對象的方法read,讀取服務器回寫的數據
        while ((len = is.read(bytes)) != -1) {
            System.out.println(new String(bytes, 0, len));
        }
        //8.釋放資源
        fis.close();
        socket.close();

    }
}

文件上傳優化–>自定義文件名

修改服務器端,讓上傳的文件按一定規則命名

代碼示例:服務器端

public class TCPServer {
    public static void main(String[] args) throws IOException {
        //1.判斷D盤是否有upload文件夾,沒有則創建
        File file = new File("D:\\upload");
        if(!file.exists()){
            file.mkdirs();
        }
        //2.創建服務器ServerSocket對象,構造方法和系統要指定的端口號
        ServerSocket server = new ServerSocket(9999);
        //3.使用ServerSocket對象accept獲取到請求的客戶端Socket對象
        Socket socket = server.accept();
        //4.使用Socket對象中的方法getInputStream,獲取網絡字節輸入流InputStream對象
        InputStream is = socket.getInputStream();
        /*
            自定義一個上傳文件的名稱
            規則:域名+毫秒值+隨機數
         */
        String fileName = "itcast"+ System.currentTimeMillis()+new Random().nextInt(999999)+".jpg";
        //5.創建本地字節輸出流FileOutputStream對象,構造方法綁定輸出的目的地
        FileOutputStream fos = new FileOutputStream(file+"\\"+fileName);
        //6.使用網絡字節輸入流InputStream對象中的方法read讀取客戶端上傳的文件
        byte[] bytes = new byte[1024];
        int len = 0;
        while((len = is.read(bytes))!=-1){
            //7.使用本地字節輸出流FileOutputStream對象中的方法write,把客戶端上傳的文件保存到服務器的硬盤上
            fos.write(bytes,0,len);
        }
        //8.使用Socket對象中的方法getOutputStream,獲取網絡字節輸出流OutputStream對象
        //9.使用write方法,給客戶端回寫“上傳成功”
        socket.getOutputStream().write("上傳成功".getBytes());
        //10.釋放資源
        fos.close();
        socket.close();
        server.close();
    }
}

文件上傳的多線程優化

由於效率太慢,使用多線程優化
代碼示例:客戶端

public class TCPClient {
    public static void main(String[] args) throws IOException {
        //1.創建本地字節輸入流FileInputStream對象,構造方法中綁定要讀取的數據源
        FileInputStream fis = new FileInputStream("D:\\1.jpg");
        //2.創建客戶端Socket對象,構造方法中綁定服務器的IP地址和端口號
        Socket socket = new Socket("127.0.0.1", 9999);
        //3.使用Socket對象中的方法getOutputStream,獲取網絡字節輸出流OutputStream對象
        OutputStream os = socket.getOutputStream();
        //4.使用本地字節輸入流FileInputStream對象中的方法read,讀取本地要上傳的文件
        byte[] bytes = new byte[1024];
        int len = 0;
        while ((len = fis.read(bytes)) != -1) {
            //5.使用網絡字節輸出流OutputStream對象中的方法write,把讀取的文件寫到服務器
            os.write(bytes, 0, len);
        }
        /*
            上傳文件完成之後,給服務器寫一個結束標記
            void shutdownOutput() 禁用此套接字的輸出流。
            對於TCP套接字,將發送任何先前寫入的數據,然後發送TCP的正常連接終止序列。(結束標記)
         */
        socket.shutdownOutput();
        //6.使用Socket對象中的方法getInputStream,獲取網絡字節輸入流InputStream對象
        InputStream is = socket.getInputStream();
        //7.使用網絡字節輸入流InputStream對象的方法read,讀取服務器回寫的數據
        while ((len = is.read(bytes)) != -1) {
            System.out.println(new String(bytes, 0, len));
        }
        //8.釋放資源
        fis.close();
        socket.close();

    }
}

服務器端

public class TCPServer {
    public static void main(String[] args) throws IOException {
        //1.判斷D盤是否有upload文件夾,沒有則創建
        File file = new File("D:\\upload");
        if(!file.exists()){
            file.mkdirs();
        }
        //2.創建服務器ServerSocket對象,構造方法和系統要指定的端口號
        ServerSocket server = new ServerSocket(9999);
        /*
            增加死循環,讓accept方法一直監聽客戶端
            有一個客戶端上傳,完成文件保存
         */
        while(true){
            //3.使用ServerSocket對象accept獲取到請求的客戶端Socket對象
            Socket socket = server.accept();
            /*
                爲了提高效率,增加多線程技術
                獲取一個客戶端,開啓一個線程,完成文件上傳
             */
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try{
                        //4.使用Socket對象中的方法getInputStream,獲取網絡字節輸入流InputStream對象
                        InputStream is = socket.getInputStream();
        /*
            自定義一個上傳文件的名稱
            規則:域名+毫秒值+隨機數
         */
                        String fileName = "itcast"+ System.currentTimeMillis()+new Random().nextInt(999999)+".jpg";
                        //5.創建本地字節輸出流FileOutputStream對象,構造方法綁定輸出的目的地
                        FileOutputStream fos = new FileOutputStream(file+"\\"+fileName);
                        //6.使用網絡字節輸入流InputStream對象中的方法read讀取客戶端上傳的文件
                        byte[] bytes = new byte[1024];
                        int len = 0;
                        while((len = is.read(bytes))!=-1){
                            //7.使用本地字節輸出流FileOutputStream對象中的方法write,把客戶端上傳的文件保存到服務器的硬盤上
                            fos.write(bytes,0,len);
                        }
                        //8.使用Socket對象中的方法getOutputStream,獲取網絡字節輸出流OutputStream對象
                        //9.使用write方法,給客戶端回寫“上傳成功”
                        socket.getOutputStream().write("上傳成功".getBytes());
                        //10.釋放資源
                        fos.close();
                        socket.close();
                    }catch (IOException e){
                        e.printStackTrace();
                    }
                }
            }).start();

        }
        //server.close();
    }
}

BS版本TCP程序的代碼實現(最終版本)

瀏覽器訪問客戶端的原理
在這裏插入圖片描述
注意:瀏覽器頁面的每張圖片,都需要請求一個服務器端來獲取圖片,所以需要使用while循環來持續監聽瀏覽器的請求信息,並使用多線程技術進行優化

代碼示例:

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

/*
    創建TCP程序BS版本的服務器
 */
public class TCPServer {
    public static void main(String[] args) throws IOException {
        //創建服務器對象ServerSocket,和系統要指定的端口號
        ServerSocket server = new ServerSocket(8080);
        /*
            服務器給客戶端回寫的html頁面中,如果包含了圖片,
            那麼客戶端就會獲取到html頁面中鏈接的圖片地址
            根據圖片地址,再請求服務器,讓服務器再讀取圖片,回寫到客戶端
            所以需要服務器一直監聽客戶端,可以使用死循環
         */
        while (true){
            //使用ServerSocket對象中的方法accept,監聽並獲取請求的客戶端對象socket(瀏覽器)
            Socket socket = server.accept();
            //爲了提高瀏覽器顯示圖片的效率,可以使用多線程技術,瀏覽器請求一次,開啓一個線程回寫一個文件
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        //使用Socket對象中的方法getInputStream,獲取網絡字節輸入流InputStream對象
                        InputStream is = socket.getInputStream();
                        //使用網絡字節輸入流InputStream對象中的方法read,讀取客戶端的請求信息
        /*byte[] bytes = new byte[1024];
        int len = is.read(bytes);
        System.out.println(new String(bytes, 0, len));*/
        /*
            服務器要做的事情:
                1.獲取到客戶端請求html頁面的地址
                2.使用本地字節輸入流讀取這個html頁面
                3.把頁面回寫到客戶端(瀏覽器)上顯示
         */
                        //把網絡字節輸入流,轉化爲網絡字符緩衝流
                        BufferedReader br = new BufferedReader(new InputStreamReader(is));
                        //讀取客戶端請求信息的第一行,包含了頁面的地址
                        String line = br.readLine();
                        //System.out.println(line);//GET /day12/web/index.html HTTP/1.1
                        //切割字符串,只要中間部分
                        String[] arr = line.split(" ");
                        //System.out.println(arr[1]);///day12/web/index.html
                        //對字符串進行截取,不要第一個/
                        String path = arr[1].substring(1);
                        System.out.println(path);//day12/web/index.html,就是html頁面的相對路徑
                        //創建本地的字節輸入流
                        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(path));
                        //獲取網絡字節輸出流,可以轉換爲字節緩衝流
                        BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());

        /*
            增加以下三行代碼告知客戶端,寫的是html網頁
            客戶端就會以網頁的形式打開文件顯示
         */
                        bos.write("HTTP/1.1 200 OK\r\n".getBytes());
                        bos.write("Content-Type:text/html\r\n".getBytes());
                        //必須寫入空行,否則瀏覽器不解析
                        bos.write("\r\n".getBytes());
                        //使用BufferedOutputStream對象中的方法read,讀取html文件
                        int len = 0;
                        while ((len = bis.read()) != -1) {
                            //使用BufferedOutputStream中的方法write,把讀取到的html文件寫到客戶端顯示
                            bos.write(len);
                        }
                        //釋放資源
                        bos.close();
                        bis.close();
                        socket.close();
                    }catch (IOException e){
                        e.printStackTrace();
                    }
                }
            }).start();
        }
        //server.close();
    }
}
/*
day12/web/index.html
day12/web/img/header.jpg
day12/web/img/big01.jpg
day12/web/img/small03.jpg
day12/web/img/footer.jpg
day12/web/img/title2.jpg
day12/web/img/ad.jpg
day12/web/img/middle01.jpg
day12/web/img/1.jpg
day12/web/img/logo2.png
 */
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章