第二十三天:網(wǎng)絡(luò)編程

一.網(wǎng)絡(luò)編程入門

(一)軟件結(jié)構(gòu)

  • C/S結(jié)構(gòu) :全稱為Client/Server結(jié)構(gòu),是指客戶端和服務(wù)器結(jié)構(gòu)。常見程序有QQ、迅雷等軟件。
    cs.jpg

    B/S結(jié)構(gòu) :全稱為Browser/Server結(jié)構(gòu),是指瀏覽器和服務(wù)器結(jié)構(gòu)。常見瀏覽器有谷歌、火狐等。
    bs.jpg

    兩種架構(gòu)各有優(yōu)勢,但是無論哪種架構(gòu),都離不開網(wǎng)絡(luò)的支持。網(wǎng)絡(luò)編程,就是在一定的協(xié)議下,實現(xiàn)兩臺計算機的通信的程序。

(二)網(wǎng)絡(luò)通信協(xié)議

  • 網(wǎng)絡(luò)通信協(xié)議:通過計算機網(wǎng)絡(luò)可以使多臺計算機實現(xiàn)連接,位于同一個網(wǎng)絡(luò)中的計算機在進行連接和通信時需要遵守一定的規(guī)則,這就好比在道路中行駛的汽車一定要遵守交通規(guī)則一樣。在計算機網(wǎng)絡(luò)中,這些連接和通信的規(guī)則被稱為網(wǎng)絡(luò)通信協(xié)議,它對數(shù)據(jù)的傳輸格式、傳輸速率、傳輸步驟等做了統(tǒng)一規(guī)定,通信雙方必須同時遵守才能完成數(shù)據(jù)交換。
  • TCP/IP協(xié)議: 傳輸控制協(xié)議/因特網(wǎng)互聯(lián)協(xié)議( Transmission Control Protocol/Internet Protocol),是Internet最基本、最廣泛的協(xié)議。它定義了計算機如何連入因特網(wǎng),以及數(shù)據(jù)如何在它們之間傳輸?shù)臉?biāo)準(zhǔn)。它的內(nèi)部包含一系列的用于處理數(shù)據(jù)通信的協(xié)議,并采用了4層的分層模型,每一層都呼叫它的下一層所提供的協(xié)議來完成自己的需求。
    tcp_ip.jpg

    上圖中,TCP/IP協(xié)議中的四層分別是應(yīng)用層、傳輸層、網(wǎng)絡(luò)層和鏈路層,每層分別負責(zé)不同的通信功能。
  • 數(shù)據(jù)鏈路層:鏈路層是用于定義物理傳輸通道,通常是對某些網(wǎng)絡(luò)連接設(shè)備的驅(qū)動協(xié)議,例如針對光纖、網(wǎng)線提供的驅(qū)動。
  • 網(wǎng)絡(luò)層:網(wǎng)絡(luò)層是整個TCP/IP協(xié)議的核心,它主要用于將傳輸?shù)臄?shù)據(jù)進行分組,將分組數(shù)據(jù)發(fā)送到目標(biāo)計算機或者網(wǎng)絡(luò)。
  • 運輸層:主要使網(wǎng)絡(luò)程序進行通信,在進行網(wǎng)絡(luò)通信時,可以采用TCP協(xié)議,也可以采用UDP協(xié)議。
  • 應(yīng)用層:主要負責(zé)應(yīng)用程序的協(xié)議,例如HTTP協(xié)議、FTP協(xié)議等。

(三)協(xié)議分類

通信的協(xié)議還是比較復(fù)雜的,java.net 包中包含的類和接口,它們提供低層次的通信細節(jié)。我們可以直接使用這些類和接口,來專注于網(wǎng)絡(luò)程序開發(fā),而不用考慮通信的細節(jié)。
java.net 包中提供了兩種常見的網(wǎng)絡(luò)協(xié)議的支持:

  • UDP:用戶數(shù)據(jù)報協(xié)議(User Datagram Protocol)。UDP是無連接通信協(xié)議,即在數(shù)據(jù)傳輸時,數(shù)據(jù)的發(fā)送端和接收端不建立邏輯連接。簡單來說,當(dāng)一臺計算機向另外一臺計算機發(fā)送數(shù)據(jù)時,發(fā)送端不會確認(rèn)接收端是否存在,就會發(fā)出數(shù)據(jù),同樣接收端在收到數(shù)據(jù)時,也不會向發(fā)送端反饋是否收到數(shù)據(jù)。
    由于使用UDP協(xié)議消耗資源小,通信效率高,所以通常都會用于音頻、視頻和普通數(shù)據(jù)的傳輸例如視頻會議都使用UDP協(xié)議,因為這種情況即使偶爾丟失一兩個數(shù)據(jù)包,也不會對接收結(jié)果產(chǎn)生太大影響。
    但是在使用UDP協(xié)議傳送數(shù)據(jù)時,由于UDP的面向無連接性,不能保證數(shù)據(jù)的完整性,因此在傳輸重要數(shù)據(jù)時不建議使用UDP協(xié)議。UDP的交換過程如下圖所示。
    image.png

特點:數(shù)據(jù)包被限制在64kb以內(nèi),超出這個范圍就不能發(fā)送了。
數(shù)據(jù)包(Datagram):網(wǎng)絡(luò)傳輸?shù)幕締挝?/p>

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

特點:在TCP連接中必須要明確客戶端與服務(wù)器端,由客戶端向服務(wù)端發(fā)出連接請求,每次連接的創(chuàng)建都需要經(jīng)過“三次握手”。

  • 三次握手:TCP協(xié)議中,在發(fā)送數(shù)據(jù)的準(zhǔn)備階段,客戶端與服務(wù)器之間的三次交互,以保證連接的可靠。
    • 第一次握手,客戶端向服務(wù)器端發(fā)出連接請求,等待服務(wù)器確認(rèn)。
    • 第二次握手,服務(wù)器端向客戶端回送一個響應(yīng),通知客戶端收到了連接請求。
    • 第三次握手,客戶端再次向服務(wù)器端發(fā)送確認(rèn)信息,確認(rèn)連接。整個交互過程如下圖所示。
4_tcp.jpg

完成三次握手,連接建立后,客戶端和服務(wù)器就可以開始進行數(shù)據(jù)傳輸了。由于這種面向連接的特性,TCP協(xié)議可以保證傳輸數(shù)據(jù)的安全,所以應(yīng)用十分廣泛,例如下載文件、瀏覽網(wǎng)頁等。

(四)網(wǎng)絡(luò)編程三要素

1.協(xié)議
  • 協(xié)議:計算機網(wǎng)絡(luò)通信必須遵守的規(guī)則。
2.IP地址
  • IP地址:指互聯(lián)網(wǎng)協(xié)議地址(Internet Protocol Address),俗稱IP。IP地址用來給一個網(wǎng)絡(luò)中的計算機設(shè)備做唯一的編號。假如我們把“個人電腦”比作“一臺電話”的話,那么“IP地址”就相當(dāng)于“電話號碼”。

① IP地址分類

  • IPv4:是一個32位的二進制數(shù),通常被分為4個字節(jié),表示成a.b.c.d 的形式,例如192.168.65.100 。其中a、b、c、d都是0~255之間的十進制整數(shù),那么最多可以表示42億個。
  • IPv6:由于互聯(lián)網(wǎng)的蓬勃發(fā)展,IP地址的需求量愈來愈大,但是網(wǎng)絡(luò)地址資源有限,使得IP的分配越發(fā)緊張。
    為了擴大地址空間,擬通過IPv6重新定義地址空間,采用128位地址長度,每16個字節(jié)一組,分成8組十六進制數(shù),表示成ABCD:EF01:2345:6789:ABCD:EF01:2345:6789,號稱可以為全世界的每一粒沙子編上一個網(wǎng)址,這樣就解決了網(wǎng)絡(luò)地址資源數(shù)量不夠的問題。

② 常用命令

  • 查看本機IP地址,在控制臺輸入:
ipconfig
  • 檢查網(wǎng)絡(luò)是否連通,在控制臺輸入:
ping 空格 IP地址
ping 220.181.57.216
  • 特殊的IP地址
本機IP地址:127.0.0.1、localhost
3.端口號

網(wǎng)絡(luò)的通信,本質(zhì)上是兩個進程(應(yīng)用程序)的通信。每臺計算機都有很多的進程,那么在網(wǎng)絡(luò)通信時,如何區(qū)分這些進程呢?
如果說IP地址可以唯一標(biāo)識網(wǎng)絡(luò)中的設(shè)備,那么端口號就可以唯一標(biāo)識設(shè)備中的進程(應(yīng)用程序)了。

  • 端口號:用兩個字節(jié)表示的整數(shù),它的取值范圍是0~65535。其中,0~1023之間的端口號用于一些知名的網(wǎng)絡(luò)服務(wù)和應(yīng)用,普通的應(yīng)用程序需要使用1024以上的端口號。如果端口號被另外一個服務(wù)或應(yīng)用所占用,會導(dǎo)致當(dāng)前程序啟動失敗。

利用協(xié)議+IP地址+端口號 三元組合,就可以標(biāo)識網(wǎng)絡(luò)中的進程了,那么進程間的通信就可以利用這個標(biāo)識與其它進程進行交互。

二.TCP通信程序

(一)概述

TCP通信能實現(xiàn)兩臺計算機之間的數(shù)據(jù)交互,通信的兩端,要嚴(yán)格區(qū)分為客戶端(Client)與服務(wù)端(Server)。

1.兩端通信時步驟:

① 服務(wù)端程序,需要事先啟動,等待客戶端的連接。
② 客戶端主動連接服務(wù)器端,連接成功才能通信。服務(wù)端不可以主動連接客戶端。

2.在Java中,提供了兩個類用于實現(xiàn)TCP通信程序:
  • 客戶端:java.net.Socket 類表示。創(chuàng)建Socket對象,向服務(wù)端發(fā)出連接請求,服務(wù)端響應(yīng)請求,兩者建立連接開始通信。
  • 服務(wù)端:java.net.ServerSocket 類表示。創(chuàng)建ServerSocket對象,相當(dāng)于開啟一個服務(wù),并等待客戶端的連接。

(二)Socket類

Socket 類:該類實現(xiàn)客戶端套接字,套接字指的是兩臺設(shè)備之間通訊的端點。

1.構(gòu)造方法
  • public Socket(String host, int port):創(chuàng)建套接字對象并將其連接到指定主機上的指定端口號。如果指定的host是null ,則相當(dāng)于指定地址為回送地址。
    參數(shù):String host:服務(wù)器主機的名稱/服務(wù)器的IP地址,int port:服務(wù)器的端口號

小貼士:回送地址(127.x.x.x) 是本機回送地址(Loopback Address),主要用于網(wǎng)絡(luò)軟件測試以及本地機進程間通信,無論什么程序,一旦使用回送地址發(fā)送數(shù)據(jù),立即返回,不進行任何網(wǎng)絡(luò)傳輸。

構(gòu)造舉例,代碼如下:

Socket client = new Socket("127.0.0.1", 6666);
2.成員方法
  • public InputStream getInputStream() : 返回此套接字的輸入流。
    • 如果此Scoket具有相關(guān)聯(lián)的通道,則生成的InputStream 的所有操作也關(guān)聯(lián)該通道。
    • 關(guān)閉生成的InputStream也將關(guān)閉相關(guān)的Socket。
  • public OutputStream getOutputStream() : 返回此套接字的輸出流。
    • 如果此Scoket具有相關(guān)聯(lián)的通道,則生成的OutputStream 的所有操作也關(guān)聯(lián)該通道。
    • 關(guān)閉生成的OutputStream也將關(guān)閉相關(guān)的Socket。
  • public void close() :關(guān)閉此套接字。
    • 一旦一個socket被關(guān)閉,它不可再使用。
    • 關(guān)閉此socket也將關(guān)閉相關(guān)的InputStream和OutputStream 。
  • public void shutdownOutput() : 禁用此套接字的輸出流。
    • 任何先前寫出的數(shù)據(jù)將被發(fā)送,隨后終止輸出流。

(三)ServerSocket類

ServerSocket類:這個類實現(xiàn)了服務(wù)器套接字,該對象等待通過網(wǎng)絡(luò)的請求。

1.構(gòu)造方法
  • public ServerSocket(int port) :使用該構(gòu)造方法在創(chuàng)建ServerSocket對象時,就可以將其綁定到一個指定的端口號上。
    參數(shù):port:服務(wù)器的端口號。

構(gòu)造舉例,代碼如下:

ServerSocket server = new ServerSocket(6666);
2.成員方法
  • public Socket accept() :偵聽并接受連接,返回一個新的Socket對象,用于和客戶端實現(xiàn)通信。該方法會一直阻塞直到建立連接。

(四)簡單的TCP網(wǎng)絡(luò)程序

  1. 【服務(wù)端】啟動,創(chuàng)建ServerSocket對象,等待連接。
  2. 【客戶端】啟動,創(chuàng)建Socket對象,請求連接。
  3. 【服務(wù)端】ServerSocket對象,接收連接,調(diào)用accept方法,并返回一個Socket對象。
  4. 【客戶端】Socket對象,獲取OutputStream,向服務(wù)端發(fā)送數(shù)據(jù)。
  5. 【服務(wù)端】Scoket對象,獲取InputStream,讀取客戶端發(fā)送的數(shù)據(jù)。

至此,客戶端向服務(wù)端發(fā)送數(shù)據(jù)成功。

簡單通信.jpg
  1. 【服務(wù)端】Socket對象,獲取OutputStream,向客戶端回寫數(shù)據(jù)。
  2. 【客戶端】Scoket對象,獲取InputStream,解析回寫數(shù)據(jù)。
  3. 【客戶端】釋放資源,斷開連接。

至此,服務(wù)端向客戶端回寫數(shù)據(jù)成功。

服務(wù)端代碼:

public class ServerTCP {
    public static void main(String[] args) {
        System.out.println("服務(wù)端啟動,等待連接...");
        try {
            // 創(chuàng)建 ServerSocket對象,綁定端口,開始等待連接
            ServerSocket serverSocket = new ServerSocket(6666);
            // 接收連接 accept 方法, 返回socket對象.
            Socket clientSocket = serverSocket.accept();
            /*
                服務(wù)端接收數(shù)據(jù)
             */
            // 通過socket獲取輸入流
            InputStreamReader isr = new InputStreamReader(clientSocket.getInputStream(), "UTF-8");
            int len;
            while ((len = isr.read()) != -1) {
                // 解析數(shù)組,打印字符串信息
                System.out.print((char) len);
            }
            /*
                服務(wù)端發(fā)送數(shù)據(jù)
             */
            // 通過socket獲取輸出流
            BufferedOutputStream bos = new BufferedOutputStream(clientSocket.getOutputStream());
            // 發(fā)送數(shù)據(jù):你好,客戶端!
            bos.write("你好,客戶端!".getBytes());
            bos.flush();
            clientSocket.shutdownOutput();

            // 關(guān)閉資源.
            isr.close();
            bos.close();
            clientSocket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

客戶端代碼:

public class ClientTCP {
    public static void main(String[] args) {
        try {
            System.out.println("客戶端啟動,正在建立連接...");
            /*
                客戶端發(fā)送數(shù)據(jù)
             */
            // 創(chuàng)建Socket(ip , port), 確定連接到哪里.
            Socket clientSocket = new Socket("127.0.0.1", 6666);
            // 獲取流對象:輸出流
            BufferedOutputStream bos = new BufferedOutputStream(clientSocket.getOutputStream());
            // 發(fā)送數(shù)據(jù):你好,服務(wù)器!
            bos.write("你好,服務(wù)器!".getBytes());
            bos.flush();
            clientSocket.shutdownOutput();
            /*
                客戶端接收數(shù)據(jù)
             */
            // 獲取流對象:輸入流
            InputStreamReader isr = new InputStreamReader(clientSocket.getInputStream(), "UTF-8");
            int len;
            while ((len = isr.read()) != -1) {
                // 解析數(shù)組,打印字符串信息
                System.out.print((char)len);
            }

            // 關(guān)閉資源.
            bos.close();
            isr.close();
            clientSocket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

運行結(jié)果:

服務(wù)端:

image.png

客戶端:

image.png

(五)文件上傳案例

1.文件上傳分析圖解
  1. 【客戶端】輸入流,從硬盤讀取文件數(shù)據(jù)到程序中。
  2. 【客戶端】輸出流,寫出文件數(shù)據(jù)到服務(wù)端。
  3. 【服務(wù)端】輸入流,讀取文件數(shù)據(jù)到服務(wù)端程序。
  4. 【服務(wù)端】輸出流,寫出文件數(shù)據(jù)到服務(wù)器硬盤中。
  5. 【服務(wù)端】獲取輸出流,回寫數(shù)據(jù)。
  6. 【客戶端】獲取輸入流,解析回寫數(shù)據(jù)。
2.文件上傳優(yōu)化分析

文件名稱寫死的問題

服務(wù)端,保存文件的名稱如果寫死,那么最終導(dǎo)致服務(wù)器硬盤,只會保留一個文件,建議使用系統(tǒng)時間優(yōu)化,保證文件名稱唯一,代碼如下:

FileOutputStream fis = new FileOutputStream(System.currentTimeMillis()+".pdf") // 文件名稱
BufferedOutputStream bos = new BufferedOutputStream(fis);

循環(huán)接收的問題

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

// 每次接收新的連接,創(chuàng)建一個Socket
while(true){
    Socket accept = serverSocket.accept();
    ......
}

效率問題

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

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

服務(wù)端代碼:

public class FileUploadServer {
    public static void main(String[] args) throws IOException {
        // 創(chuàng)建服務(wù)端ServerSocket
        System.out.println("服務(wù)端啟動,等待上傳文件...");
        ServerSocket serverSocket = new ServerSocket(6666);
        // 循環(huán)接收,建立連接
        // serverSocket對象交給子線程處理,進行讀寫操作
        while (true) {
            Socket clientSocket = serverSocket.accept();
            new Thread(() -> {
                try {

                    // 文件名上傳時間格式化
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日HH時mm分ss秒");
                    // 文件名
                    String fileName = "java " + sdf.format(new Date(System.currentTimeMillis())) + ".pdf";
                    // 文件保存路徑
                    String path = "code1\\src\\cn\\cxy\\demo24\\test2\\server_path\\" + fileName;
                    // 獲取網(wǎng)絡(luò)輸入流對象,用來接收文件
                    BufferedInputStream bisWeb = new BufferedInputStream(clientSocket.getInputStream());
                    // 獲取網(wǎng)絡(luò)輸入流對象,用來回寫數(shù)據(jù)
                    OutputStreamWriter oswWeb = new OutputStreamWriter(clientSocket.getOutputStream(), "UTF-8");
                    // 創(chuàng)建本地輸出流對象,將文件保存到本地
                    BufferedOutputStream bosLocal = new BufferedOutputStream(new FileOutputStream(path));
                    /*
                      接收文件
                     */
                    // 用來接收文件的緩存字節(jié)數(shù)組
                    byte[] receive = new byte[1024 * 8];
                    int len;
                    // 讀寫文件(網(wǎng)絡(luò)->本地)
                    while ((len = bisWeb.read(receive)) != -1) {
                        bosLocal.write(receive, 0, len);
                    }
                    System.out.println("線程:" + Thread.currentThread().getName() + "上傳文件成功--->"
                            + "文件《" + fileName + "》已保存!" + "\n=======================");
                    /*
                      回寫數(shù)據(jù)
                     */
                    oswWeb.write("服務(wù)器已接收上傳的文件!");
                    oswWeb.flush();
                    // 寫出數(shù)據(jù)完畢
                    clientSocket.shutdownOutput();

                    // 關(guān)閉資源
                    bisWeb.close();
                    bosLocal.close();
                    oswWeb.close();
                    clientSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

客戶端代碼:

public class FileUploadClient1 {
    public static void main(String[] args) {
        try {
            System.out.println("客戶端啟動,準(zhǔn)備上傳文件...");
            // 創(chuàng)建客戶端clientSocket
            Socket clientSocket = new Socket("127.0.0.1", 6666);
            // 獲取網(wǎng)絡(luò)輸出流對象,用來發(fā)送文件
            BufferedOutputStream bosWeb = new BufferedOutputStream(clientSocket.getOutputStream());
            // 獲取網(wǎng)絡(luò)輸入流對象,用來接收服務(wù)器回寫的數(shù)據(jù)
            InputStreamReader isrWeb = new InputStreamReader(clientSocket.getInputStream());
            // 創(chuàng)建本地輸出流對象,將本地文件上傳
            BufferedInputStream bisLocal = new BufferedInputStream(new FileInputStream("code1\\src\\cn\\cxy\\demo24\\test2\\client_path\\java.pdf"));
            /*
              發(fā)送文件
             */
            // 用來發(fā)送文件的緩存字節(jié)數(shù)組
            byte[] send = new byte[1024 * 8];
            int len;
            // 讀寫文件(本地->網(wǎng)絡(luò))
            while ((len = bisLocal.read(send)) != -1) {
                bosWeb.write(send, 0, len);
            }
            System.out.println("文件上傳成功!");
            bosWeb.flush();
            // 寫出數(shù)據(jù)完畢
            clientSocket.shutdownOutput();
            /*
              接收回寫的數(shù)據(jù)
             */
            char[] back = new char[20];
            // 使用指定編碼字符流讀取
            while (isrWeb.read(back) != -1) {
                System.out.print(back);
            }
            // 關(guān)閉資源
            bisLocal.close();
            bosWeb.close();
            isrWeb.close();
            clientSocket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

運行結(jié)果:

服務(wù)端:

image.png

客戶端:

image.png

三.模擬B\S服務(wù)器

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

服務(wù)端代碼:

public class WebServer {
    public static void main(String[] args) {
        // 創(chuàng)建服務(wù)端ServerSocket
        System.out.println("服務(wù)端啟動,等待瀏覽器訪問...");
        try {
            ServerSocket serverSocket = new ServerSocket(8888);
            // 使用線程池工廠類Executors里邊提供的靜態(tài)方法newFixedThreadPool()生產(chǎn)一個指定數(shù)量的線程池
            ExecutorService service = Executors.newFixedThreadPool(5);
            while (true) {
                Socket clientSocket = serverSocket.accept();
                //自己創(chuàng)建線程對象的方式
                service.submit(new Web(clientSocket));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    static class Web implements Runnable {
        private Socket socket;

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

        @Override
        public void run() {
            try {
                BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                String request = br.readLine();
                // 取出請求資源的路徑
                String[] strArr = request.split(" ");
                // 以空格作為分隔符取出位置1上的元素為要訪問的地址
                String path = "code1/src/cn/cxy/demo24/text3" + strArr[1];

                FileInputStream fis = new FileInputStream(path);
                byte[] bytes = new byte[1024];
                int len;

                // 向瀏覽器 回寫數(shù)據(jù)
                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);
                }
                // 關(guān)閉資源
                br.close();
                fis.close();
                out.close();
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

運行結(jié)果:

image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容