天天看點

【JavaLearn】 #(14)網絡及分類、TCP、UDP協定、IP、Socket、TCP程式設計、UDP程式設計1. 網絡基本概念2. 網絡程式設計常用類3. TCP程式設計4. UDP程式設計5. 檔案上傳

1. 網絡基本概念

1.1 計算機網絡

定義:是指将地理位置不同的具有獨立功能(沒有網絡可以獨立存在的)的多台計算機及其外部裝置,通過通信線路連接配接起來,在網絡作業系統,網絡管理軟體及網絡通信協定的管理和協調下,實作資源共享和資訊傳遞的計算機系統

  • 主幹:計算機網絡是計算機系統
  • 網絡功能:資源共享、資訊傳遞
  • 網絡組成
    • 網絡硬體:計算機、外部裝置、通信連接配接
    • 網絡軟體:網絡作業系統、網絡管理軟體、網絡通信協定

分類–按照規模:

  • 區域網路 LAN
  • 城域網 MAN
  • 廣域網 WAN

分類–按照傳輸媒體:

  • 同軸電纜網絡(類似于有線電視網的電纜)
  • 雙絞線網絡
  • 光纖網絡(傳輸的為光信号)
  • 衛星網絡
    【JavaLearn】 #(14)網絡及分類、TCP、UDP協定、IP、Socket、TCP程式設計、UDP程式設計1. 網絡基本概念2. 網絡程式設計常用類3. TCP程式設計4. UDP程式設計5. 檔案上傳

分類–拓撲結構:

  • 星形網絡(最常使用)
  • 總線網絡:信号在傳遞過程中都能收到,辨識是自己的則接收
  • 環狀網絡:同樣的傳遞方式
    【JavaLearn】 #(14)網絡及分類、TCP、UDP協定、IP、Socket、TCP程式設計、UDP程式設計1. 網絡基本概念2. 網絡程式設計常用類3. TCP程式設計4. UDP程式設計5. 檔案上傳

1.2 網絡通信協定

在網絡中實作通信,必須要有一些約定(通信協定),對速率、傳輸代碼、傳輸控制步驟等指定标準(好比交通規則)

問題:網絡通信涉及内容很多:源位址、目标位址、加密解密、流量控制、路由控制等,如何實作如此複雜的網絡協定?

===》

分層

:将複雜的成份分解成一些簡單的成份,再将它們複合起來(同層間可以通信、上一層可以調用下一層,而不與再下一層發生關系)

網絡通信協定分層:

  • 名義上的标準:ISO --> OSI 參考模型
  • 事實上的标準:TCP/IP協定棧
【JavaLearn】 #(14)網絡及分類、TCP、UDP協定、IP、Socket、TCP程式設計、UDP程式設計1. 網絡基本概念2. 網絡程式設計常用類3. TCP程式設計4. UDP程式設計5. 檔案上傳

資料的封裝與拆封:在傳輸過程中,經過每一層,都需要添加各種資料,最終發送到另一端,另一端再拆解這些資料

【JavaLearn】 #(14)網絡及分類、TCP、UDP協定、IP、Socket、TCP程式設計、UDP程式設計1. 網絡基本概念2. 網絡程式設計常用類3. TCP程式設計4. UDP程式設計5. 檔案上傳

1.3 TCP/IP協定棧

網絡通信最常采用的協定

  • 網絡層主要協定:IP協定
  • 傳輸層主要協定:TCP 和 UDP 協定
【JavaLearn】 #(14)網絡及分類、TCP、UDP協定、IP、Socket、TCP程式設計、UDP程式設計1. 網絡基本概念2. 網絡程式設計常用類3. TCP程式設計4. UDP程式設計5. 檔案上傳

1.4 TCP協定

面向連接配接的、可靠的、基于位元組流的傳輸層通信協定(打電話的案例)

  • 面向連接配接(一段資訊分段後發送,發送順序和接收順序一緻)
  • 點到點的通信
  • 高可靠性:三次握手
  • 占用系統資源多、效率低

應用案列:HTTP、FTP、Telnet、SMTP

【JavaLearn】 #(14)網絡及分類、TCP、UDP協定、IP、Socket、TCP程式設計、UDP程式設計1. 網絡基本概念2. 網絡程式設計常用類3. TCP程式設計4. UDP程式設計5. 檔案上傳

1.5 UDP協定

無連接配接的傳輸層協定,提供面向事務的簡單不可靠資訊傳送服務(發電報、發送群發短信)

  • 非面向連接配接,傳輸不可靠,可能丢失(一段資訊分段後發送,不一定哪一段先到達)
  • 發送不管對方是否準備好,接收方收到後也不回複
  • 可以廣播發送
  • 非常簡單的協定,開銷少

應用案例:DNS、SNMP

1.6 IP位址和端口

IP位址,用來标志網絡中的一個通信實體(計算機、路由器)的位址

分類:

  • IPV4:32位位址,點分十進制表示,如192.168.0.1
  • IPV6:128位寫個8個16位的無符号整數,每個整數用4個十六進制位辨別,數之間用 : 分割

特殊的 IP 位址:

  • 127.0.0.1:本機位址
  • 192.168.0.0 – 192.168.255.255 私有位址,專門為組織機構内部使用

端口(port):

  • IP位址用來标志一台計算機,但一個計算機可以提供多種應用程式,使用端口來區分應用程式
  • 範圍:0 – 65535(16位整數)

端口分類:

  • 0 – 1023 :公認端口(比如 80給了 WWW,21給了 FTP等)
  • 1024 – 49151:注冊端口(配置設定給使用者或應用程式)
  • 49152 – 65535:動态/私有端口

IP和端口API:

  • InetAddress 類:封裝計算機的 ip位址,沒有端口
  • InetSocketAddress:包含端口,用于 socket 通信

1.7 URL 統一資源定位符

Uniform Resource Locator:由 4部分組成:協定、存放資源的主機域名、端口号、資源檔案名

【JavaLearn】 #(14)網絡及分類、TCP、UDP協定、IP、Socket、TCP程式設計、UDP程式設計1. 網絡基本概念2. 網絡程式設計常用類3. TCP程式設計4. UDP程式設計5. 檔案上傳

1.8 Socket 套接字

Socket實際是傳輸層供給應用層的程式設計接口

【JavaLearn】 #(14)網絡及分類、TCP、UDP協定、IP、Socket、TCP程式設計、UDP程式設計1. 網絡基本概念2. 網絡程式設計常用類3. TCP程式設計4. UDP程式設計5. 檔案上傳

類似于寄信:使用者(應用層)将信(資料)投入郵筒即可(郵筒的口,就是socket),進入Socket之後,怎麼送信就是郵局、公路交管(傳輸層、網絡層)等的事。

2. 網絡程式設計常用類

2.1 封裝IP位址 – InetAddress

// 1.擷取 IP位址
InetAddress ia = InetAddress.getLocalHost();  // 本機的 ip

// 2.操作 IP位址
System.out.println(ia);  // DESKTOP-F31QQ1H/192.168.0.102
System.out.println(ia.getHostAddress()); // 192.168.0.102
System.out.println(ia.getHostName()); // DESKTOP-F31QQ1H

InetAddress ia2 = InetAddress.getByName("www.lwclick.com");  // 通過域名擷取ip
System.out.println(ia2);
           

2.2 封裝 IP 和 Port – InetSocketAddress

// 建立一個 InetSocketAddress 對象
InetSocketAddress isa = new InetSocketAddress("www.lwclick.com", 8888);

// 擷取對象内容
System.out.println(isa);  // www.lwclick.com/104.21.41.202:8888
System.out.println(isa.getAddress());  // www.lwclick.com/104.21.41.202
System.out.println(isa.getPort());  // 8888
           

2.3 URL類

// 建立一個 URL   協定:https    域名/IP位址:lwclick.com    端口:80     路徑:/categories/MySQL/
URL url = new URL("https://lwclick.com:80/categories/MySQL/");

// 擷取 URL 各個組成部分
System.out.println(url.getProtocol());  //  https
System.out.println(url.getHost());  //  lwclick.com
System.out.println(url.getPort());  //  80
System.out.println(url.getDefaultPort());  //  443 預設的 https 端口
System.out.println(url.getPath());  //  /categories/MySQL/
           

3. TCP程式設計

3.1 一次單向通信

  • 伺服器端:
    • 建立 ServerSocket,在指定端口監聽(

      accept()

      方法)

      并處理請求(如果用戶端請求到來,傳回對應的Socket,否則的話一直等待,線程也被阻塞)

  • 用戶端:
    • 建立 Socket,需要指定伺服器的 ip 和端口号,向伺服器發送和接收響應
  • 發送資料:
    • 需要使用輸出流(OutputStream),可以通過 DataOutputStream 和 ObjectOutputStream 進行包裝,提高效率
  • 接收資料:
    • 使用輸入流(InputStream),使用 DataInputStream 和 ObjectInputStream 進行包裝
【JavaLearn】 #(14)網絡及分類、TCP、UDP協定、IP、Socket、TCP程式設計、UDP程式設計1. 網絡基本概念2. 網絡程式設計常用類3. TCP程式設計4. UDP程式設計5. 檔案上傳

伺服器端:

public class LoginServer {
    public static void main(String[] args) throws IOException {
        // 1.建立一個 ServerSocket,配置監聽端口
        ServerSocket serverSocket = new ServerSocket(8080);

        // 2.使用 ServerSocket 在指定端口監聽
        Socket socket = serverSocket.accept(); // 請求不到,在此阻塞;  請求到了,傳回一個socket,繼續執行

        // 3.接收用戶端的請求資料,輸出結果
        InputStream is = socket.getInputStream(); // 擷取流
        DataInputStream dis = new DataInputStream(is); // 同樣使用資料流進行包裝
        String info = dis.readUTF(); // 讀取對應類型的寫入的資料
        System.out.println("用戶端的請求:" + info);

        // 4.關閉資源
        dis.close();
        serverSocket.close();
    }
}
           

用戶端:

public class LoginClient {
    public static void main(String[] args) throws IOException {
        // 1.建立一個 Socket,指明伺服器端ip和端口
        Socket socket = new Socket(InetAddress.getLocalHost(), 8080); // InetAddress.getByName()擷取ip

        // 2.發送資料給伺服器端
        OutputStream os = socket.getOutputStream(); // 資訊通過流發送,輸出流
        DataOutputStream dos = new DataOutputStream(os); // 資料流進行包裝
        dos.writeUTF("userName=lwclick&pwd=123");

        // 3.關閉資源
        dos.close(); // 關閉高層流,低層自動關閉
    }
}
           

注意:測試時,伺服器端需要先啟動,然後再啟動用戶端

3.2 一次雙向通信

伺服器端:

public static void main(String[] args) throws IOException {
    ServerSocket serverSocket = new ServerSocket(8080);
    Socket socket = serverSocket.accept(); 
    InputStream is = socket.getInputStream();
    DataInputStream dis = new DataInputStream(is);
    String info = dis.readUTF(); 
    System.out.println("用戶端的請求:" + info);

    // ============================== 向用戶端發送資料 =====================================
    // 4. 給用戶端一個響應
    OutputStream os = socket.getOutputStream(); // 此處的socket為接收的用戶端的響應
    DataOutputStream dos = new DataOutputStream(os);
    dos.writeUTF("登入成功,歡迎!");

    // 5.關閉資源
    dos.close();
    dis.close();
    serverSocket.close();
}
           

用戶端:

public static void main(String[] args) throws IOException {
    Socket socket = new Socket(InetAddress.getL ocalHost(), 8080);
    OutputStream os = socket.getOutputStream();
    DataOutputStream dos = new DataOutputStream(os);
    dos.writeUTF("userName=lwclick&pwd=123");

    // ============================== 接收伺服器端回報 =====================================
    // 3.接收伺服器端響應,并輸出
    InputStream is = socket.getInputStream();
    DataInputStream dis = new DataInputStream(is);
    String info = dis.readUTF();
    System.out.println("伺服器端的響應:" + info);

    // 4.關閉資源
    dis.close();
    dos.close();
}
           

3.3 傳輸對象

User 類:

在網絡上傳輸,類一定要實作序列化接口

public class User implements Serializable {
    private String userId;
    private String password;
    
    // getter / setter / toString / constructor
}
           

伺服器端:

public static void main(String[] args) throws IOException, ClassNotFoundException {
    ServerSocket serverSocket = new ServerSocket(8080);
    Socket socket = serverSocket.accept(); 
    InputStream is = socket.getInputStream(); 
    
    // =============== 此處使用【對象流】接收資料 ===================
    ObjectInputStream ois = new ObjectInputStream(is); 
    User user = (User)ois.readObject(); 
    System.out.println("用戶端的請求:" + user);
    
    OutputStream os = socket.getOutputStream(); 
    DataOutputStream dos = new DataOutputStream(os);
    if (user.getUserId().indexOf("lwclick") >= 0 && user.getPassword().length() > 6) {
        dos.writeUTF("登入成功,歡迎!");
    } else {
        dos.writeUTF("登入失敗,請重試!");
    }

    dos.close();
    ois.close();
    serverSocket.close();
}
           

用戶端:

public static void main(String[] args) throws IOException {
    Socket socket = new Socket(InetAddress.getLocalHost(), 8080);

    // 擷取使用者輸入
    Scanner sc = new Scanner(System.in);
    System.out.print("userId:  ");
    String userId = sc.next();
    System.out.print("password:  ");
    String password = sc.next();
    User user = new User(userId, password);

    OutputStream os = socket.getOutputStream();
    // 【對象流】進行包裝
    ObjectOutputStream oos = new ObjectOutputStream(os); 
    oos.writeObject(user);

    InputStream is = socket.getInputStream();
    DataInputStream dis = new DataInputStream(is);
    String info = dis.readUTF();
    System.out.println("伺服器端的響應:" + info);

    dis.close();
    oos.close();
}
           

3.4 引入多線程

将伺服器端接到請求後的處理步驟,放到線程的 run()方法 中,

每過來一個請求,就建立一個線程去執行

線程類:

public class LoginThread extends Thread {
    private Socket socket;

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

    @Override
    public void run() {
        DataOutputStream dos = null;
        ObjectInputStream ois = null;
        try {
            InputStream is = socket.getInputStream();
            ois = new ObjectInputStream(is);
            User user = (User)ois.readObject();
            System.out.println("用戶端的請求:" + user);

            OutputStream os = socket.getOutputStream();
            dos = new DataOutputStream(os);
            if (user.getUserId().indexOf("lwclick") >= 0 && user.getPassword().length() > 6) {
                dos.writeUTF("登入成功,歡迎!");
            } else {
                dos.writeUTF("登入失敗,請重試!");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            try {
                if (dos != null) {
                    dos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (ois != null) {
                    ois.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
           

伺服器端:

為每一個登入請求,建立一個線程來處理

public static void main(String[] args) throws IOException, ClassNotFoundException {
    ServerSocket serverSocket = new ServerSocket(8080);

    int i = 1;
    while (true) {
        Socket socket = serverSocket.accept();
        
        // 為每一個登入請求,建立一個線程來處理
        new LoginThread(socket).start();

        // 統計用戶端的IP位址和總的請求次數
        InetAddress ia = socket.getInetAddress();
        System.out.println("這是第" + (i++) + "個請求,對方的IP位址是:" + ia.getHostAddress());
    }
}
           

用戶端:(無需改變)

public static void main(String[] args) throws IOException {
    Socket socket = new Socket(InetAddress.getLocalHost(), 8080);

    // 擷取使用者輸入
    Scanner sc = new Scanner(System.in);
    System.out.print("userId:  ");
    String userId = sc.next();
    System.out.print("password:  ");
    String password = sc.next();
    User user = new User(userId, password);

    OutputStream os = socket.getOutputStream();
    // 【對象流】進行包裝
    ObjectOutputStream oos = new ObjectOutputStream(os); 
    oos.writeObject(user);

    InputStream is = socket.getInputStream();
    DataInputStream dis = new DataInputStream(is);
    String info = dis.readUTF();
    System.out.println("伺服器端的響應:" + info);

    dis.close();
    oos.close();
}
           

4. UDP程式設計

無連接配接的,客戶與咨詢師的線上交流

  • 使用基于 UDP 協定的 Socket 網絡程式設計實作
  • 不需要使用 IO 流實作資料的傳輸
  • 每個資料發送單元被統一封裝成資料包(ip,接口,資料等)的方式,發送方将資料發到網絡上,資料包在網絡上尋找它要去的目的地

需要使用的類:

  • DatagramSocket:用于發送或接收資料包
  • DatagramPacket:資料包

4.1 一次單向通信

用戶端:

public static void main(String[] args) throws IOException {
    // 1.建立一個 Socket,用來發送和接收資料包
    DatagramSocket socket = new DatagramSocket(9999); // 用戶端監聽的接口

    // 2.使用 socket 發送一個資料包
    String str = "親,在嗎";
    byte[] buf = str.getBytes();
    InetAddress ia = InetAddress.getLocalHost();
    int port = 8888; // 伺服器端接收資料的端口号
    DatagramPacket packet = new DatagramPacket(buf, buf.length, ia, port);
    // 發送資料包
    socket.send(packet);

    // 3.關閉 socket
    socket.close();
}
           

伺服器端:

public static void main(String[] args) throws IOException {
    // 1.建立一個 Socket,用來發送和接收資料包
    DatagramSocket socket = new DatagramSocket(8888);  // 伺服器端監聽的接口

    // 2.使用 socket 接收一個資料包
    byte[] buf = new byte[128];
    DatagramPacket packet = new DatagramPacket(buf, buf.length);
    socket.receive(packet); // ip,port等資訊
    System.out.println(new String(packet.getData(), 0, packet.getLength()));
    System.out.println(packet.getAddress());
    System.out.println(packet.getPort());

    // 3.關閉 socket
    socket.close();
}
           

4.2 多次雙向通信

用戶端:

public static void main(String[] args) throws IOException {
    // 1.建立一個 Socket,用來發送和接收資料包
    DatagramSocket socket = new DatagramSocket(9999); // 用戶端監聽的接口
    Scanner sc = new Scanner(System.in);
    while (true) {
        String line = sc.nextLine();
        // 2.使用 socket 發送一個資料包
        byte[] buf = line.getBytes();
        InetAddress ia = InetAddress.getLocalHost();
        int port = 8888; // 伺服器端接收資料的端口号
        DatagramPacket packet = new DatagramPacket(buf, buf.length, ia, port);
        // 發送資料包
        socket.send(packet);

        // 如果用戶端輸入 bye,結束對話
        if ("bye".equals(line)) {
            break;
        }

        // 接收伺服器端傳回的消息
        byte[] bytes = new byte[128];
        DatagramPacket packetReceive = new DatagramPacket(bytes, bytes.length);
        socket.receive(packetReceive);
        System.out.println(new String(packetReceive.getData(), 0, packetReceive.getLength()));
    }

    // 3.關閉 socket
    socket.close();
}
           

伺服器端:

public static void main(String[] args) throws IOException {
    // 1.建立一個 Socket,用來發送和接收資料包
    DatagramSocket socket = new DatagramSocket(8888); // 伺服器端監聽的接口
    Scanner sc = new Scanner(System.in);

    while (true) {
        // 2.使用 socket 接收一個資料包
        byte[] buf = new byte[128];
        DatagramPacket packet = new DatagramPacket(buf, buf.length);
        socket.receive(packet); // ip,port等資訊
        String info = new String(packet.getData(), 0, packet.getLength());
        System.out.println(info);
        if ("bye".equals(info)) {
            break;
        }

        // 使用 socket 給用戶端發送一個資料包
        String str = sc.nextLine();
        byte[] bytes = str.getBytes();
        InetAddress address = packet.getAddress(); // 發送資料的用戶端位址
        int port = packet.getPort(); // 端口号
        DatagramPacket sendPacket = new DatagramPacket(bytes, bytes.length, address, port);
        socket.send(sendPacket);
    }

    // 3.關閉 socket
    socket.close();
}
           

5. 檔案上傳

使用 TCP程式設計 實作檔案上傳功能

  • 思路:進行兩次檔案的複制
    • 【用戶端】将檔案從【本地】複制到【網絡】
    • 【服務端】将檔案從【網絡】複制到【本地】
【JavaLearn】 #(14)網絡及分類、TCP、UDP協定、IP、Socket、TCP程式設計、UDP程式設計1. 網絡基本概念2. 網絡程式設計常用類3. TCP程式設計4. UDP程式設計5. 檔案上傳

用戶端:

public class UploadClient {
    public static void main(String[] args) throws IOException {
        // 建立一個 socket,指明伺服器端ip 和監聽端口
        Socket socket = new Socket(InetAddress.getLocalHost(), 8800);

        // ======================== 上傳檔案到服務端的目的端口 ===========================
        // 本機的源檔案
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("e:/readme.txt")); 
        // 将檔案寫到目的伺服器端口的位置
        BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream()); 

        byte[] buf = new byte[1024];
        int len = bis.read(buf);
        while (len != -1) {
            bos.write(buf, 0, len);
            len = bis.read(buf);
        }

        bos.close();
        bis.close();
    }
}
           

伺服器端:

public class UploadServer {
    public static void main(String[] args) throws IOException {
        // 建立一個 ServerSocket
        ServerSocket serverSocket = new ServerSocket(8800);

        // 使用 ServerSocket 在指定端口監聽
        Socket socket = serverSocket.accept();

        // ===================== 從目的端口取檔案 ========================
        // 從目的端口取内容
        BufferedInputStream bis = new BufferedInputStream(socket.getInputStream()); 
        // 儲存到伺服器的本地
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("e:/readme2.txt")); 

        byte[] buf = new byte[1024];
        int len = bis.read(buf);
        while (len != -1) {
            bos.write(buf, 0, len);
            len = bis.read(buf);
        }

        bos.close();
        bis.close();
    }
}
           

繼續閱讀