ch21_network_programming

小兵学习笔记發表於2024-09-13
  • 第21章 網路程式設計
    • 網路的相關概念
      • 網路通訊
      • 網路
      • ip 地址
      • ipv4 地址分類
      • 域名
      • 網路通訊協議
      • TCP 和 UDP
    • InetAddress 類
      • 相關方法
      • 應用案例
    • Socket
      • 基本介紹
    • TCP 網路通訊程式設計
      • 基本介紹
      • 應用案例1(使用位元組流)
      • 應用案例2(使用位元組流)
      • 應用案例3(使用字元流)
      • 應用案例4
      • netstat 指令
      • TCP 網路通訊
    • UDP 網路通訊程式設計
      • 基本介紹
      • 基本流程
      • 應用案例
    • 本章作業

第21章 網路程式設計

網路的相關概念

網路通訊

  1. 概念:兩臺裝置之間透過網路實現資料傳輸
  2. 網路通訊:將資料透過網路從一臺裝置傳輸到另一臺裝置
  3. java.net包下提供了一系列的類或介面,供程式設計師使用,完成網路通訊

網路

  1. 概念:兩臺或多臺裝置透過一定物理裝置連線起來構成了網路
  2. 根據網路的覆蓋範圍不同,對網路進行分類:
  • 區域網:覆蓋範圍最小,僅僅覆蓋一個教室或一個機房
  • 都會網路:覆蓋範圍較大,可以覆蓋一個城市
  • 廣域網:覆蓋範圍最大,可以覆蓋全國,甚至全球,全球資訊網是廣域網的代表

ip 地址

  1. 概念:用於唯一標識網路中的每臺計算機/主機

  2. 檢視ip地址: ipconfig

  3. ip地址的表示形式: 點分十進位制XX.XX.XX.XX

  4. 每一個十進位制數的範圍:0~255

  5. ip地址的組成=網路地址+主機地址,比如:192.168.16.69

  6. iPv6是網際網路工程任務組設計的用於替代IPv4的下一代IP協議,其地址數量號稱可以為全世界的每一粒沙子編上一個地址。

  7. 由於IPv4最大的問題在於網路地址資源有限,嚴重製約了網際網路的應用和發展。IPv6的使用,不僅能解決網路地址資源數量的問題,而且也解決了多種接入裝置連入網際網路的障礙。

ipv4 地址分類

特殊的:127.0.0.1表示本機地址

域名

  1. www.baidu.com
  2. 好處: 為了方便記憶,解決記ip的困難
  3. 概念: 將ip地址對映成域名,這裡怎麼對映上——HTTP協議

埠號

  1. 概念: 用於標識計算機上某個特定的網路程式
  2. 表示形式: 以整數形式,埠範圍0~65535 [2個位元組表示埠 0~2^16-1]
  3. 0~1024已經被佔用, 比如ssh 22, ftp 21, smtp 25 http 80
  4. 常見的網路程式埠號
    • tomcat: 8080
    • mysql: 3306
    • oracle: 1521
    • sqlserver: 1433

網路通訊協議

協議(tcp/ip)

TCP/IP (Transmission Control Protocol/Internet Protocol) 的簡寫,中文譯名為傳輸控制協議/因特網互聯協議,又叫網路通訊協議,這個協議是lnternet最基本的協議、Internet國際網際網路絡的基礎,簡單地說,就是由網路層的IP協議和傳輸層的TCP協議組成的。

TCP 和 UDP

TCP協議:傳輸控制協議

  1. 使用TCP協議前,須先建立TCP連線,形成傳輸資料通道
  2. 傳輸前,採用“三次握手"方式,是可靠的
  3. TCP協議進行通訊的兩個應用程序: 客戶端、服務端
  4. 在連線中可進行大資料量的傳輸
  5. 傳輸完畢,需釋放已建立的連線, 效率低

UDP協議:使用者資料協議

  1. 將資料、源、目的封裝成資料包,不需要建立連線
  2. 每個資料包的大小限制在64K內,不適合傳輸大量資料
  3. 因無需連線,故是不可靠的
  4. 傳送資料結束時無需釋放資源(因為不是面向連線的),速度快

InetAddress 類

相關方法

  1. 獲取本機InetAddress物件getLocalHost
  2. 根據指定主機名/域名獲取ip地址物件getByName
  3. 獲取InetAddress物件的主機名getHostName
  4. 獲取InetAddress物件的地址getHostAddress

應用案例

編寫程式碼,獲取計算機的主機名和IP 地址相關API

package com.hspedu.api;
import java.net.InetAddress;
import java.net.UnknownHostException;

/**
 * 演示InetAddress 類的使用
 */
public class API_ {
    public static void main(String[] args) throws UnknownHostException {

        //1. 獲取本機的InetAddress 物件
        InetAddress localHost = InetAddress.getLocalHost();
        System.out.println(localHost);//DESKTOP-S4MP84S/192.168.12.1

        //2. 根據指定主機名 獲取 InetAddress物件
        InetAddress host1 = InetAddress.getByName("DESKTOP-S4MP84S");
        System.out.println("host1=" + host1);//DESKTOP-S4MP84S/192.168.12.1

        //3. 根據域名返回 InetAddress物件, 比如 www.baidu.com 對應
        InetAddress host2 = InetAddress.getByName("www.baidu.com");
        System.out.println("host2=" + host2);//www.baidu.com / 110.242.68.4

        //4. 透過 InetAddress 物件,獲取對應的地址
        String hostAddress = host2.getHostAddress();//IP 110.242.68.4
        System.out.println("host2 對應的ip = " + hostAddress);//110.242.68.4

        //5. 透過 InetAddress 物件,獲取對應的主機名/或者的域名
        String hostName = host2.getHostName();
        System.out.println("host2對應的主機名/域名=" + hostName); // www.baidu.com
    }
}

Socket

基本介紹

  1. 套接字(Socket)開發網路應用程式被廣泛採用,以至於成為事實上的標準。
  2. 通訊的兩端都要有Socket,是兩臺機器間通訊的端點。
  3. 網路通訊其實就是Socket間的通訊。
  4. Socket允許程式把網路連線當成一個流,資料在兩個Socket間透過IO傳輸。
  5. 一般主動發起通訊的應用程式屬客戶端,等待通訊請求的為服務端。

示意圖

TCP 網路通訊程式設計

基本介紹

  1. 基於客戶端—服務端的網路通訊
  2. 底層使用的是TCP/IP協議
  3. 應用場景舉例: 客戶端傳送資料,服務端接受並顯示控制檯
  4. 基於Socket的TCP程式設計

最後需要關閉socket,不然連結太多會出現問題。

應用案例1(使用位元組流)

  1. 編寫一個伺服器端,和一個客戶端
  2. 伺服器端在9999埠監聽
  3. 客戶端連線到伺服器端,傳送"hello, server",然後退出
  4. 伺服器端接收到客戶端傳送的資訊,輸出,並退出

ServerSocket 可以透過 accept() 返回多個Socket[多個客戶端連線伺服器的併發]

package com.hspedu.socket;

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;

/**
 * 客戶端,傳送 "hello, server" 給服務端
 */
public class SocketTCP01Client {
    public static void main(String[] args) throws IOException {
        //思路
        //1. 連線服務端 (ip , 埠)
        //解讀: 連線本機的 9999埠, 如果連線成功,返回Socket物件
        // 如果連結網路,第一個引數可以改為IP
        Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
        System.out.println("客戶端 socket返回=" + socket.getClass());
        //2. 連線上後,生成Socket, 透過socket.getOutputStream()
        //   得到 和 socket物件關聯的輸出流物件
        OutputStream outputStream = socket.getOutputStream();
        //3. 透過輸出流,寫入資料到 資料通道
        outputStream.write("hello, server".getBytes());
        //4. 關閉流物件和socket, 必須關閉
        outputStream.close();
        socket.close();
        System.out.println("客戶端退出.....");
    }
}
package com.hspedu.socket;

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

/**
 * 服務端
 */
public class SocketTCP01Server {
    public static void main(String[] args) throws IOException {
        //思路
        //1. 在本機 的9999埠監聽, 等待連線
        //   細節: 要求在本機沒有其它服務在監聽9999
        //   細節:這個 ServerSocket 可以透過 accept() 返回多個Socket[多個客戶端連線伺服器的併發]
        ServerSocket serverSocket = new ServerSocket(9999);
        System.out.println("服務端,在9999埠監聽,等待連線..");
        //2. 當沒有客戶端連線9999埠時,程式會 阻塞, 等待連線
        //   如果有客戶端連線,則會返回Socket物件,程式繼續

        Socket socket = serverSocket.accept();

        System.out.println("服務端 socket =" + socket.getClass());
        //
        //3. 透過socket.getInputStream() 讀取客戶端寫入到資料通道的資料, 顯示
        InputStream inputStream = socket.getInputStream();
        //4. IO讀取
        byte[] buf = new byte[1024];
        int readLen = 0;
        while ((readLen = inputStream.read(buf)) != -1) {
            System.out.println(new String(buf, 0, readLen));//根據讀取到的實際長度,顯示內容.
        }
        //5.關閉流和socket
        inputStream.close();
        socket.close();
        serverSocket.close();// 最後也需要關閉
    }
}

應用案例2(使用位元組流)

  1. 編寫一個服務端,和一個客戶端。
  2. 伺服器端在9999埠監聽。
  3. 客戶端連線到服務端,傳送"hello, server",並接收伺服器端回發的"hello,client",再退出。
  4. 伺服器端接收到客戶端傳送的資訊,輸出,併傳送"hello, client".再退出。

注意:設定結束標記。確保輸出結束。

package com.hspedu.socket;

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

/**
 * 服務端
 */
@SuppressWarnings({"all"})
public class SocketTCP02Server {
    public static void main(String[] args) throws IOException {
        //思路
        //1. 在本機 的9999埠監聽, 等待連線
        //   細節: 要求在本機沒有其它服務在監聽9999
        //   細節:這個 ServerSocket 可以透過 accept() 返回多個Socket[多個客戶端連線伺服器的併發]
        ServerSocket serverSocket = new ServerSocket(9999);
        System.out.println("服務端,在9999埠監聽,等待連線..");
        //2. 當沒有客戶端連線9999埠時,程式會 阻塞, 等待連線
        //   如果有客戶端連線,則會返回Socket物件,程式繼續

        Socket socket = serverSocket.accept();

        System.out.println("服務端 socket =" + socket.getClass());
        //
        //3. 透過socket.getInputStream() 讀取客戶端寫入到資料通道的資料, 顯示
        InputStream inputStream = socket.getInputStream();
        //4. IO讀取
        byte[] buf = new byte[1024];
        int readLen = 0;
        while ((readLen = inputStream.read(buf)) != -1) {
            System.out.println(new String(buf, 0, readLen));//根據讀取到的實際長度,顯示內容.
        }
        //5. 獲取socket相關聯的輸出流
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write("hello, client".getBytes());
        // 設定結束標記
        socket.shutdownOutput();

        //6.關閉流和socket
        outputStream.close();
        inputStream.close();
        socket.close();
        serverSocket.close();//關閉

    }
}
package com.hspedu.socket;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;

/**
 * 客戶端,傳送 "hello, server" 給服務端
 */
@SuppressWarnings({"all"})
public class SocketTCP02Client {
    public static void main(String[] args) throws IOException {
        //思路
        //1. 連線服務端 (ip , 埠)
        //解讀: 連線本機的 9999埠, 如果連線成功,返回Socket物件
        Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
        System.out.println("客戶端 socket返回=" + socket.getClass());
        //2. 連線上後,生成Socket, 透過socket.getOutputStream()
        //   得到 和 socket物件關聯的輸出流物件
        OutputStream outputStream = socket.getOutputStream();
        //3. 透過輸出流,寫入資料到 資料通道
        outputStream.write("hello, server".getBytes());
        //   設定結束標記
        socket.shutdownOutput();

        //4. 獲取和socket關聯的輸入流. 讀取資料(位元組),並顯示
        InputStream inputStream = socket.getInputStream();
        byte[] buf = new byte[1024];
        int readLen = 0;
        while ((readLen = inputStream.read(buf)) != -1) {
            System.out.println(new String(buf, 0, readLen));
        }

        //5. 關閉流物件和socket, 必須關閉
        inputStream.close();
        outputStream.close();
        socket.close();
        System.out.println("客戶端退出.....");
    }
}

應用案例3(使用字元流)

  1. 編寫一個服務端,和一個客戶端
  2. 服務端在9999埠監聽
  3. 客戶端連線到服務端,傳送"hello, server”,並接收服務端回發的“hello,client",,再退出
  4. 服務端接收到客戶端傳送的資訊,輸出,併傳送"hello, client",再退出

這裡結束標記也可以採用 writer.newLine();,但是這要求對方讀取必須使用 readLine()

package com.hspedu.socket;

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

/**
 * 客戶端,傳送 "hello, server" 給服務端, 使用字元流
 */
@SuppressWarnings({"all"})
public class SocketTCP03Client {
    public static void main(String[] args) throws IOException {
        //思路
        //1. 連線服務端 (ip , 埠)
        //解讀: 連線本機的 9999埠, 如果連線成功,返回Socket物件
        Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
        System.out.println("客戶端 socket返回=" + socket.getClass());
        //2. 連線上後,生成Socket, 透過socket.getOutputStream()
        //   得到 和 socket物件關聯的輸出流物件
        OutputStream outputStream = socket.getOutputStream();
        //3. 透過輸出流,寫入資料到 資料通道, 使用字元流
        BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
        bufferedWriter.write("hello, server 字元流");
        bufferedWriter.newLine();//插入一個換行符,表示寫入的內容結束, 注意,要求對方使用readLine()!!!!
        bufferedWriter.flush();// 如果使用的字元流,需要手動重新整理,否則資料不會寫入資料通道


        //4. 獲取和socket關聯的輸入流. 讀取資料(字元),並顯示
        InputStream inputStream = socket.getInputStream();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
        String s = bufferedReader.readLine();
        System.out.println(s);

        //5. 關閉流物件和socket, 必須關閉
        bufferedReader.close();//關閉外層流
        bufferedWriter.close();
        socket.close();
        System.out.println("客戶端退出.....");
    }
}
package com.hspedu.socket;

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

/**
 * 服務端, 使用字元流方式讀寫
 */
@SuppressWarnings({"all"})
public class SocketTCP03Server {
    public static void main(String[] args) throws IOException {
        //思路
        //1. 在本機 的9999埠監聽, 等待連線
        //   細節: 要求在本機沒有其它服務在監聽9999
        //   細節:這個 ServerSocket 可以透過 accept() 返回多個Socket[多個客戶端連線伺服器的併發]
        ServerSocket serverSocket = new ServerSocket(9999);
        System.out.println("服務端,在9999埠監聽,等待連線..");
        //2. 當沒有客戶端連線9999埠時,程式會 阻塞, 等待連線
        //   如果有客戶端連線,則會返回Socket物件,程式繼續

        Socket socket = serverSocket.accept();

        System.out.println("服務端 socket =" + socket.getClass());
        //
        //3. 透過socket.getInputStream() 讀取客戶端寫入到資料通道的資料, 顯示
        InputStream inputStream = socket.getInputStream();
        //4. IO讀取, 使用字元流, 老師使用 InputStreamReader 將 inputStream 轉成字元流
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
        String s = bufferedReader.readLine();
        System.out.println(s);//輸出

        //5. 獲取socket相關聯的輸出流
        OutputStream outputStream = socket.getOutputStream();
       //    使用字元輸出流的方式回覆資訊
        BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
        bufferedWriter.write("hello client 字元流");
        bufferedWriter.newLine();// 插入一個換行符,表示回覆內容的結束
        bufferedWriter.flush();//注意需要手動的flush

        //6.關閉流和socket
        bufferedWriter.close();
        bufferedReader.close();
        socket.close();
        serverSocket.close();//關閉
    }
}

應用案例4

  1. 編寫一個服務端,和一個客戶端
  2. 伺服器端在8888埠監聽
  3. 客戶端連線到服務端,傳送一張圖片e:llqie.png
  4. 伺服器端接收到客戶端傳送的圖片,儲存到src下,傳送“收到圖片"再退出
  5. 客戶端接收到服務端傳送的“收到圖片”,再退出
  6. 該程式要求使用StreamUtils.java, 我們直接使用其中封裝好的方法。

package com.hspedu.upload;

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


/**
 * 檔案上傳的客戶端
 */
public class TCPFileUploadClient {
    public static void main(String[] args) throws Exception {

        //客戶端連線服務端 8888,得到Socket物件
        Socket socket = new Socket(InetAddress.getLocalHost(), 8888);
        //建立讀取磁碟檔案的輸入流
        //String filePath = "e:\\qie.png";
        String filePath = "e:\\abc.mp4";
        BufferedInputStream bis  = new BufferedInputStream(new FileInputStream(filePath));

        // 把檔案讀到字元陣列中!!!!!
        // bytes 就是filePath對應的位元組陣列
        byte[] bytes = StreamUtils.streamToByteArray(bis);

        //透過socket獲取到輸出流, 將bytes資料傳送給服務端
        BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
        bos.write(bytes); //將檔案對應的位元組陣列的內容,寫入到資料通道
        bis.close();
        socket.shutdownOutput(); //設定寫入資料的結束標記

        //=====接收從服務端回覆的訊息=====

        InputStream inputStream = socket.getInputStream();
        //使用StreamUtils 的方法,直接將 inputStream 讀取到的內容 轉成字串
        String s = StreamUtils.streamToString(inputStream);
        System.out.println(s);


        //關閉相關的流
        inputStream.close();
        bos.close();
        socket.close();

    }
}
package com.hspedu.upload;

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

/**
 * 檔案上傳的服務端
 */
public class TCPFileUploadServer {
    public static void main(String[] args) throws Exception {

        //1. 服務端在本機監聽8888埠
        ServerSocket serverSocket = new ServerSocket(8888);
        System.out.println("服務端在8888埠監聽....");
        //2. 等待連線
        Socket socket = serverSocket.accept();


        //3. 讀取客戶端傳送的資料
        //   透過Socket得到輸入流
        BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
        byte[] bytes = StreamUtils.streamToByteArray(bis); // 已然讀到陣列中了
        //4. 將得到 bytes 陣列,寫入到指定的路徑,就得到一個檔案了
        String destFilePath = "src\\abc.mp4";
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFilePath));
        bos.write(bytes);
        bos.close();

        // 向客戶端回覆 "收到圖片"
        // 透過socket 獲取到輸出流(字元)
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        writer.write("收到圖片");
        writer.flush();//把內容重新整理到資料通道
        socket.shutdownOutput();//設定寫入結束標記

        //關閉其他資源
        writer.close();
        bis.close();
        socket.close();
        serverSocket.close();
    }
}
package com.hspedu.upload;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

/**
 * 此類用於演示關於流的讀寫方法
 *
 */
public class StreamUtils {
   /**
    * 功能:將輸入流轉換成byte[]
    * @param is
    * @return
    * @throws Exception
    */
   public static byte[] streamToByteArray(InputStream is) throws Exception{
      ByteArrayOutputStream bos = new ByteArrayOutputStream();//建立輸出流物件
      byte[] b = new byte[1024];
      int len;
      while((len=is.read(b))!=-1){
         bos.write(b, 0, len);// 讀取道德資料寫入bos
      }
      byte[] array = bos.toByteArray();
      bos.close();
      return array;
   }
   /**
    * 功能:將InputStream轉換成String
    * @param is
    * @return
    * @throws Exception
    */
   
   public static String streamToString(InputStream is) throws Exception{
      BufferedReader reader = new BufferedReader(new InputStreamReader(is));
      StringBuilder builder= new StringBuilder();
      String line;
      while((line=reader.readLine())!=null){ //當讀取到 null時,就表示結束
         builder.append(line+"\r\n");
      }
      return builder.toString();
      
   }

}

netstat 指令

  1. netstat -an 可以檢視當前主機網路情況,包括項口監聽情況和網路連線情況
  2. netstat -an | more 可以分頁顯示
  3. netstat -anb (在管理員狀態下執行)可以檢視是哪些應用監聽該埠。
  4. 外部地址就是 連線 該本地地址和埠號的客戶端的IP和埠號。

說明:

(1) Listening表示某個埠在監聽 Established 表示連線已經建立

(2)如果有一個外部程式(客戶端)連線到該埠,就會顯示一條連線資訊

TCP 網路通訊

  1. 當客戶端連線到服務端後,實際上客戶端也是透過一個埠和服務端進行通訊的,這個埠是TCP/IP來分配的,是不確定的,是隨機的。(客戶端的埠
  2. 程式驗證+netstat

UDP 網路通訊程式設計

基本介紹

  1. DatagramSocketDatagramPacket[資料包/資料包]實現了基於UDP
    協議網路程式。

  2. UDP資料包透過資料包套接字DatagramSocket 傳送和接收,系統不保證UDP資料包一定能夠安全送到目的地,也不能確定什麼時候可以抵達。

  3. DatagramPacket 物件封裝了UDP資料包,在資料包中包含了傳送端的IP地址和
    埠號以及接收端的IP地址和埠號。

  4. UDP協議中每個資料包都給出了完整的地址資訊,因此無須建立傳送方和接收方
    的連線

基本流程

  1. 核心的兩個類/物件 DatagramSocket與DatagramPacket
  2. 建立傳送端,接收端(沒有服務端和客戶端概念)
  3. 傳送資料前,建立資料包/報 DatagramPacket物件
  4. 呼叫DatagramSocket的傳送、接收方法
  5. 關閉DatagramSocket

應用案例

  1. 編寫一個接收端A,和一個傳送端B
  2. 接收端 A在 9999埠等待接收資料(receive)
  3. 傳送端B向接收端A傳送資料“hello,明天吃火鍋~"
  4. 接收端A接收到傳送端B傳送的資料,回覆"好的,明天見"再退出
  5. 傳送端接收回復的資料,再退出
package com.hspedu.udp;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;

/**
 * UDP接收端
 */
public class UDPReceiverA {
    public static void main(String[] args) throws IOException {
        //1. 建立一個 DatagramSocket 物件,準備在9999接收資料
        DatagramSocket socket = new DatagramSocket(9999);
        //2. 構建一個 DatagramPacket 物件,準備接收資料
        //   在前面講解UDP 協議時,老師說過一個資料包最大 64k
        byte[] buf = new byte[1024];
        DatagramPacket packet = new DatagramPacket(buf, buf.length);
        //3. 呼叫 接收方法, 將透過網路傳輸的 DatagramPacket 物件
        //   填充到 packet物件
        //老師提示: 當有資料包傳送到 本機的9999埠時,就會接收到資料
        //   如果沒有資料包傳送到 本機的9999埠, 就會阻塞等待.
        System.out.println("接收端A 等待接收資料..");
        socket.receive(packet);

        //4. 可以把packet 進行拆包,取出資料,並顯示.
        int length = packet.getLength();//實際接收到的資料位元組長度
        byte[] data = packet.getData();//接收到資料
        String s = new String(data, 0, length);
        System.out.println(s);


        //===回覆資訊給B端
        //將需要傳送的資料,封裝到 DatagramPacket物件
        data = "好的, 明天見".getBytes();
        //說明: 封裝的 DatagramPacket物件 data 內容位元組陣列 , data.length , 主機(IP) , 埠
        packet =
                new DatagramPacket(data, data.length, InetAddress.getByName("192.168.12.1"), 9998);

        socket.send(packet);//傳送

        //5. 關閉資源
        socket.close();
        System.out.println("A端退出...");

    }
}
package com.hspedu.udp;

import java.io.IOException;
import java.net.*;

/**
 * 傳送端B ====> 也可以接收資料
 */
@SuppressWarnings({"all"})
public class UDPSenderB {
    public static void main(String[] args) throws IOException {

        //1.建立 DatagramSocket 物件,準備在9998埠 接收資料
        DatagramSocket socket = new DatagramSocket(9998);

        //2. 將需要傳送的資料,封裝到 DatagramPacket物件
        byte[] data = "hello 明天吃火鍋~".getBytes(); //

        //說明: 封裝的 DatagramPacket物件 data 內容位元組陣列 , data.length , 主機(IP) , 埠
        DatagramPacket packet =
                new DatagramPacket(data, data.length, InetAddress.getByName("192.168.12.1"), 9999);

        socket.send(packet);

        //3.=== 接收從A端回覆的資訊
        //(1)   構建一個 DatagramPacket 物件,準備接收資料
        //   在前面講解UDP 協議時,老師說過一個資料包最大 64k
        byte[] buf = new byte[1024];
        packet = new DatagramPacket(buf, buf.length);
        //(2)    呼叫 接收方法, 將透過網路傳輸的 DatagramPacket 物件
        //   填充到 packet物件
        //老師提示: 當有資料包傳送到 本機的9998埠時,就會接收到資料
        //   如果沒有資料包傳送到 本機的9998埠, 就會阻塞等待.
        socket.receive(packet);

        //(3)  可以把packet 進行拆包,取出資料,並顯示.
        int length = packet.getLength();//實際接收到的資料位元組長度
        data = packet.getData();//接收到資料
        String s = new String(data, 0, length);
        System.out.println(s);

        //關閉資源
        socket.close();
        System.out.println("B端退出");
    }
}

本章作業

1.程式設計題

(1)使用字元流的方式,編寫一個客戶端程式和伺服器端程式,

(2)客戶端傳送“name"伺服器端接收到後,返回“我是nova ", nova是你自己的名字.

(3)客戶端傳送"hobby",伺服器端接收到後,返回“編寫java程式”

(4)不是這兩個問題,回覆“你說啥呢"

問題:目前,我們只能問一次,就退出了,怎麼可以問多次?->while ->聊天

package com.hspedu.homework;

import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Scanner;

/**
 * 客戶端,傳送 "hello, server" 給服務端, 使用字元流
 */
@SuppressWarnings({"all"})
public class Homework01Client {
    public static void main(String[] args) throws IOException {
        //思路
        //1. 連線服務端 (ip , 埠)
        //解讀: 連線本機的 9999埠, 如果連線成功,返回Socket物件
        Socket socket = new Socket(InetAddress.getLocalHost(), 9999);

        //2. 連線上後,生成Socket, 透過socket.getOutputStream()
        //   得到 和 socket物件關聯的輸出流物件
        OutputStream outputStream = socket.getOutputStream();
        //3. 透過輸出流,寫入資料到 資料通道, 使用字元流
        BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));

        //從鍵盤讀取使用者的問題
        Scanner scanner = new Scanner(System.in);
        System.out.println("請輸入你的問題");
        String question = scanner.next();

        bufferedWriter.write(question);
        bufferedWriter.newLine();//插入一個換行符,表示寫入的內容結束, 注意,要求對方使用readLine()!!!!
        bufferedWriter.flush();// 如果使用的字元流,需要手動重新整理,否則資料不會寫入資料通道


        //4. 獲取和socket關聯的輸入流. 讀取資料(字元),並顯示
        InputStream inputStream = socket.getInputStream();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
        String s = bufferedReader.readLine();
        System.out.println(s);

        //5. 關閉流物件和socket, 必須關閉
        bufferedReader.close();//關閉外層流
        bufferedWriter.close();
        socket.close();
        System.out.println("客戶端退出.....");
    }
}
package com.hspedu.homework;

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

/**
 * 服務端, 使用字元流方式讀寫
 */
@SuppressWarnings({"all"})
public class Homework01Server {
    public static void main(String[] args) throws IOException {
        //思路
        //1. 在本機 的9999埠監聽, 等待連線
        //   細節: 要求在本機沒有其它服務在監聽9999
        //   細節:這個 ServerSocket 可以透過 accept() 返回多個Socket[多個客戶端連線伺服器的併發]
        ServerSocket serverSocket = new ServerSocket(9999);
        System.out.println("服務端,在9999埠監聽,等待連線..");
        //2. 當沒有客戶端連線9999埠時,程式會 阻塞, 等待連線
        //   如果有客戶端連線,則會返回Socket物件,程式繼續

        Socket socket = serverSocket.accept();

        //
        //3. 透過socket.getInputStream() 讀取客戶端寫入到資料通道的資料, 顯示
        InputStream inputStream = socket.getInputStream();
        //4. IO讀取, 使用字元流, 老師使用 InputStreamReader 將 inputStream 轉成字元流
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
        String s = bufferedReader.readLine();
        String answer = "";
        if ("name".equals(s)) {
            answer = "我是韓順平";
        } else if("hobby".equals(s)) {
            answer = "編寫java程式";
        } else {
            answer = "你說的啥子";
        }


        //5. 獲取socket相關聯的輸出流
        OutputStream outputStream = socket.getOutputStream();
        //    使用字元輸出流的方式回覆資訊
        BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
        bufferedWriter.write(answer);
        bufferedWriter.newLine();// 插入一個換行符,表示回覆內容的結束
        bufferedWriter.flush();//注意需要手動的flush


        //6.關閉流和socket
        bufferedWriter.close();
        bufferedReader.close();
        socket.close();
        serverSocket.close();//關閉

    }
}

2.程式設計題

(1)編寫一個接收端A,和一個傳送端B,使用UDP協議完成

(2)接收端在8888埠等待接收資料(receive)

(3)傳送端向接收端傳送資料“四大名著是哪些”

(4)接收端接收到傳送端傳送的問題後,返回“四大名著是<<紅樓夢>>.….",否則返回what?

(5)接收端和傳送端程式退出

package com.hspedu.homework;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;

/**
 * 傳送端B ====> 也可以接收資料
 */
@SuppressWarnings({"all"})
public class Homework02SenderB {
    public static void main(String[] args) throws IOException {

        //1.建立 DatagramSocket 物件,準備在9998埠 接收資料
        DatagramSocket socket = new DatagramSocket(9998);

        //2. 將需要傳送的資料,封裝到 DatagramPacket物件
        Scanner scanner = new Scanner(System.in);
        System.out.println("請輸入你的問題: ");
        String question = scanner.next();
        byte[] data = question.getBytes(); //

        //說明: 封裝的 DatagramPacket物件 data 內容位元組陣列 , data.length , 主機(IP) , 埠
        DatagramPacket packet =
                new DatagramPacket(data, data.length, InetAddress.getByName("192.168.12.1"), 8888);

        socket.send(packet);

        //3.=== 接收從A端回覆的資訊
        //(1)   構建一個 DatagramPacket 物件,準備接收資料
        //   在前面講解UDP 協議時,老師說過一個資料包最大 64k
        byte[] buf = new byte[1024];
        packet = new DatagramPacket(buf, buf.length);
        //(2)    呼叫 接收方法, 將透過網路傳輸的 DatagramPacket 物件
        //   填充到 packet物件
        //老師提示: 當有資料包傳送到 本機的9998埠時,就會接收到資料
        //   如果沒有資料包傳送到 本機的9998埠, 就會阻塞等待.
        socket.receive(packet);

        //(3)  可以把packet 進行拆包,取出資料,並顯示.
        int length = packet.getLength();//實際接收到的資料位元組長度
        data = packet.getData();//接收到資料
        String s = new String(data, 0, length);
        System.out.println(s);

        //關閉資源
        socket.close();
        System.out.println("B端退出");
    }
}
package com.hspedu.homework;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

/**
 * UDP接收端
 */
@SuppressWarnings({"all"})
public class Homework02ReceiverA {
    public static void main(String[] args) throws IOException {
        //1. 建立一個 DatagramSocket 物件,準備在8888接收資料
        DatagramSocket socket = new DatagramSocket(8888);
        //2. 構建一個 DatagramPacket 物件,準備接收資料
        //   在前面講解UDP 協議時,老師說過一個資料包最大 64k
        byte[] buf = new byte[1024];
        DatagramPacket packet = new DatagramPacket(buf, buf.length);
        //3. 呼叫 接收方法, 將透過網路傳輸的 DatagramPacket 物件
        //   填充到 packet物件
        System.out.println("接收端 等待接收問題 ");
        socket.receive(packet);

        //4. 可以把packet 進行拆包,取出資料,並顯示.
        int length = packet.getLength();//實際接收到的資料位元組長度
        byte[] data = packet.getData();//接收到資料
        String s = new String(data, 0, length); // 將位元組陣列轉為string!!!!
        //判斷接收到的資訊是什麼
        String answer = "";
        if("四大名著是哪些".equals(s)) {
            answer = "四大名著 <<紅樓夢>> <<三國演示>> <<西遊記>> <<水滸傳>>";
        } else {
            answer = "what?";
        }


        //===回覆資訊給B端
        //將需要傳送的資料,封裝到 DatagramPacket物件
        data = answer.getBytes();
        //說明: 封裝的 DatagramPacket物件 data 內容位元組陣列 , data.length , 主機(IP) , 埠
        packet =
                new DatagramPacket(data, data.length, InetAddress.getByName("192.168.12.1"), 9998);

        socket.send(packet);//傳送

        //5. 關閉資源
        socket.close();
        System.out.println("A端退出...");

    }
}

3.程式設計題

(1)編寫客戶端程式和伺服器端程式

(2)客戶端可以輸入一個音樂檔名,比如高山流水, 服務端收到音樂名後,可以給客戶端返回這個音樂檔案,如果伺服器沒有這個檔案,返回一個預設的音樂即可。

(3)客戶端收到檔案後,儲存到本地e:\\

(4)提示:該程式可以使用 StreamUtils.java

package com.hspedu.homework;

import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;

/**
 * 檔案下載的客戶端
 */
public class Homework03Client {
    public static void main(String[] args) throws Exception {


        //1. 接收使用者輸入,指定下載檔名
        Scanner scanner = new Scanner(System.in);
        System.out.println("請輸入下載檔名");
        String downloadFileName = scanner.next();

        //2. 客戶端連線服務端,準備傳送
        Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
        //3. 獲取和Socket關聯的輸出流
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write(downloadFileName.getBytes());
        //設定寫入結束的標誌
        socket.shutdownOutput();

        //4. 讀取服務端返回的檔案(位元組資料)
        BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
        byte[] bytes = StreamUtils.streamToByteArray(bis);
        //5. 得到一個輸出流,準備將 bytes 寫入到磁碟檔案
        //比如你下載的是 高山流水 => 下載的就是 高山流水.mp3
        //    你下載的是 無名 => 下載的就是 無名.mp3
        String filePath = "e:\\" + downloadFileName + ".mp3";
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath));
        bos.write(bytes);

        //6. 關閉相關的資源
        bos.close();
        bis.close();
        outputStream.close();
        socket.close();

        System.out.println("客戶端下載完畢,正確退出..");


    }
}
package com.hspedu.homework;

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

/**
 * 先寫檔案下載的服務端
 */
public class Homework03Server {
    public static void main(String[] args) throws Exception {

        //1 監聽 9999埠
        ServerSocket serverSocket = new ServerSocket(9999);
        //2.等待客戶端連線
        System.out.println("服務端,在9999埠監聽,等待下載檔案");
        Socket socket = serverSocket.accept();
        //3.讀取 客戶端傳送要下載的檔名
        //  這裡老師使用了while讀取檔名,時考慮將來客戶端傳送的資料較大的情況
        InputStream inputStream = socket.getInputStream();
        byte[] b = new byte[1024];
        int len = 0;
        String downLoadFileName = "";
        while ((len = inputStream.read(b)) != -1) {
            downLoadFileName += new String(b, 0 , len);
        }
        System.out.println("客戶端希望下載檔名=" + downLoadFileName);

        //老師在伺服器上有兩個檔案, 無名.mp3 高山流水.mp3
        //如果客戶下載的是 高山流水 我們就返回該檔案,否則一律返回 無名.mp3

        String resFileName = "";
        if("高山流水".equals(downLoadFileName)) {
            resFileName = "src\\高山流水.mp3";
        } else {
            resFileName = "src\\無名.mp3";
        }

        //4. 建立一個輸入流,讀取檔案
        BufferedInputStream bis =
                new BufferedInputStream(new FileInputStream(resFileName));

        //5. 使用工具類StreamUtils ,讀取檔案到一個位元組陣列

        byte[] bytes = StreamUtils.streamToByteArray(bis);
        //6. 得到Socket關聯的輸出流
        BufferedOutputStream bos =
                new BufferedOutputStream(socket.getOutputStream());
        //7. 寫入到資料通道,返回給客戶端
        bos.write(bytes);
        socket.shutdownOutput();//很關鍵.

        //8 關閉相關的資源
        bis.close();
        inputStream.close();
        socket.close();
        serverSocket.close();
        System.out.println("服務端退出...");

    }
}
package com.hspedu.homework;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

/**
 * 此類用於演示關於流的讀寫方法
 *
 */
public class StreamUtils {
   /**
    * 功能:將輸入流轉換成byte[]
    * @param is
    * @return
    * @throws Exception
    */
   public static byte[] streamToByteArray(InputStream is) throws Exception{
      ByteArrayOutputStream bos = new ByteArrayOutputStream();//建立輸出流物件
      byte[] b = new byte[1024];
      int len;
      while((len=is.read(b))!=-1){
         bos.write(b, 0, len);  
      }
      byte[] array = bos.toByteArray();
      bos.close();
      return array;
   }
   /**
    * 功能:將InputStream轉換成String
    * @param is
    * @return
    * @throws Exception
    */
   
   public static String streamToString(InputStream is) throws Exception{
      BufferedReader reader = new BufferedReader(new InputStreamReader(is));
      StringBuilder builder= new StringBuilder();
      String line;
      while((line=reader.readLine())!=null){
         builder.append(line+"\r\n");
      }
      return builder.toString();
   }
}