<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      今日內(nèi)容

      • 網(wǎng)絡(luò)編程三要素
        • IP
        • 端口號
        • 協(xié)議
      • TCP通信------->掌握
        • 模擬兩臺電腦相互之間互發(fā)信息(聊天)
        • 模擬文件上傳
        • 模擬B/S結(jié)構(gòu)軟件的服務(wù)器
      • NIO------>難點\理解
        • Buffer緩沖數(shù)組
        • Channel通道
        • Selector選擇器
      • NIO2(AIO)------>難點\理解
        • 異步非阻塞

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

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

      • C/S結(jié)構(gòu) :全稱為Client/Server結(jié)構(gòu),是指客戶端和服務(wù)器結(jié)構(gòu)。常見程序有QQ、迅雷等軟件。
      • 特點: 客戶端和服務(wù)器是分開的,需要下載客戶端,對網(wǎng)絡(luò)要求相對低,減少服務(wù)器壓力,相對穩(wěn)定, 開發(fā)和維護(hù)成本高

      1566446300784

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

      特點:沒有客戶端,只有服務(wù)器,不需要下載客戶端,直接通過瀏覽器訪問, 對網(wǎng)絡(luò)要求相對高 ,服務(wù)器壓力很大,相對不穩(wěn)定,開發(fā)和維護(hù)成本低

      1566446315067

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

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

      協(xié)議

      網(wǎng)絡(luò)通信協(xié)議:通信協(xié)議是計算機必須遵守的規(guī)則,只有遵守這些規(guī)則,計算機之間才能進(jìn)行通信。這就好比在道路中行駛的汽車一定要遵守交通規(guī)則一樣,協(xié)議中對數(shù)據(jù)的傳輸格式、傳輸速率、傳輸步驟等做了統(tǒng)一規(guī)定,通信雙方必須同時遵守,最終完成數(shù)據(jù)交換。

      java.net 包中提供了兩種常見的網(wǎng)絡(luò)協(xié)議的支持:

      • TCP:傳輸控制協(xié)議 (Transmission Control Protocol)。TCP協(xié)議是面向連接的通信協(xié)議,即傳輸數(shù)據(jù)之前,在發(fā)送端和接收端建立邏輯連接,然后再傳輸數(shù)據(jù),它提供了兩臺計算機之間可靠無差錯的數(shù)據(jù)傳輸。
      • TCP協(xié)議特點: 面向連接,傳輸數(shù)據(jù)安全,傳輸速度慢
      • 例如: 村長發(fā)現(xiàn)張三家的牛丟了
      • TCP協(xié)議: 村長一定要找到張三,面對面的告訴他他家的牛丟了 打電話: 電話一定要接通,并且是張三接的
        • 連接三次握手:TCP協(xié)議中,在發(fā)送數(shù)據(jù)的準(zhǔn)備階段,客戶端與服務(wù)器之間的三次交互,以保證連接的可靠。
          • 第一次握手,客戶端向服務(wù)器端發(fā)出連接請求,等待服務(wù)器確認(rèn)。 你愁啥?
          • 第二次握手,服務(wù)器端向客戶端回送一個響應(yīng),通知客戶端收到了連接請求。我愁你咋地?
          • 第三次握手,客戶端再次向服務(wù)器端發(fā)送確認(rèn)信息,確認(rèn)連接。整個交互過程如下圖所示。你再愁試試

      1566446712862

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

      • UDP:用戶數(shù)據(jù)報協(xié)議(User Datagram Protocol)。UDP協(xié)議是一個面向無連接的協(xié)議。傳輸數(shù)據(jù)時,不需要建立連接,不管對方端服務(wù)是否啟動,直接將數(shù)據(jù)、數(shù)據(jù)源和目的地都封裝在數(shù)據(jù)包中,直接發(fā)送。每個數(shù)據(jù)包的大小限制在64k以內(nèi)。它是不可靠協(xié)議,因為無連接,所以傳輸速度快,但是容易丟失數(shù)據(jù)。日常應(yīng)用中,例如視頻會議、QQ聊天等。
      • UDP特點: 面向無連接,傳輸數(shù)據(jù)不安全,傳輸速度快
      • 例如: 村長發(fā)現(xiàn)張三家的牛丟了
      • UDP協(xié)議: 村長在村里的廣播站廣播一下張三家的牛丟了,信息丟失,信息發(fā)布速度快

      IP地址

      • IP地址:指互聯(lián)網(wǎng)協(xié)議地址(Internet Protocol Address),俗稱IP。IP地址用來給一個網(wǎng)絡(luò)中的計算機設(shè)備做唯一的編號。相當(dāng)于每個人的身份證號碼。

      **IP地址分類 **

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

      • IPv6:由于互聯(lián)網(wǎng)的蓬勃發(fā)展,IP地址的需求量愈來愈大,但是網(wǎng)絡(luò)地址資源有限,使得IP的分配越發(fā)緊張。有資料顯示,全球IPv4地址在2011年2月分配完畢。

        為了擴大地址空間,擬通過IPv6重新定義地址空間,采用128位地址長度,每16個字節(jié)一組,分成8組十六進(jìn)制數(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
      ping www.baidu.com
      

      特殊的IP地址

      • 本機IP地址:127.0.0.1localhost

      端口號

      網(wǎng)絡(luò)的通信,本質(zhì)上是兩個進(jìn)程(應(yīng)用程序)的通信。每臺計算機都有很多的進(jìn)程,那么在網(wǎng)絡(luò)通信時,如何區(qū)分這些進(jìn)程呢?

      如果說IP地址可以唯一標(biāo)識網(wǎng)絡(luò)中的設(shè)備,那么端口號就可以唯一標(biāo)識設(shè)備中的進(jìn)程(應(yīng)用程序)了。

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

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

      域名

      • 域名----->綁定了ip地址
      • 域名是唯一的

      1.3 InetAddress類

      InetAddress類的概述

      • 一個該類的對象就代表一個IP地址對象。

      InetAddress類的方法

      • static InetAddress getLocalHost() 獲得本地主機IP地址對象

      • static InetAddress getByName(String host) 根據(jù)IP地址字符串或主機名獲得對應(yīng)的IP地址對象

      • String getHostName();獲得主機名

      • String getHostAddress();獲得IP地址字符串

        /**
         * Created by PengZhiLin on 2021/8/13 9:57
         */
        public class Test {
            public static void main(String[] args) throws Exception {
                //* static InetAddress getLocalHost()   獲得本地主機IP地址對象
                //* static InetAddress getByName(String host) 根據(jù)IP地址字符串或主機名獲得對應(yīng)的IP地址對象
                // 獲得本機的ip地址對象
                InetAddress ip1 = InetAddress.getLocalHost();
                System.out.println("ip1:" + ip1);// DESKTOP-F810C9C/192.168.30.98
        
                InetAddress ip2 = InetAddress.getByName("DESKTOP-F810C9C");
                System.out.println("ip2:" + ip2);// DESKTOP-F810C9C/192.168.30.98
        
                //* String getHostName();獲得主機名
                //* String getHostAddress();獲得IP地址字符串
                System.out.println("主機名:"+ip1.getHostName());
                System.out.println("ip地址:"+ip1.getHostAddress());
        
                System.out.println(InetAddress.getLocalHost().getHostAddress());
                System.out.println("192.168.30.98");
                System.out.println("127.0.0.1");
            }
        }
        
        

      第二章 TCP通信程序

      2.1 TCP

      TCP通信的流程

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

      1566446503937

      TCP協(xié)議相關(guān)的類

      • Socket : 一個該類的對象就代表一個客戶端程序。
        • Socket(String host, int port) 根據(jù)ip地址字符串和端口號創(chuàng)建客戶端Socket對象
          * 注意事項:只要執(zhí)行該方法,就會立即連接指定的服務(wù)器程序,如果連接不成功,則會拋出異常。
          如果連接成功,則表示三次握手通過。
        • OutputStream getOutputStream(); 獲得字節(jié)輸出流對象
        • InputStream getInputStream();獲得字節(jié)輸入流對象
        • void close();關(guān)閉Socket, 會自動關(guān)閉相關(guān)的流
        • 補充:關(guān)閉通過socket獲得的流,會關(guān)閉socket,關(guān)閉socket,同時也會關(guān)閉通過socket獲得的流
      • ServerSocket : 一個該類的對象就代表一個服務(wù)器端程序。
        • ServerSocket(int port); 根據(jù)指定的端口號開啟服務(wù)器。
        • Socket accept(); 等待客戶端連接并獲得與客戶端關(guān)聯(lián)的Socket對象 如果沒有客戶端連接,該方法會一直阻塞
        • void close();關(guān)閉ServerSocket

      2.2 TCP通信案例1

      需求

      • 客戶端向服務(wù)器發(fā)送字符串?dāng)?shù)據(jù)

      分析

      客戶端:
      1.創(chuàng)建Socket對象,指定要連接的服務(wù)器的ip地址和端口號
      2.通過Socket對象獲得字節(jié)輸出流對象
      3.使用字節(jié)輸出流寫字符串?dāng)?shù)據(jù)到連接通道中
      4.釋放資源
      
      服務(wù)器:
      1.創(chuàng)建ServerSocket對象,指定服務(wù)器的端口號
      2.調(diào)用accpet()方法,接收客戶端請求,建立連接,返回Socket對象
      3.通過返回的Socket對象獲得字節(jié)輸入流
      4.讀數(shù)據(jù)
      5.釋放資源
      

      實現(xiàn)

      • 客戶端代碼實現(xiàn)

        /**
         * Created by PengZhiLin on 2021/8/13 10:16
         */
        public class Client {
            public static void main(String[] args) throws Exception{
                //1.創(chuàng)建Socket對象,指定要連接的服務(wù)器的ip地址和端口號
                Socket socket = new Socket("127.0.0.1",6666);
        
                //2.通過Socket對象獲得字節(jié)輸出流對象
                OutputStream os = socket.getOutputStream();
        
                //3.使用字節(jié)輸出流寫字符串?dāng)?shù)據(jù)到連接通道中
                os.write("服務(wù)器你好,今晚約嗎?".getBytes());
        
                //4.釋放資源
                socket.close();
            }
        }
        
        
      • 服務(wù)端代碼實現(xiàn)

        /**
         * Created by PengZhiLin on 2021/8/13 10:16
         */
        public class Server {
            public static void main(String[] args) throws Exception{
                //1.創(chuàng)建ServerSocket對象,指定服務(wù)器的端口號
                ServerSocket ss = new ServerSocket(6666);
        
                //2.調(diào)用accpet()方法,接收客戶端請求,建立連接,返回Socket對象
                Socket socket = ss.accept();
        
                //3.通過返回的Socket對象獲得字節(jié)輸入流
                InputStream is = socket.getInputStream();
        
                //4.讀數(shù)據(jù)
                byte[] bys = new byte[1024];
                int len = is.read(bys);
                System.out.println("服務(wù)器接收到的信息:"+new String(bys,0,len));
        
                //5.釋放資源
                socket.close();
                ss.close();
            }
        }
        
        

      2.3 TCP通信案例2

      需求

      • 客戶端向服務(wù)器發(fā)送字符串?dāng)?shù)據(jù),服務(wù)器回寫字符串?dāng)?shù)據(jù)給客戶端(模擬聊天)

      分析

      客戶端:
      1.創(chuàng)建Socket對象,指定要連接的服務(wù)器的ip地址和端口號
      2.通過Socket對象獲得字節(jié)輸出流對象
      3.使用字節(jié)輸出流寫字符串?dāng)?shù)據(jù)到連接通道中
      4.通過Socket對象獲得字節(jié)輸入流對象
      5.讀服務(wù)器回寫的數(shù)據(jù)
      6.釋放資源
      
      服務(wù)器:
      1.創(chuàng)建ServerSocket對象,指定服務(wù)器的端口號
      2.調(diào)用accpet()方法,接收客戶端請求,建立連接,返回Socket對象
      3.通過返回的Socket對象獲得字節(jié)輸入流
      4.讀數(shù)據(jù)
      5.通過返回的Socket對象獲得字節(jié)輸出流
      6.使用字節(jié)輸出流寫字符串?dāng)?shù)據(jù)到連接通道中
      7.釋放資源
      

      實現(xiàn)

      • TCP客戶端代碼
      /**
       * Created by PengZhiLin on 2021/8/13 10:16
       */
      public class Client {
          public static void main(String[] args) throws Exception{
              //1.創(chuàng)建Socket對象,指定要連接的服務(wù)器的ip地址和端口號
              Socket socket = new Socket("127.0.0.1",6666);
      
              //2.通過Socket對象獲得字節(jié)輸出流對象
              OutputStream os = socket.getOutputStream();
      
              //3.使用字節(jié)輸出流寫字符串?dāng)?shù)據(jù)到連接通道中
              os.write("服務(wù)器你好,今晚約嗎?".getBytes());
      
              //4.通過Socket對象獲得字節(jié)輸入流對象
              InputStream is = socket.getInputStream();
      
              //5.讀服務(wù)器回寫的數(shù)據(jù)
              byte[] bys = new byte[1024];
              int len = is.read(bys);
              System.out.println("客戶端接收到的信息:"+new String(bys,0,len));
      
              //6.釋放資源
              socket.close();
          }
      }
      
      
      • 服務(wù)端代碼實現(xiàn)
      /**
       * Created by PengZhiLin on 2021/8/13 10:16
       */
      public class Server {
          public static void main(String[] args) throws Exception{
              //1.創(chuàng)建ServerSocket對象,指定服務(wù)器的端口號
              ServerSocket ss = new ServerSocket(6666);
      
              //2.調(diào)用accpet()方法,接收客戶端請求,建立連接,返回Socket對象
              Socket socket = ss.accept();
      
              //3.通過返回的Socket對象獲得字節(jié)輸入流
              InputStream is = socket.getInputStream();
      
              //4.讀數(shù)據(jù)
              byte[] bys = new byte[1024];
              int len = is.read(bys);
              System.out.println("服務(wù)器接收到的信息:"+new String(bys,0,len));
      
              //5.通過返回的Socket對象獲得字節(jié)輸出流
              OutputStream os = socket.getOutputStream();
      
              //6.使用字節(jié)輸出流寫字符串?dāng)?shù)據(jù)到連接通道中
              os.write("客戶端你好,今晚小樹林見!".getBytes());
      
              //7.釋放資源
              socket.close();
              ss.close();
          }
      }
      
      

      2.4 擴展模擬循環(huán)聊天

      • 服務(wù)器

        
        /**
         * Created by PengZhiLin on 2021/8/13 10:16
         */
        public class Server {
            public static void main(String[] args) throws Exception{
                //1.創(chuàng)建ServerSocket對象,指定服務(wù)器的端口號
                ServerSocket ss = new ServerSocket(6666);
        
                //2.調(diào)用accpet()方法,接收客戶端請求,建立連接,返回Socket對象
                Socket socket = ss.accept();
        
                // 循環(huán)
                while (true) {
                    //3.通過返回的Socket對象獲得字節(jié)輸入流
                    InputStream is = socket.getInputStream();
        
                    //4.讀數(shù)據(jù)
                    byte[] bys = new byte[1024];
                    int len = is.read(bys);
                    System.out.println("服務(wù)器接收到的信息:" + new String(bys, 0, len));
        
                    //5.通過返回的Socket對象獲得字節(jié)輸出流
                    OutputStream os = socket.getOutputStream();
        
                    //6.使用字節(jié)輸出流寫字符串?dāng)?shù)據(jù)到連接通道中
                    Scanner sc = new Scanner(System.in);
                    System.out.println("請輸入您要對客戶端說的話:");
                    String msg = sc.next();
                    os.write(msg.getBytes());
        
                    //7.釋放資源
                    //socket.close();
                    //ss.close();
                }
            }
        }
        
        
      • 客戶端

        /**
         * Created by PengZhiLin on 2021/8/13 10:16
         */
        public class Client {
            public static void main(String[] args) throws Exception{
                //1.創(chuàng)建Socket對象,指定要連接的服務(wù)器的ip地址和端口號
                Socket socket = new Socket("127.0.0.1",6666);
        
                // 循環(huán)
                while (true) {
                    //2.通過Socket對象獲得字節(jié)輸出流對象
                    OutputStream os = socket.getOutputStream();
        
                    //3.使用字節(jié)輸出流寫字符串?dāng)?shù)據(jù)到連接通道中
                    Scanner sc = new Scanner(System.in);
                    System.out.println("請輸入您要對服務(wù)器說的話:");
                    String msg = sc.next();
        
                    os.write(msg.getBytes());
        
                    //4.通過Socket對象獲得字節(jié)輸入流對象
                    InputStream is = socket.getInputStream();
        
                    //5.讀服務(wù)器回寫的數(shù)據(jù)
                    byte[] bys = new byte[1024];
                    int len = is.read(bys);
                    System.out.println("客戶端接收到的信息:"+new String(bys,0,len));
        
                    //6.釋放資源
                    //socket.close();
                }
            }
        }
        

      第三章 綜合案例

      3.1 文件上傳案例

      需求

      • 使用TCP協(xié)議, 通過客戶端向服務(wù)器上傳一個文件

      分析

      客戶端:
      1.創(chuàng)建Socket對象,指定要連接的服務(wù)器的ip地址和端口號
      2.創(chuàng)建字節(jié)輸入流對象,關(guān)聯(lián)要上傳的文件路徑
      3.通過Socket對象獲得字節(jié)輸出流對象
      4.定義一個byte數(shù)組,用來存儲讀取到的字節(jié)數(shù)據(jù)
      5.定義一個int變量,用來存儲讀取到的字節(jié)個數(shù)
      6.循環(huán)讀
      7.寫數(shù)據(jù)
      8.釋放資源
      
      服務(wù)器:
      1.創(chuàng)建ServerSocket對象,指定服務(wù)器的端口號
      2.調(diào)用accept方法,接收請求建立連接,返回Socket對象
      3.通過返回的Socket對象獲得字節(jié)輸入流對象
      4.創(chuàng)建字節(jié)輸出流對象,關(guān)聯(lián)目的地文件路徑
      5.定義一個byte數(shù)組,用來存儲讀取到的字節(jié)數(shù)據(jù)
      5.定義一個int變量,用來存儲讀取到的字節(jié)個數(shù)
      6.循環(huán)讀
      7.寫數(shù)據(jù)
      8.釋放資源
      
      

      1566446548503

      實現(xiàn)

      文件上傳

      • 服務(wù)器

        
        /**
         * Created by PengZhiLin on 2021/8/13 10:51
         */
        public class Server {
            public static void main(String[] args) throws Exception {
                //服務(wù)器:
                //1.創(chuàng)建ServerSocket對象,指定服務(wù)器的端口號
                ServerSocket ss = new ServerSocket(7777);
        
                //2.調(diào)用accept方法,接收請求建立連接,返回Socket對象
                Socket socket = ss.accept();
        
                //3.通過返回的Socket對象獲得字節(jié)輸入流對象
                InputStream is = socket.getInputStream();
        
                //4.創(chuàng)建字節(jié)輸出流對象,關(guān)聯(lián)目的地文件路徑
                FileOutputStream fos = new FileOutputStream("day12\\bbb\\mm2.jpg");
        
                //5.定義一個byte數(shù)組,用來存儲讀取到的字節(jié)數(shù)據(jù)
                byte[] bys = new byte[8192];
        
                //5.定義一個int變量,用來存儲讀取到的字節(jié)個數(shù)
                int len;
        
                //6.循環(huán)讀
                while ((len = is.read(bys)) != -1) {
                    //7.寫數(shù)據(jù)
                    fos.write(bys,0,len);
                }
        
                //8.釋放資源
                fos.close();
                socket.close();
                ss.close();
            }
        }
        
        
      • 客戶端

        
        /**
         * Created by PengZhiLin on 2021/8/13 10:51
         */
        public class Client {
            public static void main(String[] args) throws Exception{
                //客戶端:
                //1.創(chuàng)建Socket對象,指定要連接的服務(wù)器的ip地址和端口號
                Socket socket = new Socket("127.0.0.1",7777);
        
                //2.創(chuàng)建字節(jié)輸入流對象,關(guān)聯(lián)要上傳的文件路徑
                FileInputStream fis = new FileInputStream("day12\\aaa\\mm.jpg");
        
                //3.通過Socket對象獲得字節(jié)輸出流對象
                OutputStream os = socket.getOutputStream();
        
                //4.定義一個byte數(shù)組,用來存儲讀取到的字節(jié)數(shù)據(jù)
                byte[] bys = new byte[8192];
        
                //5.定義一個int變量,用來存儲讀取到的字節(jié)個數(shù)
                int len;
        
                //6.循環(huán)讀
                while ((len = fis.read(bys)) != -1) {
                    //7.寫數(shù)據(jù)
                    os.write(bys,0,len);
                }
                //8.釋放資源
                fis.close();
                socket.close();
        
            }
        }
        
        

      文件上傳成功后服務(wù)器回寫字符串?dāng)?shù)據(jù)

      • 服務(wù)器

        /**
         * Created by PengZhiLin on 2021/8/13 10:51
         */
        public class Server {
            public static void main(String[] args) throws Exception {
                //服務(wù)器:
                //1.創(chuàng)建ServerSocket對象,指定服務(wù)器的端口號
                ServerSocket ss = new ServerSocket(7777);
        
                //2.調(diào)用accept方法,接收請求建立連接,返回Socket對象
                Socket socket = ss.accept();
        
                //3.通過返回的Socket對象獲得字節(jié)輸入流對象
                InputStream is = socket.getInputStream();
        
                //4.創(chuàng)建字節(jié)輸出流對象,關(guān)聯(lián)目的地文件路徑
                FileOutputStream fos = new FileOutputStream("day12\\bbb\\mm4.jpg");
        
                //5.定義一個byte數(shù)組,用來存儲讀取到的字節(jié)數(shù)據(jù)
                byte[] bys = new byte[8192];
        
                //5.定義一個int變量,用來存儲讀取到的字節(jié)個數(shù)
                int len;
        
                System.out.println("服務(wù)器1");
                //6.循環(huán)讀
                // 循環(huán)讀客戶端寫過來的數(shù)據(jù)
                while ((len = is.read(bys)) != -1) {// 卡
                    //7.寫數(shù)據(jù)
                    fos.write(bys,0,len);
                }
                System.out.println("服務(wù)器2");
        
                //8.通過Socket獲得字節(jié)輸出流對象
                OutputStream os = socket.getOutputStream();
        
                //9.回寫上傳成功信息給客戶端
                os.write("文件上傳成功!".getBytes());
        
                //10.釋放資源
                fos.close();
                socket.close();
                ss.close();
            }
        }
        
        
      • 客戶端

        /**
         * Created by PengZhiLin on 2021/8/13 10:51
         */
        public class Client {
            public static void main(String[] args) throws Exception{
                //客戶端:
                //1.創(chuàng)建Socket對象,指定要連接的服務(wù)器的ip地址和端口號
                Socket socket = new Socket("127.0.0.1",7777);
        
                //2.創(chuàng)建字節(jié)輸入流對象,關(guān)聯(lián)要上傳的文件路徑
                FileInputStream fis = new FileInputStream("day12\\aaa\\mm.jpg");
        
                //3.通過Socket對象獲得字節(jié)輸出流對象
                OutputStream os = socket.getOutputStream();
        
                //4.定義一個byte數(shù)組,用來存儲讀取到的字節(jié)數(shù)據(jù)
                byte[] bys = new byte[8192];
        
                //5.定義一個int變量,用來存儲讀取到的字節(jié)個數(shù)
                int len;
        
                //6.循環(huán)讀
                while ((len = fis.read(bys)) != -1) {
                    //7.寫數(shù)據(jù)
                    os.write(bys,0,len);
                }
        
                // 問題原因:客戶端不再寫數(shù)據(jù)過來了,而服務(wù)器不知道,所以服務(wù)器一直在等待讀客戶端寫過來的數(shù)據(jù)
                // 解決辦法: 客戶端要告訴服務(wù)器,客戶端不再寫數(shù)據(jù)過來了
                // 禁止客戶端寫數(shù)據(jù)的功能
                socket.shutdownOutput();
        
                System.out.println("客戶端1");
        
                //8.通過Socket獲得字節(jié)輸入流對象
                InputStream is = socket.getInputStream();
        
                //9.讀服務(wù)器回寫的信息
                int len2 = is.read(bys);// 卡
                System.out.println("服務(wù)器回寫的信息是:"+new String(bys,0,len2));
        
                //10.釋放資源
                fis.close();
                socket.close();
        
            }
        }
        
        

      優(yōu)化文件上傳案例

      • 需要優(yōu)化的問題

        • 文件名----->目前是固定名稱,改成自動生成名稱
        • 服務(wù)器只能接收客戶端上傳一次文件---->改成服務(wù)器循環(huán)接收
        • 效率問題---->目前是單線程會影響效率,改成多線程
          • 張三先連接服務(wù)器,需要上傳一個2GB的文件
          • 李四后連接服務(wù)器,需要上傳一個2KB的文件
            • 單線程: 李四一定要等張三上傳完畢才可以上傳,所以李四需要等待的時間很長
            • 多線程: 張三,和李四就可以搶著執(zhí)行,李四就有可能先執(zhí)行完畢,就不需要等待
      • 優(yōu)化實現(xiàn)

        
        /**
         * Created by PengZhiLin on 2021/8/13 10:51
         */
        public class Server {
            public static void main(String[] args) throws Exception {
                //服務(wù)器:
                //1.創(chuàng)建ServerSocket對象,指定服務(wù)器的端口號
                ServerSocket ss = new ServerSocket(7777);
        
                // 循環(huán)
                while (true) {
        
                    //2.調(diào)用accept方法,接收請求建立連接,返回Socket對象
                    Socket socket = ss.accept();
        
                    // 開啟線程,實現(xiàn)上傳任務(wù)
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            try{
                                //3.通過返回的Socket對象獲得字節(jié)輸入流對象
                                InputStream is = socket.getInputStream();
        
                                //4.創(chuàng)建字節(jié)輸出流對象,關(guān)聯(lián)目的地文件路徑
                                FileOutputStream fos = new FileOutputStream("day12\\bbb\\" + System.currentTimeMillis() + ".jpg");
        
                                //5.定義一個byte數(shù)組,用來存儲讀取到的字節(jié)數(shù)據(jù)
                                byte[] bys = new byte[8192];
        
                                //5.定義一個int變量,用來存儲讀取到的字節(jié)個數(shù)
                                int len;
        
                                System.out.println("服務(wù)器1");
                                //6.循環(huán)讀
                                // 循環(huán)讀客戶端寫過來的數(shù)據(jù)
                                while ((len = is.read(bys)) != -1) {// 卡
                                    //7.寫數(shù)據(jù)
                                    fos.write(bys, 0, len);
                                }
                                System.out.println("服務(wù)器2");
        
                                //8.通過Socket獲得字節(jié)輸出流對象
                                OutputStream os = socket.getOutputStream();
        
                                //9.回寫上傳成功信息給客戶端
                                os.write("文件上傳成功!".getBytes());
        
                                //10.釋放資源
                                fos.close();
                                socket.close();
                                //ss.close();
                            }catch (Exception e){
                                e.printStackTrace();
                            }
                        }
                    }).start();
        
                }
            }
        }
        
        

      3.2 模擬B\S服務(wù)器 擴展

      需求

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

      分析

      1. 準(zhǔn)備頁面數(shù)據(jù),web文件夾。

      2. 我們模擬服務(wù)器端,ServerSocket類監(jiān)聽端口,使用瀏覽器訪問,查看網(wǎng)頁效果

      3. 注意:

        // 1.瀏覽器工作原理是遇到圖片會開啟一個線程進(jìn)行單獨的訪問,因此在服務(wù)器端加入線程技術(shù)。
        // 2. 響應(yīng)頁面的時候需要同時把以下信息響應(yīng)過去給瀏覽器
        os.write("HTTP/1.1 200 OK\r\n".getBytes());
        os.write("Content-Type:text/html\r\n".getBytes());
        os.write("\r\n".getBytes());
        
        1.創(chuàng)建ServerSocket對象,指定服務(wù)器的端口號為8888
        2.調(diào)用accept方法接收請求,建立連接,返回Socket對象
        3.通過返回的Socket對象獲得字節(jié)輸入流
        4.讀請求里面的數(shù)據(jù)
        5.從讀到的數(shù)據(jù)中篩選出要訪問的頁面路徑--->day12/web/index.html
        6.創(chuàng)建字節(jié)輸入流,關(guān)聯(lián)瀏覽器要訪問的頁面路徑
        7.通過Socket對象獲得字節(jié)輸出流
        8.定義一個byte數(shù)組,用來存儲讀取到的字節(jié)數(shù)據(jù)
        8.定義一個int變量,用來存儲讀取到的字節(jié)個數(shù)
        9.循環(huán)讀
        10.在循環(huán)中,寫數(shù)據(jù)
        11.釋放資源
        
        

      實現(xiàn)

      
      /**
       * Created by PengZhiLin on 2021/8/13 11:37
       */
      public class Server {
          public static void main(String[] args) throws Exception {
              //1.創(chuàng)建ServerSocket對象,指定服務(wù)器的端口號為8888
              ServerSocket ss = new ServerSocket(8888);
      
              while (true) {
                  //2.調(diào)用accept方法接收請求,建立連接,返回Socket對象
                  Socket socket = ss.accept();
      
                  // 開啟線程
                  new Thread(new Runnable() {
                      @Override
                      public void run() {
                          try {
                              //3.通過返回的Socket對象獲得字節(jié)輸入流
                              InputStream is = socket.getInputStream();
      
                              //4.讀請求里面的數(shù)據(jù)
                              InputStreamReader isr = new InputStreamReader(is);
                              BufferedReader br = new BufferedReader(isr);
                              String line = br.readLine();
      
                              //5.從讀到的數(shù)據(jù)中篩選出要訪問的頁面路徑--->day12/web/index.html
                              //String[] arr = line.split(" ");
                              //String path = arr[1].substring(1);
                              String path = line.split(" ")[1].substring(1);
                              System.out.println("path:" + path);
      
                              //6.創(chuàng)建字節(jié)輸入流,關(guān)聯(lián)瀏覽器要訪問的頁面路徑
                              FileInputStream fis = new FileInputStream(path);
      
                              //7.通過Socket對象獲得字節(jié)輸出流
                              OutputStream os = socket.getOutputStream();
      
                              os.write("HTTP/1.1 200 OK\r\n".getBytes());
                              os.write("Content-Type:text/html\r\n".getBytes());
                              os.write("\r\n".getBytes());
      
                              //8.定義一個byte數(shù)組,用來存儲讀取到的字節(jié)數(shù)據(jù)
                              byte[] bys = new byte[8192];
                              //8.定義一個int變量,用來存儲讀取到的字節(jié)個數(shù)
                              int len;
                              //9.循環(huán)讀
                              while ((len = fis.read(bys)) != -1) {
                                  //10.在循環(huán)中,寫數(shù)據(jù)
                                  os.write(bys,0,len);
                              }
                              //11.釋放資源
                              fis.close();
                              socket.close();
                              //ss.close();
                          } catch (Exception e) {
                              
                          }
                      }
                  }).start();
              }
          }
      }
      
      

      訪問效果:

      1566446578300

      第四章 NIO

      4.1 NIO概述

      在我們學(xué)習(xí)Java的NIO流之前,我們都要了解幾個關(guān)鍵詞

      • 同步與異步(synchronous/asynchronous):同步是一種可靠的有序運行機制,當(dāng)我們進(jìn)行同步操作時,后續(xù)的任務(wù)是等待當(dāng)前調(diào)用返回,才會進(jìn)行下一步;而異步則相反,其他任務(wù)不需要等待當(dāng)前調(diào)用返回,通常依靠事件、回調(diào)等機制來實現(xiàn)任務(wù)間次序關(guān)系
        • 同步: 調(diào)用方法之后,必須要得到一個返回值
        • 異步: 調(diào)用方法之后,沒有返回值,但是會有回調(diào)函數(shù),回調(diào)函數(shù)指的是滿足條件之后會自動執(zhí)行的方法
      • 阻塞與非阻塞:在進(jìn)行阻塞操作時,當(dāng)前線程會處于阻塞狀態(tài),無法從事其他任務(wù),只有當(dāng)條件就緒才能繼續(xù),比如ServerSocket新連接建立完畢,或者數(shù)據(jù)讀取、寫入操作完成;而非阻塞則是不管IO操作是否結(jié)束,直接返回,相應(yīng)操作在后臺繼續(xù)處理
        • 阻塞:如果沒有達(dá)到方法的目的,就會一直停在那里(等待) , 例如: ServerSocket的accept()方法
        • 非阻塞: 不管方法有沒有達(dá)到目的,都直接往下執(zhí)行(不等待)

      在Java1.4之前的I/O系統(tǒng)中,提供的都是面向流的I/O系統(tǒng),系統(tǒng)一次一個字節(jié)地處理數(shù)據(jù),一個輸入流產(chǎn)生一個字節(jié)的數(shù)據(jù),一個輸出流消費一個字節(jié)的數(shù)據(jù),面向流的I/O速度非常慢,而在Java 1.4中推出了NIO,這是一個面向塊的I/O系統(tǒng),系統(tǒng)以塊的方式處理數(shù)據(jù),每一個操作在一步中產(chǎn)生或者消費一個數(shù)據(jù),按塊處理要比按字節(jié)處理數(shù)據(jù)快的多。

      在 Java 7 中,NIO 有了進(jìn)一步的改進(jìn),也就是 NIO 2,引入了異步非阻塞 IO 方式,也有很多人叫它 AIO(Asynchronous IO)。異步 IO 操作基于事件和回調(diào)機制,可以簡單理解為,應(yīng)用操作直接返回,而不會阻塞在那里,當(dāng)后臺處理完成,操作系統(tǒng)會通知相應(yīng)線程進(jìn)行后續(xù)工作。

      NIO之所以是同步,是因為它的accept/read/write方法的內(nèi)核I/O操作都會阻塞當(dāng)前線程

      首先,我們要先了解一下NIO的三個主要組成部分:Buffer(緩沖區(qū))、Channel(通道)、Selector(選擇器)

      IO: 同步阻塞

      NIO:同步非阻塞

      AIO: 異步非阻塞

      第五章 Buffer類(緩沖區(qū))

      5.1 Buffer的概述和分類

      概述:Buffer是一個對象,它是對某種基本類型的數(shù)組進(jìn)行了封裝。

      作用: 在NIO中,就是通過 Buffer 來讀寫數(shù)據(jù)的。所有的數(shù)據(jù)都是用Buffer來處理的,它是NIO讀寫數(shù)據(jù)的中轉(zhuǎn)池, 通常使用字節(jié)數(shù)組。

      Buffer主要有如下幾種:

      • ByteBuffer--->byte[]
      • CharBuffer
      • DoubleBuffer
      • FloatBuffer
      • IntBuffer
      • LongBuffer
      • ShortBuffer

      5.2 創(chuàng)建ByteBuffer

      • ByteBuffer類內(nèi)部封裝了一個byte[]數(shù)組,并可以通過一些方法對這個數(shù)組進(jìn)行操作。

      • 創(chuàng)建ByteBuffer對象

        • 方式一:在堆中創(chuàng)建緩沖區(qū):public static ByteBuffer allocate(int capacity)

        • 方式二: 在系統(tǒng)內(nèi)存創(chuàng)建緩沖區(qū):public static ByteBuffer allocatDirect(int capacity)

          • 在堆中創(chuàng)建緩沖區(qū)稱為:間接緩沖區(qū)

          • 在系統(tǒng)內(nèi)存創(chuàng)建緩沖區(qū)稱為:直接緩沖區(qū)

            • 間接緩沖區(qū)的創(chuàng)建和銷毀效率要高于直接緩沖區(qū)
            • 間接緩沖區(qū)的工作效率要低于直接緩沖區(qū)
        • 方式三:通過數(shù)組創(chuàng)建緩沖區(qū):public static ByteBuffer wrap(byte[] arr)

          • 此種方式創(chuàng)建的緩沖區(qū)為:間接緩沖區(qū)
      • 案例:

        /**
         * Created by PengZhiLin on 2021/8/13 12:09
         */
        public class Test {
            public static void main(String[] args) {
                // 方式一: public static ByteBuffer allocate(int capacity)  堆區(qū)  推薦
                ByteBuffer b1 = ByteBuffer.allocate(10);
        
                // 方式二: public static ByteBuffer allocateDirect(int capacity) 直接內(nèi)存
                ByteBuffer b2 = ByteBuffer.allocateDirect(10);
        
                // 方式三:  public static ByteBuffer wrap(byte[] array)  堆區(qū)  推薦
                byte[] bys = {10,20,30};
                ByteBuffer b3 = ByteBuffer.wrap(bys);
        
                // 獲取b1封裝的byte數(shù)組: public final byte[] array()
                byte[] arr = b1.array();
                System.out.println(Arrays.toString(arr));// [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
                System.out.println(Arrays.toString(b3.array()));// [10, 20, 30]
        
        
            }
        }
        
        

      5.3 添加數(shù)據(jù)-put

      • public ByteBuffer put(byte b):向當(dāng)前可用位置添加數(shù)據(jù)。

      • public ByteBuffer put(byte[] byteArray):向當(dāng)前可用位置添加一個byte[]數(shù)組

      • public ByteBuffer put(byte[] byteArray,int offset,int len):添加一個byte[]數(shù)組的一部分

      • public byte[] array(); 獲取封裝的字節(jié)數(shù)組

        /**
         * Created by PengZhiLin on 2021/8/13 12:14
         */
        public class Test1_put {
            public static void main(String[] args) {
                // - public ByteBuffer put(byte b):向當(dāng)前可用位置添加數(shù)據(jù)。
                //- public ByteBuffer put(byte[] byteArray):向當(dāng)前可用位置添加一個byte[]數(shù)組
                //- public ByteBuffer put(byte[] byteArray,int offset,int len):添加一個byte[]數(shù)組的一部分
                //- public byte[] array(); 獲取封裝的字節(jié)數(shù)組
                // 創(chuàng)建ByteBuffer對象
                ByteBuffer b = ByteBuffer.allocate(10);
        
                // 添加單個byte數(shù)據(jù)
                b.put((byte)10);
                b.put((byte)20);
                b.put((byte)30);
                System.out.println("封裝的數(shù)組:"+ Arrays.toString(b.array()));
        
                // 添加整個數(shù)組中的所有數(shù)據(jù)
                byte[] bys = {10,20,30};
                b.put(bys);
                System.out.println("封裝的數(shù)組:"+ Arrays.toString(b.array()));
        
                // 添加指定范圍的數(shù)組數(shù)據(jù)
                b.put(bys,0,2);
                System.out.println("封裝的數(shù)組:"+ Arrays.toString(b.array()));
        
            }
        }
        
        

      5.4 容量-capacity

      • Buffer的容量(capacity)是指:Buffer所能夠包含的元素的最大數(shù)量。定義了Buffer后,容量是不可變的。

      • public final int capacity();獲取緩沖數(shù)組的容量
        
      • 示例代碼:

        /**
         * Created by PengZhiLin on 2021/8/13 12:19
         */
        public class Test2_capacity {
            public static void main(String[] args) {
                // 創(chuàng)建ByteBuffer對象
                ByteBuffer b = ByteBuffer.allocate(10);
                System.out.println("ByteBuffer對象的容量:" + b.capacity());// 10
        
                // 添加單個byte數(shù)據(jù)
                b.put((byte) 10);
                b.put((byte) 20);
                b.put((byte) 30);
                System.out.println("封裝的數(shù)組:" + Arrays.toString(b.array()));
                System.out.println("ByteBuffer對象的容量:" + b.capacity());// 10
            }
        }
        
        

      5.5 限制-limit

      • 限制limit是指:第一個不能讀或?qū)懭朐氐膇ndex索引。緩沖區(qū)的限制(limit)不能為負(fù),并且不能大于容量。

      • 有兩個相關(guān)方法:

        • public int limit():獲取此緩沖區(qū)的限制。
        • public Buffer limit(int newLimit):設(shè)置此緩沖區(qū)的限制。
      • 示例代碼:

        
        /**
         * Created by PengZhiLin on 2021/8/13 12:22
         */
        public class Test2_limit {
            public static void main(String[] args) {
                // 創(chuàng)建ByteBuffer對象
                ByteBuffer b = ByteBuffer.allocate(10);
                System.out.println("limit:" + b.limit());// 10
        
                // 添加單個byte數(shù)據(jù)
                b.put((byte) 10);
                b.put((byte) 20);
                b.put((byte) 30);
                System.out.println("limit:" + b.limit());// 10
        
                // 修改limit
                b.limit(3);
                System.out.println("limit:" + b.limit());// 3
        
                //b.put((byte) 30);// 報錯,因為3索引這個位置是不能使用了BufferOverflowException異常
            }
        }
        
        

        圖示:

      5.6 位置-position

      • 位置position是指:當(dāng)前可寫入的索引。位置不能小于0,并且不能大于"限制"。

      • 結(jié)論: 操作緩沖數(shù)組,其實就是操作position到limit之間位置上的元素

      • 有兩個相關(guān)方法:

        • public int position():獲取當(dāng)前可寫入位置索引。
        • public Buffer position(int p):更改當(dāng)前可寫入位置索引。
      • 示例代碼:

        /**
         * Created by PengZhiLin on 2021/8/13 12:25
         */
        public class Test4_position {
            public static void main(String[] args) {
                // 創(chuàng)建ByteBuffer對象
                ByteBuffer b = ByteBuffer.allocate(10);
                System.out.println("position:" + b.position());// 0
        
                // 添加單個byte數(shù)據(jù)
                b.put((byte) 10);
                b.put((byte) 20);
                b.put((byte) 30);
                System.out.println("position:" + b.position());// 3
        
                // 修改position的位置
                b.position(5);
                System.out.println("position:" + b.position());// 5
                b.put((byte) 30);// --- 添加在索引為5的位置
                // 封裝的數(shù)組:[10, 20, 30, 0, 0, 30, 0, 0, 0, 0]
                System.out.println("封裝的數(shù)組:"+ Arrays.toString(b.array()));
        
            }
        }
        
        

      5.7 標(biāo)記-mark

      • 標(biāo)記mark是指:當(dāng)調(diào)用緩沖區(qū)的reset()方法時,會將緩沖區(qū)的position位置重置為該標(biāo)記的索引。

      • 相關(guān)方法:

        • public Buffer mark():設(shè)置此緩沖區(qū)的標(biāo)記為當(dāng)前的position位置。
        • public Buffer reset() : 將此緩沖區(qū)的位置重置為以前標(biāo)記的位置。
      • 示例代碼:

        /**
         * Created by PengZhiLin on 2021/8/13 14:33
         */
        public class Test5_mark {
            public static void main(String[] args) {
                // 創(chuàng)建ByteBuffer對象
                ByteBuffer b = ByteBuffer.allocate(10);
        
                // 添加單個byte數(shù)據(jù)
                b.put((byte) 10);
                b.put((byte) 20);
                b.put((byte) 30);
                System.out.println("position:"+b.position());// 3
        
                // 標(biāo)記一下
                b.mark();
        
                b.put((byte) 10);
                b.put((byte) 20);
                b.put((byte) 30);
                System.out.println("position:"+b.position());// 6
        
                // 重置一下
                b.reset();
                System.out.println("position:"+b.position());// 3
        
                // 往索引為3的位置添加元素
                b.put((byte) 100);
                // [10,20,30,100,20,30,0,0,0,0]
                System.out.println("封裝的數(shù)組:"+ Arrays.toString(b.array()));
        
            }
        }
        
        

      5.8 clear和flip

      - public Buffer clear():還原緩沖區(qū)的狀態(tài)。
        - 將position設(shè)置為:0
        - 將限制limit設(shè)置為容量capacity;
        - 丟棄標(biāo)記mark。
      - public Buffer flip():縮小limit的范圍。
        - 將當(dāng)前position位置設(shè)置為0;
        - 將limit設(shè)置為當(dāng)前position位置;
        - 丟棄標(biāo)記。
      
      • clear方法演示

      • flip方法演示

        /**
         * Created by PengZhiLin on 2021/8/13 14:38
         */
        public class Test6_clear和flip {
            public static void main(String[] args) {
                // 創(chuàng)建ByteBuffer對象
                ByteBuffer b = ByteBuffer.allocate(10);
                // position:0,limit:10,capacity:10
                System.out.println("position:" + b.position() + ",limit:" + b.limit() + ",capacity:" + b.capacity());
        
                // 添加單個byte數(shù)據(jù)
                b.put((byte) 10);
                b.put((byte) 20);
                b.put((byte) 30);
                //  position:3,limit:10,capacity:10
                System.out.println("position:" + b.position() + ",limit:" + b.limit() + ",capacity:" + b.capacity());
        
                // 調(diào)用flip一下
                b.flip();
                //  position:0,limit:3,capacity:10
                System.out.println("position:" + b.position() + ",limit:" + b.limit() + ",capacity:" + b.capacity());
        
                // clear一下
                b.clear();
                //  position:0,limit:10,capacity:10
                System.out.println("position:" + b.position() + ",limit:" + b.limit() + ",capacity:" + b.capacity());
        
            }
        }
        
        

      第六章 Channel(通道)

      6.1 Channel概述

      Channel 的概述

      Channel(通道):Channel是一個對象,可以通過它讀取和寫入數(shù)據(jù), 可以把它看做是IO中的流,不同的是:Channel是雙向的, Channel對象既可以調(diào)用讀取的方法, 也可以調(diào)用寫出的方法 。

      輸入流: 讀

      輸出流: 寫

      Channel: 讀,寫

      • public int read(ByteBuffer b) 讀數(shù)據(jù),把讀到的數(shù)據(jù)放在b數(shù)組中,返回讀取到的字節(jié)個數(shù),如果讀到文件的末尾就返回-1
      • public int write(ByteBuffer b) 寫數(shù)據(jù),把數(shù)據(jù)寫到目的地文件中,寫的是position到limit之間的數(shù)據(jù)

      Channel 的分類

      在Java NIO中的Channel主要有如下幾種類型:

      • FileChannel:從文件讀寫數(shù)據(jù)的 輸入流和輸出流
      • DatagramChannel:讀寫UDP網(wǎng)絡(luò)協(xié)議數(shù)據(jù) Datagram
      • SocketChannel:讀寫TCP網(wǎng)絡(luò)協(xié)議數(shù)據(jù) Socket
      • ServerSocketChannel:可以監(jiān)聽TCP連接 ServerSocket

      6.2 FileChannel類的基本使用

      獲取FileChannel類的對象

      • java.nio.channels.FileChannel (抽象類):用于讀、寫文件的通道。

      • FileChannel是抽象類,我們可以通過FileInputStream和FileOutputStream的getChannel()方法方便的獲取一個它的子類對象。

        FileInputStream fis=new FileInputStream("數(shù)據(jù)源文件路徑");
        FileOutputStream fos=new FileOutputStream("目的地文件路徑");
        //獲得傳輸通道channel
        FileChannel inChannel=fis.getChannel();
        FileChannel outChannel=fos.getChannel();
        

      使用FileChannel類完成文件的復(fù)制

      • 我們將通過CopyFile這個示例讓大家體會NIO的操作過程。CopyFile執(zhí)行三個基本的操作:創(chuàng)建一個Buffer,然后從源文件讀取數(shù)據(jù)到緩沖區(qū),然后再將緩沖區(qū)寫入目標(biāo)文件。

      • public int read(ByteBuffer b) 讀數(shù)據(jù),把讀到的數(shù)據(jù)放在b數(shù)組中,返回讀取到的字節(jié)個數(shù),如果讀到文件的末尾就返回-1

      • public int write(ByteBuffer b) 寫數(shù)據(jù),把數(shù)據(jù)寫到目的地文件中

        /**
         * Created by PengZhiLin on 2021/8/13 14:55
         */
        public class Test {
            public static void main(String[] args) throws Exception{
                // 1.獲得FileChannel對象
                FileInputStream fis = new FileInputStream("day12\\aaa\\mm.jpg");
                FileOutputStream fos = new FileOutputStream("day12\\ccc\\mm1.jpg");
                FileChannel c1 = fis.getChannel();
                FileChannel c2 = fos.getChannel();
        
                // 2.創(chuàng)建ByteBuffer數(shù)組對象
                ByteBuffer bys = ByteBuffer.allocate(8192);
        
                // 3.循環(huán)讀數(shù)據(jù)
                while (c1.read(bys) != -1) {
                    // flip一下---把position改為0,limit改為position--->目的是為了寫的時候?qū)懙氖莿倓傋x到的字節(jié)數(shù)據(jù)
                    bys.flip();
                    // 4.在循環(huán)中,寫數(shù)據(jù)
                    c2.write(bys);
                    // clear一下--->把position改為0,limit改為capacity--->目的是為了還原數(shù)組為最初的狀態(tài)供下一次循環(huán)使用
                    bys.clear();
                }
        
                // 5.釋放資源
                c2.close();
                c1.close();
                fos.close();
                fis.close();
        
            }
        }
        
        

      6.3 FileChannel結(jié)合MappedByteBuffer實現(xiàn)高效讀寫

      MappedByteBuffer類的概述

      • 上例直接使用FileChannel結(jié)合ByteBuffer實現(xiàn)的管道讀寫,但并不能提高文件的讀寫效率。

      • ByteBuffer有個抽象子類:MappedByteBuffer,它可以將文件直接映射至內(nèi)存把硬盤中的讀寫變成內(nèi)存中的讀寫, 所以可以提高大文件的讀寫效率。

      • 可以調(diào)用FileChannel的map()方法獲取一個MappedByteBuffer,map()方法的原型:

        ? MappedByteBuffer map(MapMode mode, long position, long size);

        ? 說明:將節(jié)點中從position開始的size個字節(jié)映射到返回的MappedByteBuffer中。

      • 代碼說明:

        • map()方法的第一個參數(shù)mode:映射的三種模式,在這三種模式下得到的將是三種不同的MappedByteBuffer:三種模式都是Channel的內(nèi)部類MapMode中定義的靜態(tài)常量,這里以FileChannel舉例:
          1). FileChannel.MapMode.READ_ONLY:得到的鏡像只能讀不能寫(只能使用get之類的讀取Buffer中的內(nèi)容);

          2). FileChannel.MapMode.READ_WRITE:得到的鏡像可讀可寫(既然可寫了必然可讀),對其寫會直接更改到存儲節(jié)點;

          3). FileChannel.MapMode.PRIVATE:得到一個私有的鏡像,其實就是一個(position, size)區(qū)域的副本罷了,也是可讀可寫,只不過寫不會影響到存儲節(jié)點,就是一個普通的ByteBuffer了!!

        • 為什么使用RandomAccessFile?

          1). 使用InputStream獲得的Channel可以映射,使用map時只能指定為READ_ONLY模式,不能指定為READ_WRITE和PRIVATE,否則會拋出運行時異常!

          2). 使用OutputStream得到的Channel不可以映射!并且OutputStream的Channel也只能write不能read!

          3). 只有RandomAccessFile獲取的Channel才能開啟任意的這三種模式!

      復(fù)制2GB以下的文件

      • 復(fù)制d:\b.rar文件,此文件大概600多兆,復(fù)制完畢用時不到2秒。此例不能復(fù)制大于2G的文件,因為map的第三個參數(shù)被限制在Integer.MAX_VALUE(字節(jié)) = 2G。
      
      /**
       * Created by PengZhiLin on 2021/8/13 15:22
       */
      public class Test {
          public static void main(String[] args) throws Exception {
              // 1.創(chuàng)建RandomAccessFile對象,r表示讀,rw表示讀寫
              RandomAccessFile r1 = new RandomAccessFile("day12\\aaa\\mm.jpg", "r");
              RandomAccessFile r2 = new RandomAccessFile("day12\\aaa\\mm2.jpg", "rw");
      
              // 2.根據(jù)RandomAccessFile對象獲取FileChanel對象
              FileChannel c1 = r1.getChannel();
              FileChannel c2 = r2.getChannel();
      
              // 3.獲取數(shù)據(jù)源文件的字節(jié)大小
              long size = c1.size();
      
              // 4.映射操作
              MappedByteBuffer m1 = c1.map(FileChannel.MapMode.READ_ONLY, 0, size);
              MappedByteBuffer m2 = c2.map(FileChannel.MapMode.READ_WRITE, 0, size);
      
              // 5.復(fù)制字節(jié)數(shù)據(jù)
              for (long i = 0; i < size; i++) {
                  byte b = m1.get();
                  m2.put(b);
              }
      
              // 6.釋放資源
              c2.close();
              c1.close();
              r2.close();
              r1.close();
      
      
          }
      }
      
      

      復(fù)制2GB以上的文件

      • 下例使用循環(huán),將文件分塊,可以高效的復(fù)制大于2G的文件
      /**
       * Created by PengZhiLin on 2021/8/13 15:22
       */
      public class Test {
          public static void main(String[] args) throws Exception {
              // 1.創(chuàng)建RandomAccessFile對象,r表示讀,rw表示讀寫
              RandomAccessFile r1 = new RandomAccessFile("H:\\a.zip", "r");
              RandomAccessFile r2 = new RandomAccessFile("day12\\aaa\\a.zip", "rw");
      
              // 2.根據(jù)RandomAccessFile對象獲取FileChanel對象
              FileChannel c1 = r1.getChannel();
              FileChannel c2 = r2.getChannel();
      
              // 3.獲取數(shù)據(jù)源文件的字節(jié)大小
              long size = c1.size();
      
              // 定義一個變量,表示每次平均映射的字節(jié)大小
              long everySize = 500 * 1024 * 1024;
      
              // 映射的總次數(shù):
              long count = size % everySize == 0 ? size / everySize : size / everySize + 1;
      
              // 循環(huán)映射
              for (long i = 0; i < count; i++) {
                  //起始位置:
                  long start = i * everySize;
      
                  //每次真正映射的字節(jié)大小:
                  long trueSize = size - start > everySize ? everySize : size - start;
      
                  // 4.映射操作
                  MappedByteBuffer m1 = c1.map(FileChannel.MapMode.READ_ONLY, start, trueSize);
                  MappedByteBuffer m2 = c2.map(FileChannel.MapMode.READ_WRITE, start, trueSize);
      
                  // 5.復(fù)制字節(jié)數(shù)據(jù)
                  for (long j = 0; j < trueSize; j++) {
                      byte b = m1.get();
                      m2.put(b);
                  }
              }
      
              // 6.釋放資源
              c2.close();
              c1.close();
              r2.close();
              r1.close();
      
      
          }
      }
      
      

      6.4 ServerSocketChannel和SocketChannel創(chuàng)建連接

      SocketChannel創(chuàng)建連接

      • 客戶端:SocketChannel類用于連接的客戶端,它相當(dāng)于:Socket。

        1). 先調(diào)用SocketChannel的open()方法打開通道:

        SocketChannel socket = SocketChannel.open()
        

        2). 調(diào)用SocketChannel的實例方法connect(SocketAddress add)連接服務(wù)器:

         socket.connect(new InetSocketAddress("127.0.0.1", 8888));
        

        示例:客戶端連接服務(wù)器:

        /**
         * Created by PengZhiLin on 2021/8/13 16:12
         */
        public class Client {
            public static void main(String[] args) throws Exception{
                // 1.打開SocketChannel通道
                SocketChannel sc = SocketChannel.open();
        
                // 2.連接服務(wù)器
                sc.connect(new InetSocketAddress("127.0.0.1",8888));
                System.out.println("連接上了...");
        
            }
        }
        
        

      ServerSocketChanne創(chuàng)建連接

      • 服務(wù)器端:ServerSocketChannel類用于連接的服務(wù)器端,它相當(dāng)于:ServerSocket。

      • 調(diào)用ServerSocketChannel的靜態(tài)方法open()就可以獲得ServerSocketChannel對象, 但并沒有指定端口號, 必須通過其套接字的bind方法將其綁定到特定地址,才能接受連接。

        ServerSocketChannel serverChannel = ServerSocketChannel.open()
        
      • 調(diào)用ServerSocketChannel的實例方法bind(SocketAddress add):綁定本機監(jiān)聽端口,準(zhǔn)備接受連接。

        ? 注:java.net.SocketAddress(抽象類):代表一個Socket地址。

        ? 我們可以使用它的子類:java.net.InetSocketAddress(類)

        ? 構(gòu)造方法:InetSocketAddress(int port):指定本機監(jiān)聽端口。

        serverChannel.bind(new InetSocketAddress(8888));
        
      • 調(diào)用ServerSocketChannel的實例方法accept():等待連接。

        SocketChannel accept = serverChannel.accept();
        System.out.println("后續(xù)代碼...");
        

        示例:服務(wù)器端等待連接(默認(rèn)-阻塞模式)

        /**
         * Created by PengZhiLin on 2021/8/13 16:16
         */
        public class Server {
            public static void main(String[] args) throws Exception{
                // 1.打開服務(wù)器通道
                ServerSocketChannel ssc = ServerSocketChannel.open();
        
                // 2.綁定端口號
                ssc.bind(new InetSocketAddress(8888));
        
                // 3.等待連接
                System.out.println("等待連接...");
                SocketChannel socketChannel = ssc.accept();// 阻塞
                System.out.println("連接成功...");
            }
        }
        
        

        運行后結(jié)果:

        【服務(wù)器】等待連接...
        
      • 我們可以通過ServerSocketChannel的configureBlocking(boolean b)方法設(shè)置accept()是否阻塞

        /**
         * Created by PengZhiLin on 2021/8/13 16:16
         */
        public class Server {
            public static void main(String[] args) throws Exception{
                // 1.打開服務(wù)器通道
                ServerSocketChannel ssc = ServerSocketChannel.open();
        
                // 2.綁定端口號
                ssc.bind(new InetSocketAddress(8888));
        
                // 設(shè)置非阻塞
                ssc.configureBlocking(false);
        
                // 3.等待連接
                System.out.println("等待連接...");
                SocketChannel socketChannel = ssc.accept();// 非阻塞
                System.out.println("連接成功...");
            }
        }
        
        

      6.5 NIO網(wǎng)絡(luò)編程收發(fā)信息

      書寫服務(wù)器代碼

      
      /**
       * Created by PengZhiLin on 2021/8/13 16:20
       */
      public class Server {
          public static void main(String[] args) throws Exception{
              // 1.打開服務(wù)器通道
              ServerSocketChannel ssc = ServerSocketChannel.open();
      
              // 2.綁定端口號
              ssc.bind(new InetSocketAddress(8888));
      
              // 3.建立連接
              SocketChannel sc = ssc.accept();
      
              // 4.接收數(shù)據(jù)
              ByteBuffer b = ByteBuffer.allocate(1024);
              int len = sc.read(b);
              System.out.println("服務(wù)器接收到的數(shù)據(jù)是:"+new String(b.array(),0,len));
      
              // 5.釋放資源
              sc.close();
              ssc.close();
      
          }
      }
      
      

      書寫客戶端代碼

      
      /**
       * Created by PengZhiLin on 2021/8/13 16:20
       */
      public class Client {
          public static void main(String[] args) throws Exception{
              // 1.打開客戶端通道
              SocketChannel sc = SocketChannel.open();
      
              // 2.連接服務(wù)器
              sc.connect(new InetSocketAddress("127.0.0.1",8888));
      
              // 3.寫數(shù)據(jù)給服務(wù)器
              byte[] bytes = "服務(wù)器你好,今晚約嗎?".getBytes();
              ByteBuffer b = ByteBuffer.wrap(bytes);
              sc.write(b);
      
              // 4.釋放資源
              sc.close();
      
          }
      }
      
      

      第七章 Selector(選擇器)

      7.1 多路復(fù)用的概念

      選擇器Selector是NIO中的重要技術(shù)之一。它與SelectableChannel聯(lián)合使用實現(xiàn)了非阻塞的多路復(fù)用。使用它可以節(jié)省CPU資源,提高程序的運行效率。

      "多路"是指:服務(wù)器端同時監(jiān)聽多個“端口”的情況。每個端口都要監(jiān)聽多個客戶端的連接。

      • 服務(wù)器端的非多路復(fù)用效果

        如果不使用“多路復(fù)用”,服務(wù)器端需要開很多線程處理每個端口的請求。如果在高并發(fā)環(huán)境下,造成系統(tǒng)性能下降。

      • 服務(wù)器端的多路復(fù)用效果

        使用了多路復(fù)用,只需要一個線程就可以處理多個通道,降低內(nèi)存占用率,減少CPU切換時間,在高并發(fā)、高頻段業(yè)務(wù)環(huán)境下有非常重要的優(yōu)勢

      7.2 選擇器Selector的獲取和注冊

      Selector選擇器的概述和作用

      概述: Selector被稱為:選擇器,也被稱為:多路復(fù)用器,可以把多個Channel注冊到一個Selector選擇器上, 那么就可以實現(xiàn)利用一個線程來處理這多個Channel上發(fā)生的事件,并且能夠根據(jù)事件情況決定Channel讀寫。這樣,通過一個線程管理多個Channel,就可以處理大量網(wǎng)絡(luò)連接了, 減少系統(tǒng)負(fù)擔(dān), 提高效率。因為線程之間的切換對操作系統(tǒng)來說代價是很高的,并且每個線程也會占用一定的系統(tǒng)資源。所以,對系統(tǒng)來說使用的線程越少越好。

      作用: 一個Selector可以監(jiān)聽多個Channel發(fā)生的事件, 減少系統(tǒng)負(fù)擔(dān) , 提高程序執(zhí)行效率 .

      Selector選擇器的獲取

      Selector selector = Selector.open();
      

      注冊Channel到Selector

      通過調(diào)用 channel.register(Selector sel, int ops)方法來實現(xiàn)注冊:

      channel.configureBlocking(false);// 一定要設(shè)置非阻塞
      SelectionKey key =channel.register(selector,SelectionKey.OP_ACCEPT);
      

      register()方法的第二個參數(shù):是一個int值,意思是在通過Selector監(jiān)聽Channel時對什么事件感興趣。可以監(jiān)聽四種不同類型的事件,而且可以使用SelectionKey的四個常量表示:

      1. 連接就緒--常量:SelectionKey.OP_CONNECT

      2. 接收就緒--常量:SelectionKey.OP_ACCEPT (ServerSocketChannel在注冊時只能使用此項)

      3. 讀就緒--常量:SelectionKey.OP_READ

      4. 寫就緒--常量:SelectionKey.OP_WRITE

        注意:對于ServerSocketChannel在注冊時,只能使用OP_ACCEPT,否則拋出異常。

      • 案例演示; 監(jiān)聽一個通道

        /**
         * Created by PengZhiLin on 2021/8/13 16:54
         */
        public class Test1 {
            public static void main(String[] args) throws Exception{
                // 1.打開服務(wù)器通道
                ServerSocketChannel ssc = ServerSocketChannel.open();
        
                // 2.綁定端口號
                ssc.bind(new InetSocketAddress(6666));
        
                // 3.設(shè)置非阻塞
                ssc.configureBlocking(false);
        
                // 4.獲取選擇器
                Selector selector = Selector.open();
        
                // 5.把服務(wù)器通道注冊到選擇器上
                System.out.println(1);
                ssc.register(selector, SelectionKey.OP_ACCEPT);
                System.out.println(2);
        
            }
        }
        
        
      • 示例:服務(wù)器創(chuàng)建3個通道,同時監(jiān)聽3個端口,并將3個通道注冊到一個選擇器中

        /**
         * Created by PengZhiLin on 2021/8/13 16:54
        

      */
      public class Test2 {
      public static void main(String[] args) throws Exception{
      // 1.打開服務(wù)器通道
      ServerSocketChannel ssc1 = ServerSocketChannel.open();
      ServerSocketChannel ssc2 = ServerSocketChannel.open();
      ServerSocketChannel ssc3 = ServerSocketChannel.open();

            // 2.綁定端口號
            ssc1.bind(new InetSocketAddress(7777));
            ssc2.bind(new InetSocketAddress(8888));
            ssc3.bind(new InetSocketAddress(9999));
      
            // 3.設(shè)置非阻塞
            ssc1.configureBlocking(false);
            ssc2.configureBlocking(false);
            ssc3.configureBlocking(false);
      
            // 4.獲取選擇器
            Selector selector = Selector.open();
      
            // 5.把服務(wù)器通道注冊到選擇器上
            ssc1.register(selector, SelectionKey.OP_ACCEPT);
            ssc2.register(selector, SelectionKey.OP_ACCEPT);
            ssc3.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println(1);
      
        }
      

      }

      
      
      
      接下來,就可以通過選擇器selector操作三個通道了。
      
      ## 7.3 Selector的常用方法
      
      #### Selector的select()方法:---->面試
      
      - 作用: 選擇器監(jiān)聽客戶端的請求方法
      
      - 阻塞問題:
      
      - 在連接到第一個客戶端之前,會一直阻塞
      - 當(dāng)連接到客戶端后,如果客戶端沒有被處理,該方法會計入不阻塞狀態(tài)
      - 當(dāng)連接到客戶端后,如果客戶端有被處理,該方法又會進(jìn)入阻塞狀態(tài)
      
      ```java
      /**
       * Created by PengZhiLin on 2021/8/13 17:00
       */
      public class Test1_select方法 {
          public static void main(String[] args) throws Exception{
              // 1.打開服務(wù)器通道
              ServerSocketChannel ssc1 = ServerSocketChannel.open();
              ServerSocketChannel ssc2 = ServerSocketChannel.open();
              ServerSocketChannel ssc3 = ServerSocketChannel.open();
      
              // 2.綁定端口號
              ssc1.bind(new InetSocketAddress(7777));
              ssc2.bind(new InetSocketAddress(8888));
              ssc3.bind(new InetSocketAddress(9999));
      
              // 3.設(shè)置非阻塞
              ssc1.configureBlocking(false);
              ssc2.configureBlocking(false);
              ssc3.configureBlocking(false);
      
              // 4.獲取選擇器
              Selector selector = Selector.open();
      
              // 5.把服務(wù)器通道注冊到選擇器上
              ssc1.register(selector, SelectionKey.OP_ACCEPT);
              ssc2.register(selector, SelectionKey.OP_ACCEPT);
              ssc3.register(selector, SelectionKey.OP_ACCEPT);
      
              while (true){
                  System.out.println(1);
                  // 監(jiān)聽客戶端的請求
                  selector.select();
                  System.out.println(2);
              }
      
          }
      
      }
      
      

      Selector的keys()方法

      • 獲取已注冊的所有通道集合

        /**
         * Created by PengZhiLin on 2021/8/13 17:00
         */
        public class Test1_select方法 {
            public static void main(String[] args) throws Exception {
                // 1.打開服務(wù)器通道
                ServerSocketChannel ssc1 = ServerSocketChannel.open();
                ServerSocketChannel ssc2 = ServerSocketChannel.open();
                ServerSocketChannel ssc3 = ServerSocketChannel.open();
        
                // 2.綁定端口號
                ssc1.bind(new InetSocketAddress(7777));
                ssc2.bind(new InetSocketAddress(8888));
                ssc3.bind(new InetSocketAddress(9999));
        
                // 3.設(shè)置非阻塞
                ssc1.configureBlocking(false);
                ssc2.configureBlocking(false);
                ssc3.configureBlocking(false);
        
                // 4.獲取選擇器
                Selector selector = Selector.open();
        
                // 5.把服務(wù)器通道注冊到選擇器上
                ssc1.register(selector, SelectionKey.OP_ACCEPT);
                ssc2.register(selector, SelectionKey.OP_ACCEPT);
                ssc3.register(selector, SelectionKey.OP_ACCEPT);
                System.out.println("已注冊的所有通道集合:" + selector.keys().size());
        
                while (true) {
                    System.out.println(1);
                    // 監(jiān)聽客戶端的請求
                    selector.select();
                    System.out.println(2);
                    System.out.println("已注冊的所有通道集合:" + selector.keys().size());
                }
        
            }
        
        }
        
        

      Selector的selectedKeys()方法

      • 獲取已連接的所有通道集合
      /**
      * Created by PengZhiLin on 2021/8/13 17:00
      */
      public class Test2_selectedKeys方法 {
         public static void main(String[] args) throws Exception {
             // 1.打開服務(wù)器通道
             ServerSocketChannel ssc1 = ServerSocketChannel.open();
             ServerSocketChannel ssc2 = ServerSocketChannel.open();
             ServerSocketChannel ssc3 = ServerSocketChannel.open();
      
             // 2.綁定端口號
             ssc1.bind(new InetSocketAddress(7777));
             ssc2.bind(new InetSocketAddress(8888));
             ssc3.bind(new InetSocketAddress(9999));
      
             // 3.設(shè)置非阻塞
             ssc1.configureBlocking(false);
             ssc2.configureBlocking(false);
             ssc3.configureBlocking(false);
      
             // 4.獲取選擇器
             Selector selector = Selector.open();
      
             // 5.把服務(wù)器通道注冊到選擇器上
             ssc1.register(selector, SelectionKey.OP_ACCEPT);
             ssc2.register(selector, SelectionKey.OP_ACCEPT);
             ssc3.register(selector, SelectionKey.OP_ACCEPT);
             System.out.println("已注冊的所有通道集合:" + selector.keys().size());
      
             while (true) {
                 System.out.println(1);
                 // 監(jiān)聽客戶端的請求
                 selector.select();
                 System.out.println(2);
                 // 處理客戶端的請求
                 // 獲取已連接的所有通道集合
                 Set<SelectionKey> keys = selector.selectedKeys();
      
                 // 循環(huán)遍歷所有通道集合
                 for (SelectionKey key : keys) {
                     // SelectionKey-->其實就是封裝了ServerSocketChannel
                     // 通過SelectionKey獲得ServerSocketChannel
                     // SelectableChannel是ServerSocketChannel的父類
                     ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                     // 處理客戶端的請求,建立連接
                     SocketChannel sc = ssc.accept();
                     // 接收客戶端發(fā)過來的數(shù)據(jù)
                     ByteBuffer b = ByteBuffer.allocate(1024);
                     int len = sc.read(b);
                     System.out.println("服務(wù)器接收到的數(shù)據(jù)是:" + new String(b.array(), 0, len));
                     // 處理完了,釋放資源
                     sc.close();
                 }
      
      
                 System.out.println("已注冊的所有通道集合:" + selector.keys().size());
             }
      
         }
      
      }
      
      

      7.4 Selector多路復(fù)用

      需求

      • 使用Selector進(jìn)行多路復(fù)用,監(jiān)聽3個服務(wù)器端口

      分析

      • 創(chuàng)建3個服務(wù)器通道,設(shè)置成非阻塞
      • 獲取Selector選擇器
      • 把Selector注冊到三個服務(wù)器通道上
      • 循環(huán)去等待客戶端連接
      • 遍歷所有被連接的服務(wù)器通道集合
      • 處理客戶端請求

      實現(xiàn)

      • 案例:

        /**
         * Created by PengZhiLin on 2021/8/13 17:00
         */
        public class Test {
            public static void main(String[] args) throws Exception {
                // 1.打開服務(wù)器通道
                ServerSocketChannel ssc1 = ServerSocketChannel.open();
                ServerSocketChannel ssc2 = ServerSocketChannel.open();
                ServerSocketChannel ssc3 = ServerSocketChannel.open();
        
                // 2.綁定端口號
                ssc1.bind(new InetSocketAddress(7777));
                ssc2.bind(new InetSocketAddress(8888));
                ssc3.bind(new InetSocketAddress(9999));
        
                // 3.設(shè)置非阻塞
                ssc1.configureBlocking(false);
                ssc2.configureBlocking(false);
                ssc3.configureBlocking(false);
        
                // 4.獲取選擇器
                Selector selector = Selector.open();
        
                // 5.把服務(wù)器通道注冊到選擇器上
                ssc1.register(selector, SelectionKey.OP_ACCEPT);
                ssc2.register(selector, SelectionKey.OP_ACCEPT);
                ssc3.register(selector, SelectionKey.OP_ACCEPT);
                System.out.println("已注冊的所有通道集合:" + selector.keys().size());
        
                while (true) {
                    System.out.println(1);
                    // 監(jiān)聽客戶端的請求
                    selector.select();
                    System.out.println(2);
                    // 處理客戶端的請求
                    // 獲取已連接的所有通道集合
                    Set<SelectionKey> keys = selector.selectedKeys();
        
                    // 循環(huán)遍歷所有通道集合
                    Iterator<SelectionKey> it = keys.iterator();
                    while (it.hasNext()) {
                        SelectionKey key = it.next();
                        // SelectionKey-->其實就是封裝了ServerSocketChannel
                        // 通過SelectionKey獲得ServerSocketChannel
                        // SelectableChannel是ServerSocketChannel的父類
                        ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                        // 處理客戶端的請求,建立連接
                        SocketChannel sc = ssc.accept();// null
                        // 接收客戶端發(fā)過來的數(shù)據(jù)
                        ByteBuffer b = ByteBuffer.allocate(1024);
                        int len = sc.read(b);
                        System.out.println("服務(wù)器接收到的數(shù)據(jù)是:" + new String(b.array(), 0, len));
                        // 處理完了,釋放資源
                        sc.close();
        
                        // 移除keys集合中已處理的服務(wù)器通道--->迭代器
                        it.remove();
                    }
        
        
                    System.out.println("已注冊的所有通道集合:" + selector.keys().size());
                }
        
            }
        
        }
        
        
      • 問題: Selector把所有被連接的服務(wù)器對象放在了一個Set集合中,但是使用完后并沒有刪除,導(dǎo)致在遍歷集合時,遍歷到已經(jīng)沒用的對象,出現(xiàn)了異常

      • 解決辦法: 使用完了,應(yīng)該從集合中刪除,由于遍歷的同時集合不能刪除,所以使用迭代器進(jìn)行遍歷

      第八章 NIO2-AIO(異步、非阻塞)

      8.1 AIO概述

      同步,異步,阻塞,非阻塞概念回顧

      - 同步:調(diào)用方法之后,必須要得到一個返回值。
      
      - 異步:調(diào)用方法之后,沒有返回值,但是會有回調(diào)函數(shù)。回調(diào)函數(shù)指的是滿足條件之后會自動執(zhí)行的方法
      
      - 阻塞:如果沒有達(dá)到方法的目的,就一直停在這里【等待】。  
      
      - 非阻塞:不管有沒有達(dá)到目的,都直接【往下執(zhí)行】。  
      IO: 同步阻塞
      NIO: 同步阻塞,同步非阻塞
      NIO2:異步非阻塞
      

      image-20200512230232797

      AIO相關(guān)類和方法介紹

      AIO是異步IO的縮寫,雖然NIO在網(wǎng)絡(luò)操作中,提供了非阻塞的方法,但是NIO的IO行為還是同步的。對于NIO來說,我們的業(yè)務(wù)線程是在IO操作準(zhǔn)備好時,得到通知,接著就由這個線程自行進(jìn)行IO操作,IO操作本身是同步的。

      但是對AIO來說,則更加進(jìn)了一步,它不是在IO準(zhǔn)備好時再通知線程,而是在IO操作已經(jīng)完成后,再給線程發(fā)出通知。因此AIO是不會阻塞的,此時我們的業(yè)務(wù)邏輯將變成一個回調(diào)函數(shù),等待IO操作完成后,由系統(tǒng)自動觸發(fā)。

      與NIO不同,當(dāng)進(jìn)行讀寫操作時,只須直接調(diào)用API的read或write方法即可。這兩種方法均為異步的,對于讀操作而言,當(dāng)有流可讀取時,操作系統(tǒng)會將可讀的流傳入read方法的緩沖區(qū),并通知應(yīng)用程序;對于寫操作而言,當(dāng)操作系統(tǒng)將write方法傳遞的流寫入完畢時,操作系統(tǒng)主動通知應(yīng)用程序。 即可以理解為,read/write方法都是異步的,完成后會主動調(diào)用回調(diào)函數(shù)。 在JDK1.7中,這部分內(nèi)容被稱作NIO.2---->AIO,主要在Java.nio.channels包下增加了下面四個異步通道:

      • AsynchronousSocketChannel
      • AsynchronousServerSocketChannel
      • AsynchronousFileChannel
      • AsynchronousDatagramChannel

      在AIO socket編程中,服務(wù)端通道是AsynchronousServerSocketChannel,這個類提供了一個open()靜態(tài)工廠,一個bind()方法用于綁定服務(wù)端IP地址(還有端口號),另外還提供了accept()用于接收用戶連接請求。在客戶端使用的通道是AsynchronousSocketChannel,這個通道處理提供open靜態(tài)工廠方法外,還提供了read和write方法。

      在AIO編程中,發(fā)出一個事件(accept read write等)之后要指定事件處理類(回調(diào)函數(shù)),AIO中的事件處理類是CompletionHandler<V,A>,這個接口定義了如下兩個方法,分別在異步操作成功和失敗時被回調(diào)。

      void completed(V result, A attachment);

      void failed(Throwable exc, A attachment);

      8.2 AIO 異步非阻塞連接

      需求

      • AIO異步非阻塞的連接方法

      分析

      • 獲取AsynchronousServerSocketChannel對象,綁定端口
      • 異步接收客戶端請求
        • void accept(A attachment, CompletionHandler<AsynchronousSocketChannel,? super A> handler)
        • 第一個參數(shù): 附件,沒啥用,傳入null即可
        • 第二個參數(shù): CompletionHandler接口 ,AIO中的事件處理接口
          • void completed(V result, A attachment);異步連接成功,就會自動調(diào)用這個方法
          • void failed(Throwable exc, A attachment);異步連接失敗,就會自動調(diào)用這個方法

      實現(xiàn)

      • 服務(wù)器端:
      /**
       * Created by PengZhiLin on 2021/8/13 17:43
       */
      public class Server {
          public static void main(String[] args) throws Exception{
              // 1.打開異步服務(wù)器通道
              AsynchronousServerSocketChannel assc = AsynchronousServerSocketChannel.open();
      
              // 2.綁定端口號
              assc.bind(new InetSocketAddress(7777));
      
              // 3.接收請求,建立連接
              System.out.println(1);
              assc.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
                 @Override
                 public void completed(AsynchronousSocketChannel result, Object attachment) {
                     // 連接成功,就會來到這里
                     System.out.println(2);
                 }
      
                 @Override
                 public void failed(Throwable exc, Object attachment) {
                      // 連接失敗,就會來到這里
                     System.out.println(3);
                 }
             });
              System.out.println(4);
      
              while (true){
      
              }
      
          }
      }
      
      

      小結(jié)

      8.3 AIO 異步非阻塞連接和異步讀

      需求

      • 實現(xiàn)異步連接,異步讀

      分析

      • 獲取AsynchronousServerSocketChannel對象,綁定端口
      • 異步接收客戶端請求
      • 在CompletionHandler的completed方法中異步讀數(shù)據(jù)

      實現(xiàn)

      • 服務(wù)器端代碼:
      
      /**
       * Created by PengZhiLin on 2021/8/13 17:51
       */
      public class Server {
          public static void main(String[] args)throws Exception{
              // 1.打開異步服務(wù)器通道
              AsynchronousServerSocketChannel assc = AsynchronousServerSocketChannel.open();
      
              // 2.綁定端口號
              assc.bind(new InetSocketAddress(7777));
      
              // 3.接收請求,建立連接
              System.out.println(1);
              assc.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
                  @Override
                  public void completed(AsynchronousSocketChannel asc, Object attachment) {
                      // 連接成功
                      System.out.println(2);
      
                      // 創(chuàng)建ByteBuffer對象
                      ByteBuffer b = ByteBuffer.allocate(1024);
                      // 異步讀
                      asc.read(b, null, new CompletionHandler<Integer, Object>() {
                          @Override
                          public void completed(Integer len, Object attachment) {
                              // 讀成功
                              System.out.println(5);
                              System.out.println("服務(wù)器接收到的信息:"+new String(b.array(),0,len));
                          }
      
                          @Override
                          public void failed(Throwable exc, Object attachment) {
                              // 讀失敗
                              System.out.println(6);
                          }
                      });
      
                      System.out.println(7);
                  }
      
                  @Override
                  public void failed(Throwable exc, Object attachment) {
                      // 連接失敗
                      System.out.println(3);
                  }
              });
      
              System.out.println(4);
      
              // 為了讓程序不結(jié)束
              while (true){
      
              }
          }
      }
      
      

      總結(jié)

      練習(xí):
      	1.TCP模擬聊天程序(客戶端和服務(wù)器互發(fā)字符串?dāng)?shù)據(jù))---->必須
          2.TCP模擬文件上傳---->必須
          
          3.使用FileChannel拷貝文件---->理解
          4.使用MappedByteBuffer拷貝2GB以上的文件---->理解
          5.Selector多路復(fù)用---->理解
          6.服務(wù)器異步連接異步讀,實現(xiàn)服務(wù)器接收客戶端的信息---->理解
              
      - 能夠辨別UDP和TCP協(xié)議特點
         - TCP: 面向連接,傳輸數(shù)據(jù)安全,傳輸速度慢
         - UDP: 面向無連接,傳輸不數(shù)據(jù)安全,傳輸速度快
       
      - 能夠說出TCP協(xié)議下兩個常用類名稱
         - Socket : 一個該類的對象就代表一個客戶端程序。
        - Socket(String host, int port)   根據(jù)ip地址字符串和端口號創(chuàng)建客戶端Socket對象
          - 注意事項:只要執(zhí)行該方法,就會立即連接指定的服務(wù)器程序,如果連接不成功,則會拋出異常。
      如果連接成功,則表示三次握手通過。
        - OutputStream getOutputStream(); 獲得字節(jié)輸出流對象
        - InputStream getInputStream();獲得字節(jié)輸入流對象
        - void close();關(guān)閉Socket, 會自動關(guān)閉相關(guān)的流
        - 補充:關(guān)閉通過socket獲得的流,會關(guān)閉socket,關(guān)閉socket,同時也會關(guān)閉通過socket獲得的流
            
      - ServerSocket : 一個該類的對象就代表一個服務(wù)器端程序。 
        - ServerSocket(int port); 根據(jù)指定的端口號開啟服務(wù)器。
        - Socket accept(); 等待客戶端連接并獲得與客戶端關(guān)聯(lián)的Socket對象  如果沒有客戶端連接,該方法會一直阻塞
        - void close();關(guān)閉ServerSocket
        
      - 能夠編寫TCP協(xié)議下字符串?dāng)?shù)據(jù)傳輸程序
      - 能夠理解TCP協(xié)議下文件上傳案例
      - 能夠理解TCP協(xié)議下BS案例
      - 能夠說出NIO的優(yōu)點
          解決高并發(fā),提高cpu執(zhí)行效率   
      
      
      posted on 2022-04-24 23:43  ofanimon  閱讀(65)  評論(0)    收藏  舉報
      // 側(cè)邊欄目錄 // https://blog-static.cnblogs.com/files/douzujun/marvin.nav.my1502.css
      主站蜘蛛池模板: 久久精品国产亚洲不av麻豆| 性一交一乱一伦一| 久久人妻精品大屁股一区| 激情综合色综合久久综合| 亚洲av无码精品色午夜| 精品国产一区av天美传媒| 国内在线视频一区二区三区| 欧洲亚洲国内老熟女超碰| 欧美野外伦姧在线观看| 国产精品露脸视频观看| 国产午夜亚洲精品不卡下载| 咸阳市| 精品一区二区无码免费| 极品白嫩少妇无套内谢| 日本japanese丰满白浆| 日韩加勒比一本无码精品| 18禁无遮挡啪啪无码网站破解版| 亚洲av鲁丝一区二区三区黄| 成人污视频| 亚洲高清免费在线观看| 国产仑乱无码内谢| 亚洲精品日韩在线观看| 钟山县| 久久精品人妻无码一区二区三区| AV老司机AV天堂| 久女女热精品视频在线观看| 国产精品呻吟一区二区三区| 国产精品亚洲综合网一区| 麻豆精品一区二区三区蜜臀| 午夜福利高清在线观看| 屏东县| 内射极品少妇xxxxxhd| 青青草无码免费一二三区| 97人妻天天爽夜夜爽二区| 国产黄色一区二区三区四区| h无码精品3d动漫在线观看| 亚洲性日韩精品一区二区| 亚洲av永久无码精品水牛影视 | 99视频偷窥在线精品国自产拍| 国产精品美女一区二三区| 国厂精品114福利电影免费|