Java基礎-網絡編程篇

1. 網絡編程基礎

1.1 軟件結構

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

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

兩種架構各有優點,可是不管哪一種架構,都離不開網絡的支持。網絡編程,就是在必定的協議下,實現兩臺計算機 的通訊的程序。web

1.2 網絡通訊協議

網絡通訊協議:通訊協議是對計算機必須遵照的規則,只有遵照這些規則,計算機之間才能進行通訊。這就 比如在道路中行駛的汽車必定要遵照交通規則同樣,協議中對數據的傳輸格式、傳輸速率、傳輸步驟等作了編程

統一規定,通訊雙方必須同時遵照,最終完成數據交換。數組

TCP/IP協議: 傳輸控制協議/因特網互聯協議( Transmission Control Protocol/Internet Protocol),是瀏覽器

Internet最基本、最普遍的協議。它定義了計算機如何連入因特網,以及數據如何在它們之間傳輸的標準。它安全

的內部包含一系列的用於處理數據通訊的協議,並採用了4層的分層模型,每一層都呼叫它的下一層所提供的服務器

協議來完成本身的需求。網絡

1.3 協議分類

​ 通訊的協議仍是比較複雜的, java.net 包中包含的類和接口,它們提供低層次的通訊細節。咱們能夠直接使用這些類和接口,來專一於網絡程序開發,而不用考慮通訊的細節。多線程

  • TCP:傳輸控制協議 (Transmission Control Protocol)。TCP協議是面向鏈接的通訊協議,即傳輸數據以前,

    在發送端和接收端創建邏輯鏈接,而後再傳輸數據,它提供了兩臺計算機之間可靠無差錯的數據傳輸。

    • 三次握手:TCP協議中,在發送數據的準備階段,客戶端與服務器之間的三次交互,以保證鏈接的可靠。
      1. 第一次握手,客戶端向服務器端發出鏈接請求,等待服務器確認。
      2. 第二次握手,服務器端向客戶端回送一個響應,通知客戶端收到了鏈接請求。
      3. 第三次握手,客戶端再次向服務器端發送確認信息,確認鏈接。整個交互過程以下圖所示。
    • 完成三次握手,鏈接創建後,客戶端和服務器就能夠開始進行數據傳輸了。因爲這種面向鏈接的特性,TCP協議能夠保證傳輸數據的安全,因此應用十分普遍,例以下載文件、瀏覽網頁等。
  • UDP:用戶數據報協議(User Datagram Protocol)。UDP協議是一個面向無鏈接的協議。傳輸數據時,不需

    要創建鏈接,無論對方端服務是否啓動,直接將數據、數據源和目的地都封裝在數據包中,直接發送。每一個

    數據包的大小限制在64k之內。它是不可靠協議,由於無鏈接,因此傳輸速度快,可是容易丟失數據。平常應

    用中,例如視頻會議、QQ聊天等。

1.4 網絡編程三要素

  1. 協議:計算機網絡通訊必須遵照的規則,已經介紹過了,再也不贅述。

  2. 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的分配愈加緊張。有資料顯示,全球IPv4地址在2011年2月分配完畢。 爲了擴大地址空間,擬經過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.1 、 localhost 。
  3. 端口號:

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

    ​ 若是說IP地址能夠惟一標識網絡中的設備,那麼端口號就能夠惟一標識設備中的進程(應用程序)了。

    ​ 端口號:用兩個字節表示的整數,它的取值範圍是0-65535。其中,0~1023之間的端口號用於一些知名的網絡服務和應用,普通的應用程序須要使用1024以上的端口號。若是端口號被另一個服務或應用所佔用,會致使當前程序啓動失敗。

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

2. TCP通訊程序

2.1 TCP通訊程序介紹

​ 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,讀取客戶端發送的數據。

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

  • 客戶端向服務器發送數據

    • 服務端代碼

      public static void main(String[] args) throws IOException {
          System.out.println("啓動服務端...");
          // 1. 建立ServerSocket對象
          ServerSocket server = new ServerSocket(6666);
          // 2. 接收客戶端的socket對象
          Socket socket = server.accept();
          // 3. 建立字節輸入流對象
          InputStream is = socket.getInputStream();
          // 4. 讀取數據
          int len = 0;
          byte[]bts = new byte[1024];
          len = is.read(bts);
          System.out.println(new String(bts,0,len));
          // 關閉網絡流
          is.close();
          // 關閉socket
          socket.close();
      
        }
    • 客戶端代碼

      public static void main(String[] args) throws IOException {
          System.out.println("啓動客戶端");
          // 1. 建立Socket對象
          Socket sk = new Socket("127.0.0.1",6666);
          // 2. 建立字節網絡流輸出對象
          OutputStream os = sk.getOutputStream();
          // 3. 輸出內容
          os.write("你好哈,服務端大大".getBytes());
          // 關閉網絡流
          os.close();
          // 關閉socket
          sk.close();
        }
  • 服務器向客戶端回寫數據

  • 服務端代碼

    public static void main(String[] args) throws IOException {
        System.out.println("啓動服務端...");
        // 1. 建立ServerSocket對象
        ServerSocket server = new ServerSocket(6666);
        // 2. 接收客戶端的socket對象
        Socket socket = server.accept();
        // 3. 建立字節輸入流對象
        InputStream is = socket.getInputStream();
        // 4. 讀取數據
        int len = 0;
        byte[]bts = new byte[1024];
        len = is.read(bts);
        System.out.println(new String(bts,0,len));
        // 【-------服務端回寫數據--------】
        // 1. 建立輸出網絡流
        OutputStream os = socket.getOutputStream();
        // 2. 寫入內容
        os.write("我很好,謝謝,思密達".getBytes());
        // 關閉socket
        socket.close();
        server.close();
    
      }
  • 客戶端代碼

    public static void main(String[] args) throws IOException {
        System.out.println("啓動客戶端");
        // 1. 建立Socket對象
        Socket sk = new Socket("127.0.0.1",6666);
        // 2. 建立字節網絡流輸出對象
        OutputStream os = sk.getOutputStream();
        // 3. 輸出內容
        os.write("你好哈,服務端大大".getBytes());
        // 【解析服務端的迴應】
        // 1. 建立輸入網絡流
        InputStream is = sk.getInputStream();
        // 2. 讀取數據
        int len = 0;
        byte[]bs = new byte[1024];
        len = is.read(bs);
        System.out.println(new String(bs,0,len));
        // 關閉socket
        sk.close();
      }

3. 文件上傳

3.1 文件上傳分析圖

3.2 基本實現

  • 服務端代碼

    public static void main(String[] args) throws IOException {
        System.out.println("啓動服務端...");
        // 1. 建立SeverSocket對象,用來處理客戶端請求
        ServerSocket sever = new ServerSocket(6666);
        // 2. 獲取socket對象,用來處理請求
        Socket socket = sever.accept();
        // 3. 建立本地輸入流對象,用來讀取客戶端發送的數據
        BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
        // 4. 建立本地輸出流對象,把客戶端上傳的文件寫入本地
        String fileName = System.currentTimeMillis() + "" + (new Random().nextInt(99999)) + ".jpg";
        File file = new File("d:\\upload");
        if(!file.exists()){
          file.mkdirs();
        }
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file +"\\"+ fileName));
        // 5. 循環讀取客戶端數據
        int len = 0;
        byte[]bts = new byte[1024];
        while((len=bis.read(bts))!=-1){
          bos.write(bts,0,len);
        }
        // 關閉資源
        bis.close();
        bos.close();
        socket.close();
        sever.close();
    
      }
  • 客戶端代碼

    public static void main(String[] args) throws IOException {
        System.out.println("啓動客戶端...");
        // 1. 建立Socket對象,鏈接服務端
        Socket socket = new Socket("127.0.0.1",6666);
        // 2. 建立本地輸入流對象,讀取本地文件
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("C:\\Users\\Bruce\\Desktop\\logo\\logo01.png"));
        // 3. 建立網絡輸出流對象,向服務端傳送數據
        BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
        // 4. 定義字節數組,設置每次讀取的字節
        int len = 0;
        byte[]bts = new byte[1024];
        // 5. 循環讀取本地文件數據,並向服務端輸出
        while ((len=bis.read(bts))!=-1){
          bos.write(bts,0,len);
        }
        System.out.println("客戶端:文件上傳完畢!");
        // 關閉資源
        bis.close();
        bos.close();
        socket.close();
      }

3.3 優化分析

  1. 循環接收問題

    • 問題:服務端,指保存一個文件就關閉了,以後的用戶沒法再上傳,這是不符合實際的,使用循環改進,能夠不斷 的接收不一樣用戶的文件

    • 代碼:

      while(true){
         Socket accept = serverSocket.accept();
         ......
      }
  2. 效率問題

    • 問題:服務端,在接收大文件時,可能耗費幾秒鐘的時間,此時不能接收其餘用戶上傳,因此,使用多線程技術優 化

    • 代碼:

      while(true){
          Socket accept = serverSocket.accept();
          // accept 交給子線程處理.
          new Thread(() ‐> {
              ......
              InputStream bis = accept.getInputStream();
              ......
          }).start();
      }
  3. 代碼

    public static void main(String[] args) throws IOException {
        System.out.println("啓動服務端...");
        // 1. 建立SeverSocket對象,用來處理客戶端請求
        ServerSocket sever = new ServerSocket(6666);
        while (true){
          new Thread(new Runnable() {
            @Override
            public void run() {
              try {
                // 2. 獲取socket對象,用來處理請求
                Socket socket = sever.accept();
                // 3. 建立本地輸入流對象,用來讀取客戶端發送的數據
                BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
                // 4. 建立本地輸出流對象,把客戶端上傳的文件寫入本地
                String fileName = System.currentTimeMillis() + "" + (new Random().nextInt(99999)) + ".jpg";
                File file = new File("d:\\upload");
                if(!file.exists()){
                  file.mkdirs();
                }
                BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file +"\\"+ fileName));
                // 5. 循環讀取客戶端數據
                int len = 0;
                byte[]bts = new byte[1024];
                while((len=bis.read(bts))!=-1){
                  bos.write(bts,0,len);
                }
                // 關閉資源
                bis.close();
                bos.close();
                socket.close();
                System.out.println(Thread.currentThread().getName()+":已經保存到本地!");
              }catch (IOException e) {
                e.printStackTrace();
              }
            }
          }).start();
        }
    
      }

3.4 回寫實現

  • 圖解

  • 代碼

    • 服務端

      public static void main(String[] args) throws IOException {
          System.out.println("啓動服務端...");
          // 1. 建立SeverSocket對象,用來處理客戶端請求
          ServerSocket sever = new ServerSocket(6666);
          while (true){
            new Thread(new Runnable() {
              @Override
              public void run() {
                try {
                  // 2. 獲取socket對象,用來處理請求
                  Socket socket = sever.accept();
                  // 3. 建立本地輸入流對象,用來讀取客戶端發送的數據
                  BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
                  // 4. 建立本地輸出流對象,把客戶端上傳的文件寫入本地
                  String fileName = System.currentTimeMillis() + "" + (new Random().nextInt(99999)) + ".jpg";
                  File file = new File("d:\\upload");
                  if(!file.exists()){
                    file.mkdirs();
                  }
                  BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file +"\\"+ fileName));
                  // 5. 循環讀取客戶端數據
                  int len = 0;
                  byte[]bts = new byte[1024];
                  while((len=bis.read(bts))!=-1){
                    bos.write(bts,0,len);
                  }
                  // 回寫客戶端----------------
                  OutputStream os = socket.getOutputStream();
                  os.write("服務器端:已經保存在服務端本地".getBytes());
                  // 關閉資源
                  bis.close();
                  bos.close();
                  os.close();
                  socket.close();
                  System.out.println(Thread.currentThread().getName()+":已經保存到本地!");
                }catch (IOException e) {
                  e.printStackTrace();
                }
              }
            }).start();
          }
      
        }
    • 客戶端

      public static void main(String[] args) throws IOException {
          System.out.println("啓動客戶端...");
          // 1. 建立Socket對象,鏈接服務端
          Socket socket = new Socket("127.0.0.1",6666);
          // 2. 建立本地輸入流對象,讀取本地文件
          BufferedInputStream bis = new BufferedInputStream(new FileInputStream("C:\\Users\\Bruce\\Desktop\\logo\\logo01.png"));
          // 3. 建立網絡輸出流對象,向服務端傳送數據
          BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
          // 4. 定義字節數組,設置每次讀取的字節
          int len = 0;
          byte[]bts = new byte[1024];
          // 5. 循環讀取本地文件數據,並向服務端輸出
          while ((len=bis.read(bts))!=-1){
            bos.write(bts,0,len);
          }
          bos.flush();
          // 關閉輸出流,通知服務端,寫出數據完畢
          socket.shutdownOutput();
          System.out.println("客戶端:文件上傳完畢!");
          // 接收服務返回的信息
          InputStream is = socket.getInputStream();
          while ((len=is.read(bts))!=-1){
            System.out.print(new String(bts,0,len));
          }
          // 關閉資源
          is.close();
          bis.close();
          socket.close();
        }

4. 模擬B/S服務器

  • 項目中有一個web項目,咱們寫服務端代碼,而後經過瀏覽器輸入地址127.0.0.1:8888/web/index.html

    訪問網頁。

  • 代碼:

    public class WebSever {
      public static void main(String[] args) throws IOException {
        // 建立ServerSocket對象
       ServerSocket server = new ServerSocket(8888);
       while (true){
         new Thread(new Runnable() {
           @Override
           public void run() {
            try{
              // 獲取socket對象
              Socket socket = server.accept();
              // 讀取接收的內容
              BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
              String line = reader.readLine(); // GET /web/index.html HTTP/1.1
              String path = line.split(" ")[1].substring(1);
              // 建立本地字節輸入流
              FileInputStream fis = new FileInputStream("day09_Socket\\" +path);
              // 建立網絡輸出流
              OutputStream os = socket.getOutputStream();
              // 寫入HTTP協議響應頭,固定寫法
              os.write("HTTP/1.1 200 OK\r\n".getBytes());
              os.write("Content‐Type:text/html\r\n".getBytes());
              // 必需要寫入空行,不然瀏覽器不解析
              os.write("\r\n".getBytes());
              int len = 0;
              byte[]bts = new byte[1024];
    
              while((len=fis.read(bts))!=-1){
                os.write(bts,0,len);
              }
              // 關閉資源
              os.close();
              fis.close();
              reader.close();
              socket.close();
            } catch (IOException e){
              e.printStackTrace();
            }
           }
         }).start();
       }
      }
    }
  • 頁面效果

相關文章
相關標籤/搜索