網絡編程

網絡編程

主要內容

  • 軟件架構CS/BS
  • 網絡通信三要素
  • TCP通信
  • Socket套接字
  • ServerSocket

網絡編程入門

1.1軟件結構

  • C/S結構 :全稱爲Client/Server結構,是指客戶端和服務器結構。常見程序有QQ、迅雷等軟件。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-PfniBeDu-1575978533390)(img/1_cs.jpg)]

B/S結構 :全稱爲Browser/Server結構,是指瀏覽器和服務器結構。常見瀏覽器有谷歌、火狐等。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-kVCZEMPS-1575978533391)(img/2_bs.jpg)]

兩種架構各有優勢,但是無論哪種架構,都離不開網絡的支持。網絡編程,就是在一定的協議下,實現兩臺計算機的通信的程序。

1.2 網絡通信協議

  • **網絡通信協議:**通過計算機網絡可以使多臺計算機實現連接,位於同一個網絡中的計算機在進行連接和通信時需要遵守一定的規則,這就好比在道路中行駛的汽車一定要遵守交通規則一樣。在計算機網絡中,這些連接和通信的規則被稱爲網絡通信協議,它對數據的傳輸格式、傳輸速率、傳輸步驟等做了統一規定,通信雙方必須同時遵守才能完成數據交換。

  • TCP/IP協議: 傳輸控制協議/因特網互聯協議( Transmission Control Protocol/Internet Protocol),是Internet最基本、最廣泛的協議。它定義了計算機如何連入因特網,以及數據如何在它們之間傳輸的標準。它的內部包含一系列的用於處理數據通信的協議,並採用了4層的分層模型,每一層都呼叫它的下一層所提供的協議來完成自己的需求。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-moUnDrOV-1575978533392)(img/3_tcp_ip.jpg)]

上圖中,TCP/IP協議中的四層分別是應用層、傳輸層、網絡層和鏈路層,每層分別負責不同的通信功能。
鏈路層:鏈路層是用於定義物理傳輸通道,通常是對某些網絡連接設備的驅動協議,例如針對光纖、網線提供的驅動。
網絡層:網絡層是整個TCP/IP協議的核心,它主要用於將傳輸的數據進行分組,將分組數據發送到目標計算機或者網絡。
運輸層:主要使網絡程序進行通信,在進行網絡通信時,可以採用TCP協議,也可以採用UDP協議。
應用層:主要負責應用程序的協議,例如HTTP協議、FTP協議等。

1.3 協議分類

通信的協議還是比較複雜的,java.net 包中包含的類和接口,它們提供低層次的通信細節。我們可以直接使用這些類和接口,來專注於網絡程序開發,而不用考慮通信的細節。

java.net 包中提供了兩種常見的網絡協議的支持:

  • UDP:用戶數據報協議(User Datagram Protocol)。UDP是無連接通信協議,即在數據傳輸時,數據的發送端和接收端不建立邏輯連接。簡單來說,當一臺計算機向另外一臺計算機發送數據時,發送端不會確認接收端是否存在,就會發出數據,同樣接收端在收到數據時,也不會向發送端反饋是否收到數據。

    由於使用UDP協議消耗資源小,通信效率高,所以通常都會用於音頻、視頻和普通數據的傳輸例如視頻會議都使用UDP協議,因爲這種情況即使偶爾丟失一兩個數據包,也不會對接收結果產生太大影響。

    但是在使用UDP協議傳送數據時,由於UDP的面向無連接性,不能保證數據的完整性,因此在傳輸重要數據時不建議使用UDP協議。UDP的交換過程如下圖所示。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-zaaSdiMD-1575978533393)(img\UDP通信圖解.bmp)]

特點:數據被限制在64kb以內,超出這個範圍就不能發送了。

數據報(Datagram):網絡傳輸的基本單位

  • TCP:傳輸控制協議 (Transmission Control Protocol)。TCP協議是面向連接的通信協議,即傳輸數據之前,在發送端和接收端建立邏輯連接,然後再傳輸數據,它提供了兩臺計算機之間可靠無差錯的數據傳輸。

    在TCP連接中必須要明確客戶端與服務器端,由客戶端向服務端發出連接請求,每次連接的創建都需要經過“三次握手”。

    • 三次握手:TCP協議中,在發送數據的準備階段,客戶端與服務器之間的三次交互,以保證連接的可靠。
      • 第一次握手,客戶端向服務器端發出連接請求,等待服務器確認。
      • 第二次握手,服務器端向客戶端回送一個響應,通知客戶端收到了連接請求。
      • 第三次握手,客戶端再次向服務器端發送確認信息,確認連接。整個交互過程如下圖所示。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-oTMwPmel-1575978533394)(img/4_tcp.jpg)]

? 完成三次握手,連接建立後,客戶端和服務器就可以開始進行數據傳輸了。由於這種面向連接的特性,TCP協議可以保證傳輸數據的安全,所以應用十分廣泛,例如下載文件、瀏覽網頁等。

1.4 網絡編程三要素

協議

  • **協議:**計算機網絡通信必須遵守的規則,已經介紹過了,不再贅述。

IP地址

  • IP地址:指互聯網協議地址(Internet Protocol Address),俗稱IP。IP地址用來給一個網絡中的計算機設備做唯一的編號。假如我們把“個人電腦”比作“一臺電話”的話,那麼“IP地址”就相當於“電話號碼”。

IP地址分類

  • IPv4:是一個32位的二進制數,通常被分爲4個字節,表示成a.b.c.d 的形式,例如192.168.65.100 。其中a、b、c、d都是0~255之間的十進制整數,那麼最多可以表示42億個。

  • IPv6:由於互聯網的蓬勃發展,IP地址的需求量愈來愈大,但是網絡地址資源有限,使得IP的分配越發緊張。

    爲了擴大地址空間,擬通過IPv6重新定義地址空間,採用128位地址長度,每16個字節一組,分成8組十六進制數,表示成ABCD:EF01:2345:6789:ABCD:EF01:2345:6789,號稱可以爲全世界的每一粒沙子編上一個網址,這樣就解決了網絡地址資源數量不夠的問題。

常用命令

  • 查看本機IP地址,在控制檯輸入:
ipconfig
  • 檢查網絡是否連通,在控制檯輸入:
ping 空格 IP地址
ping 220.181.57.216

特殊的IP地址

  • 本機IP地址:127.0.0.1localhost

端口號

網絡的通信,本質上是兩個進程(應用程序)的通信。每臺計算機都有很多的進程,那麼在網絡通信時,如何區分這些進程呢?

如果說IP地址可以唯一標識網絡中的設備,那麼端口號就可以唯一標識設備中的進程(應用程序)了。

  • **端口號:用兩個字節表示的整數,它的取值範圍是065535**。其中,01023之間的端口號用於一些知名的網絡服務和應用,普通的應用程序需要使用1024以上的端口號。如果端口號被另外一個服務或應用所佔用,會導致當前程序啓動失敗。

利用協議+IP地址+端口號 三元組合,就可以標識網絡中的進程了,那麼進程間的通信就可以利用這個標識與其它進程進行交互。

TCP通信程序

2.1 概述

TCP通信能實現兩臺計算機之間的數據交互,通信的兩端,要嚴格區分爲客戶端(Client)與服務端(Server)。

兩端通信時步驟:

  1. 服務端程序,需要事先啓動,等待客戶端的連接。
  2. 客戶端主動連接服務器端,連接成功才能通信。服務端不可以主動連接客戶端。

在Java中,提供了兩個類用於實現TCP通信程序:

  1. 客戶端:java.net.Socket 類表示。創建Socket對象,向服務端發出連接請求,服務端響應請求,兩者建立連接開始通信。
  2. 服務端:java.net.ServerSocket 類表示。創建ServerSocket對象,相當於開啓一個服務,並等待客戶端的連接。

2.2 Socket類

Socket 類:該類實現客戶端套接字,套接字指的是兩臺設備之間通訊的端點。

構造方法

  • public Socket(String host, int port) :創建套接字對象並將其連接到指定主機上的指定端口號。如果指定的host是null ,則相當於指定地址爲回送地址。

    小貼士:回送地址(127.x.x.x) 是本機回送地址(Loopback Address),主要用於網絡軟件測試以及本地機進程間通信,無論什麼程序,一旦使用回送地址發送數據,立即返回,不進行任何網絡傳輸。

構造舉例,代碼如下:

Socket client = new Socket("127.0.0.1", 6666);

成員方法

  • public InputStream getInputStream() : 返回此套接字的輸入流。

    • 如果此Scoket具有相關聯的通道,則生成的InputStream 的所有操作也關聯該通道。
    • 關閉生成的InputStream也將關閉相關的Socket。
  • public OutputStream getOutputStream() : 返回此套接字的輸出流。

    • 如果此Scoket具有相關聯的通道,則生成的OutputStream 的所有操作也關聯該通道。
    • 關閉生成的OutputStream也將關閉相關的Socket。
  • public void close() :關閉此套接字。

    • 一旦一個socket被關閉,它不可再使用。
    • 關閉此socket也將關閉相關的InputStream和OutputStream 。
  • public void shutdownOutput() : 禁用此套接字的輸出流。

    • 任何先前寫出的數據將被髮送,隨後終止輸出流。

    2.3 ServerSocket類

ServerSocket類:這個類實現了服務器套接字,該對象等待通過網絡的請求。

構造方法

  • public ServerSocket(int port) :使用該構造方法在創建ServerSocket對象時,就可以將其綁定到一個指定的端口號上,參數port就是端口號。

構造舉例,代碼如下:

ServerSocket server = new ServerSocket(6666);

成員方法

  • public Socket accept() :偵聽並接受連接,返回一個新的Socket對象,用於和客戶端實現通信。該方法會一直阻塞直到建立連接。

2.4 簡單的TCP網絡程序

TCP通信分析圖解

  1. 【服務端】啓動,創建ServerSocket對象,等待連接。
  2. 【客戶端】啓動,創建Socket對象,請求連接。
  3. 【服務端】接收連接,調用accept方法,並返回一個Socket對象。
  4. 【客戶端】Socket對象,獲取OutputStream,向服務端寫出數據。
  5. 【服務端】Scoket對象,獲取InputStream,讀取客戶端發送的數據。

到此,客戶端向服務端發送數據成功。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-5zR3vaDP-1575978533395)(img/5_簡單通信.jpg)]

自此,服務端向客戶端回寫數據。

  1. 【服務端】Socket對象,獲取OutputStream,向客戶端回寫數據。
  2. 【客戶端】Scoket對象,獲取InputStream,解析回寫數據。
  3. 【客戶端】釋放資源,斷開連接。

客戶端向服務器發送數據

服務端實現:

public class ServerTCP {
    public static void main(String[] args) throws IOException {
        System.out.println("服務端啓動 , 等待連接 .... ");
        // 1.創建 ServerSocket對象,綁定端口,開始等待連接
        ServerSocket ss = new ServerSocket(6666);
        // 2.接收連接 accept 方法, 返回 socket 對象.
        Socket server = ss.accept();
        // 3.通過socket 獲取輸入流
        InputStream is = server.getInputStream();
        // 4.一次性讀取數據
      	// 4.1 創建字節數組
        byte[] b = new byte[1024];
      	// 4.2 據讀取到字節數組中.
        int len = is.read(b)// 4.3 解析數組,打印字符串信息
        String msg = new String(b, 0, len);
        System.out.println(msg);
        //5.關閉資源.
        is.close();
        server.close();
    }
}

客戶端實現:

public class ClientTCP {
	public static void main(String[] args) throws Exception {
		System.out.println("客戶端 發送數據");
		// 1.創建 Socket ( ip , port ) , 確定連接到哪裏.
		Socket client = new Socket("localhost", 6666);
		// 2.獲取流對象 . 輸出流
		OutputStream os = client.getOutputStream();
		// 3.寫出數據.
		os.write("你好麼? tcp ,我來了".getBytes());
		// 4. 關閉資源 .
		os.close();
		client.close();
	}
}

服務器向客戶端回寫數據

服務端實現:

public class ServerTCP {
    public static void main(String[] args) throws IOException {
        System.out.println("服務端啓動 , 等待連接 .... ");
        // 1.創建 ServerSocket對象,綁定端口,開始等待連接
        ServerSocket ss = new ServerSocket(6666);
        // 2.接收連接 accept 方法, 返回 socket 對象.
        Socket server = ss.accept();
        // 3.通過socket 獲取輸入流
        InputStream is = server.getInputStream();
        // 4.一次性讀取數據
      	// 4.1 創建字節數組
        byte[] b = new byte[1024];
      	// 4.2 據讀取到字節數組中.
        int len = is.read(b)// 4.3 解析數組,打印字符串信息
        String msg = new String(b, 0, len);
        System.out.println(msg);
      	// =================回寫數據=======================
      	// 5. 通過 socket 獲取輸出流
      	 OutputStream out = server.getOutputStream();
      	// 6. 回寫數據
      	 out.write("我很好,謝謝你".getBytes());
      	// 7.關閉資源.
      	out.close();
        is.close();
        server.close();
    }
}

客戶端實現:

public class ClientTCP {
	public static void main(String[] args) throws Exception {
		System.out.println("客戶端 發送數據");
		// 1.創建 Socket ( ip , port ) , 確定連接到哪裏.
		Socket client = new Socket("localhost", 6666);
		// 2.通過Scoket,獲取輸出流對象 
		OutputStream os = client.getOutputStream();
		// 3.寫出數據.
		os.write("你好麼? tcp ,我來了".getBytes());
      	// ==============解析回寫=========================
      	// 4. 通過Scoket,獲取 輸入流對象
      	InputStream in = client.getInputStream();
      	// 5. 讀取數據數據
      	byte[] b = new byte[100];
      	int len = in.read(b);
      	System.out.println(new String(b, 0, len));
		// 6. 關閉資源 .
      	in.close();
		os.close();
		client.close();
	}
}

綜合案例

3.1 文件上傳案例

文件上傳分析圖解

  1. 【客戶端】輸入流,從硬盤讀取文件數據到程序中。
  2. 【客戶端】輸出流,寫出文件數據到服務端。
  3. 【服務端】輸入流,讀取文件數據到服務端程序。
  4. 【服務端】輸出流,寫出文件數據到服務器硬盤中。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-XyzC61gL-1575978533396)(img/6_upload.jpg)]

基本實現

服務端實現:

public class FileUpload_Server {
    public static void main(String[] args) throws IOException {
        System.out.println("服務器 啓動.....  ");
        // 1. 創建服務端ServerSocket
      	ServerSocket serverSocket = new ServerSocket(6666);
  		// 2. 建立連接 
        Socket accept = serverSocket.accept();
      	// 3. 創建流對象
      	// 3.1 獲取輸入流,讀取文件數據
        BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
        // 3.2 創建輸出流,保存到本地 .
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy.jpg"));
		// 4. 讀寫數據
        byte[] b = new byte[1024 * 8];
        int len;
        while ((len = bis.read(b)) != -1) {
            bos.write(b, 0, len);
        }
        //5. 關閉 資源
        bos.close();
        bis.close();
        accept.close();
        System.out.println("文件上傳已保存");
    }
}

客戶端實現:

public class FileUPload_Client {
	public static void main(String[] args) throws IOException {
        // 1.創建流對象
        // 1.1 創建輸入流,讀取本地文件  
        BufferedInputStream bis  = new BufferedInputStream(new FileInputStream("test.jpg"));
        // 1.2 創建輸出流,寫到服務端 
        Socket socket = new Socket("localhost", 6666);
        BufferedOutputStream   bos   = new BufferedOutputStream(socket.getOutputStream());

        //2.寫出數據. 
        byte[] b  = new byte[1024 * 8 ];
        int len ; 
        while (( len  = bis.read(b))!=-1) {
            bos.write(b, 0, len);
            bos.flush();
        }
        System.out.println("文件發送完畢");
        // 3.釋放資源

        bos.close(); 
        socket.close();
        bis.close(); 
        System.out.println("文件上傳完畢 ");
	}
}

文件上傳優化分析

  1. 文件名稱寫死的問題

    服務端,保存文件的名稱如果寫死,那麼最終導致服務器硬盤,只會保留一個文件,建議使用系統時間優化,保證文件名稱唯一,代碼如下:

FileOutputStream fis = new FileOutputStream(System.currentTimeMillis()+".jpg") // 文件名稱
BufferedOutputStream bos = new BufferedOutputStream(fis);
  1. 循環接收的問題

    服務端,指保存一個文件就關閉了,之後的用戶無法再上傳,這是不符合實際的,使用循環改進,可以不斷的接收不同用戶的文件,代碼如下:

// 每次接收新的連接,創建一個Socket
whiletrue{
    Socket accept = serverSocket.accept();
    ......
}
  1. 效率問題

    服務端,在接收大文件時,可能耗費幾秒鐘的時間,此時不能接收其他用戶上傳,所以,使用多線程技術優化,代碼如下:

whiletrue{
    Socket accept = serverSocket.accept();
    // accept 交給子線程處理.
    new Thread(() -> {
      	......
        InputStream bis = accept.getInputStream();
      	......
    }).start();
}

優化實現

public class FileUpload_Server {
    public static void main(String[] args) throws IOException {
        System.out.println("服務器 啓動.....  ");
        // 1. 創建服務端ServerSocket
        ServerSocket serverSocket = new ServerSocket(6666);
      	// 2. 循環接收,建立連接
        while (true) {
            Socket accept = serverSocket.accept();
          	/* 
          	3. socket對象交給子線程處理,進行讀寫操作
               Runnable接口中,只有一個run方法,使用lambda表達式簡化格式
            */
            new Thread(() -> {
                try (
                    //3.1 獲取輸入流對象
                    BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
                    //3.2 創建輸出流對象, 保存到本地 .
                    FileOutputStream fis = new FileOutputStream(System.currentTimeMillis() + ".jpg");
                    BufferedOutputStream bos = new BufferedOutputStream(fis);) {
                    // 3.3 讀寫數據
                    byte[] b = new byte[1024 * 8];
                    int len;
                    while ((len = bis.read(b)) != -1) {
                      bos.write(b, 0, len);
                    }
                    //4. 關閉 資源
                    bos.close();
                    bis.close();
                    accept.close();
                    System.out.println("文件上傳已保存");
                } catch (IOException e) {
                  	e.printStackTrace();
                }
            }).start();
        }
    }
}

信息回寫分析圖解

前四步與基本文件上傳一致.

  1. 【服務端】獲取輸出流,回寫數據。
  2. 【客戶端】獲取輸入流,解析回寫數據。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-zog1SQZH-1575978533398)(img/6_upload2.jpg)]

回寫實現

public class FileUpload_Server {
    public static void main(String[] args) throws IOException {
        System.out.println("服務器 啓動.....  ");
        // 1. 創建服務端ServerSocket
        ServerSocket serverSocket = new ServerSocket(6666);
        // 2. 循環接收,建立連接
        while (true) {
            Socket accept = serverSocket.accept();
          	/*
          	3. socket對象交給子線程處理,進行讀寫操作
               Runnable接口中,只有一個run方法,使用lambda表達式簡化格式
            */
            new Thread(() -> {
                try (
                    //3.1 獲取輸入流對象
                    BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
                    //3.2 創建輸出流對象, 保存到本地 .
                    FileOutputStream fis = new FileOutputStream(System.currentTimeMillis() + ".jpg");
                    BufferedOutputStream bos = new BufferedOutputStream(fis);
                ) {
                    // 3.3 讀寫數據
                    byte[] b = new byte[1024 * 8];
                    int len;
                    while ((len = bis.read(b)) != -1) {
                        bos.write(b, 0, len);
                    }

                    // 4.=======信息回寫===========================
                    System.out.println("back ........");
                    OutputStream out = accept.getOutputStream();
                    out.write("上傳成功".getBytes());
                    out.close();
                    //================================

                    //5. 關閉 資源
                    bos.close();
                    bis.close();
                    accept.close();
                    System.out.println("文件上傳已保存");
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

客戶端實現:

public class FileUpload_Client {
    public static void main(String[] args) throws IOException {
        // 1.創建流對象
        // 1.1 創建輸入流,讀取本地文件
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("test.jpg"));
        // 1.2 創建輸出流,寫到服務端
        Socket socket = new Socket("localhost", 6666);
        BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());

        //2.寫出數據.
        byte[] b  = new byte[1024 * 8 ];
        int len ;
        while (( len  = bis.read(b))!=-1) {
            bos.write(b, 0, len);
        }
      	// 關閉輸出流,通知服務端,寫出數據完畢
        socket.shutdownOutput();
        System.out.println("文件發送完畢");
        // 3. =====解析回寫============
        InputStream in = socket.getInputStream();
        byte[] back = new byte[20];
        in.read(back);
        System.out.println(new String(back));
        in.close();
        // ============================

        // 4.釋放資源
        socket.close();
        bis.close();
    }
}

3.2 模擬B\S服務器(擴展知識點)

模擬網站服務器,使用瀏覽器訪問自己編寫的服務端程序,查看網頁效果。

案例分析

  1. 準備頁面數據,web文件夾。

    複製到我們Module中,比如複製到day08中

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-R4CV15qL-1575978533399)(img\複製.png)]

  2. 我們模擬服務器端,ServerSocket類監聽端口,使用瀏覽器訪問

    public static void main(String[] args) throws IOException {
        	ServerSocket server = new ServerSocket(8000);
        	Socket socket = server.accept();
        	InputStream in = socket.getInputStream();
       	    byte[] bytes = new byte[1024];
        	int len = in.read(bytes);
        	System.out.println(new String(bytes,0,len));
        	socket.close();
        	server.close();
    }
    

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-5d0xTupu-1575978533399)(img\無法訪問.jpg)]

  3. 服務器程序中字節輸入流可以讀取到瀏覽器發來的請求信息

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-GJczbXFA-1575978533400)(img\讀取訪問信息.jpg)]

GET/web/index.html HTTP/1.1是瀏覽器的請求消息。/web/index.html爲瀏覽器想要請求的服務器端的資源,使用字符串切割方式獲取到請求的資源。

//轉換流,讀取瀏覽器請求第一行
BufferedReader readWb = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String requst = readWb.readLine();
//取出請求資源的路徑
String[] strArr = requst.split(" ");
//去掉web前面的/
String path = strArr[1].substring(1);
System.out.println(path);

案例實現

服務端實現:

public class SerDemo {
    public static void main(String[] args) throws IOException {
        System.out.println("服務端  啓動 , 等待連接 .... ");
        // 創建ServerSocket 對象
        ServerSocket server = new ServerSocket(8888);
        Socket socket = server.accept();
        // 轉換流讀取瀏覽器的請求消息
        BufferedReader readWb = new
        BufferedReader(new InputStreamReader(socket.getInputStream()));
        String requst = readWb.readLine();
        // 取出請求資源的路徑
        String[] strArr = requst.split(" ");
        // 去掉web前面的/
        String path = strArr[1].substring(1);
        // 讀取客戶端請求的資源文件
        FileInputStream fis = new FileInputStream(path);
        byte[] bytes= new byte[1024];
        int len = 0 ;
        // 字節輸出流,將文件寫會客戶端
        OutputStream out = socket.getOutputStream();
        // 寫入HTTP協議響應頭,固定寫法
        out.write("HTTP/1.1 200 OK\r\n".getBytes());
        out.write("Content-Type:text/html\r\n".getBytes());
        // 必須要寫入空行,否則瀏覽器不解析
        out.write("\r\n".getBytes());
        while((len = fis.read(bytes))!=-1){
            out.write(bytes,0,len);
        }
        fis.close();
        out.close();
        readWb.close();	
        socket.close();
        server.close();
    }
}

小貼士:不同的瀏覽器,內核不一樣,解析效果有可能不一樣。

發現瀏覽器中出現很多的叉子,說明瀏覽器沒有讀取到圖片信息導致。

瀏覽器工作原理是遇到圖片會開啓一個線程進行單獨的訪問,因此在服務器端加入線程技術。

public class ServerDemo {
    public static void main(String[] args) throws IOException {
        ServerSocket server = new ServerSocket(8888);
        while(true){
            Socket socket = server.accept();
            new Thread(new Web(socket)).start();
        }
    }
    static class Web implements Runnable{
        private Socket socket;

        public Web(Socket socket){
            this.socket=socket;
        }

        public void run() {
            try{
                //轉換流,讀取瀏覽器請求第一行
                BufferedReader readWb = new
                        BufferedReader(new InputStreamReader(socket.getInputStream()));
                String requst = readWb.readLine();
                //取出請求資源的路徑
                String[] strArr = requst.split(" ");
                System.out.println(Arrays.toString(strArr));
                String path = strArr[1].substring(1);
                System.out.println(path);

                FileInputStream fis = new FileInputStream(path);
                System.out.println(fis);
                byte[] bytes= new byte[1024];
                int len = 0 ;
                //向瀏覽器 回寫數據
                OutputStream out = socket.getOutputStream();
                out.write("HTTP/1.1 200 OK\r\n".getBytes());
                out.write("Content-Type:text/html\r\n".getBytes());
                out.write("\r\n".getBytes());
                while((len = fis.read(bytes))!=-1){
                    out.write(bytes,0,len);
                }
                fis.close();
                out.close();
                readWb.close();
                socket.close();
            }catch(Exception ex){

            }
        }
    }

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章