1. 深入Hotspot源碼與Linux內(nèi)核理解NIO與Epoll

        共 22170字,需瀏覽 45分鐘

         ·

        2021-06-28 09:43

        有道無(wú)術(shù),術(shù)尚可求也!有術(shù)無(wú)道,止于術(shù)!

        一、為什么必須去了解NIO

        首先你需要之后Netty的主要實(shí)現(xiàn)手段就是Nio,很多人一直學(xué)不明白Netty,根本原因是 除了日常開(kāi)發(fā)中很難能夠?qū)嵺`,很大一部分原因是不熟悉NIO,事實(shí)上真正熟悉了NIO和它背后的原理之后,去查看Netty的源碼就有如神助!我們今天就從最基本的IO、以及NIO學(xué)起!

        二、操作系統(tǒng)是如何定義I/O的

        I/O相關(guān)的操作,詳細(xì)各位從事java的人員并不陌生,顧名思義也就是Input/Output,對(duì)應(yīng)著連個(gè)動(dòng)詞,Read/Write 讀寫(xiě)兩個(gè)動(dòng)作,但是在上層系統(tǒng)應(yīng)用中無(wú)論是讀還是寫(xiě),操作系統(tǒng)都不會(huì)直接的操作物理機(jī)磁盤(pán)數(shù)據(jù),而是由系統(tǒng)內(nèi)核加載磁盤(pán)數(shù)據(jù)!我們以Read為例,當(dāng)程序中發(fā)起了一個(gè)Read請(qǐng)求后,操作系統(tǒng)會(huì)將數(shù)據(jù)從內(nèi)核緩沖區(qū)加載到用戶緩沖區(qū),如果內(nèi)核緩沖區(qū)內(nèi)沒(méi)有數(shù)據(jù),內(nèi)核會(huì)將該次讀請(qǐng)求追加到請(qǐng)求隊(duì)列,當(dāng)內(nèi)核將磁盤(pán)數(shù)據(jù)讀取到內(nèi)核緩沖區(qū)后,再次執(zhí)行讀請(qǐng)求,將內(nèi)核緩沖區(qū)的數(shù)據(jù)復(fù)制到用戶緩沖區(qū),繼而返回給上層應(yīng)用系統(tǒng)!

        write請(qǐng)求也是類似于上圖的情況,用戶進(jìn)程寫(xiě)入到用戶緩沖區(qū),復(fù)制到內(nèi)核緩沖區(qū),然后當(dāng)數(shù)據(jù)到達(dá)一定量級(jí)之后由內(nèi)核寫(xiě)入到網(wǎng)口或者磁盤(pán)文件!

        假設(shè)我們以Socket服務(wù)端為例,我們口述一下一個(gè)完整的讀寫(xiě)操作的流程:

        1. 客戶端發(fā)送一個(gè)數(shù)據(jù)到網(wǎng)卡,由操作系統(tǒng)內(nèi)核將數(shù)據(jù)復(fù)制到內(nèi)核緩沖區(qū)!
        2. 當(dāng)用戶進(jìn)程發(fā)起read請(qǐng)求后,將數(shù)據(jù)從內(nèi)核緩沖區(qū)復(fù)制到用戶緩沖區(qū)!
        3. 用戶緩沖區(qū)獲取到數(shù)據(jù)之后程序開(kāi)始進(jìn)行業(yè)務(wù)處理!處理完成后,調(diào)用Write請(qǐng)求,將數(shù)據(jù)從用戶緩沖區(qū)寫(xiě)入到內(nèi)核緩沖區(qū)!
        4. 系統(tǒng)內(nèi)核將數(shù)據(jù)從內(nèi)核緩沖區(qū)寫(xiě)入到網(wǎng)卡,通過(guò)底層的通訊協(xié)議發(fā)送到客戶端!

        三、網(wǎng)絡(luò)編程中的IO模型

        本文旨在讓初學(xué)者先大致了解一下基本原理,所以這里并不會(huì)涉及到太多代碼,具體的實(shí)現(xiàn)邏輯,可以關(guān)注后續(xù)源碼分析的時(shí)候的文章,這里只做一個(gè)鋪墊,為日后的學(xué)習(xí)做一個(gè)比較好的鋪墊!

        1. 同步阻塞I/O

        I. 傳統(tǒng)的阻塞IO模型

        這種模型是單線程應(yīng)用,服務(wù)端監(jiān)聽(tīng)客戶端連接,當(dāng)監(jiān)聽(tīng)到客戶端的連接后立即去做業(yè)務(wù)邏輯的處理,該次請(qǐng)求沒(méi)有處理完成之前,服務(wù)端接收到的其他連接全部阻塞不可操作!當(dāng)然開(kāi)發(fā)中,我們也不會(huì)這樣寫(xiě),這種寫(xiě)法只會(huì)存在于協(xié)議demo中!這種寫(xiě)法的缺陷在哪呢?

        我們看圖發(fā)現(xiàn),當(dāng)一個(gè)新連接被接入后,其他客戶端的連接全部處于阻塞狀態(tài),那么當(dāng)該客戶端處理客戶端時(shí)間過(guò)長(zhǎng)的時(shí)候,會(huì)導(dǎo)致阻塞的客戶端連接越來(lái)越多導(dǎo)致系統(tǒng)崩潰,我們是否能夠找到一個(gè)辦法,使其能夠?qū)I(yè)務(wù)處理與Accept接收新連接分離開(kāi)來(lái)!這樣業(yè)務(wù)處理不影響新連接接入就能夠解決該問(wèn)題!

        II. 偽異步阻塞IO模型

        這種業(yè)務(wù)模型是是對(duì)上一步單線程模型的一種優(yōu)化,當(dāng)一個(gè)新連接接入后,獲取到這個(gè)鏈接的Socket,交給一條新的線程去處理,主程序繼續(xù)接收下一個(gè)新連接,這樣就能夠解決同一時(shí)間只能處理一個(gè)新連接的問(wèn)題,但是,明眼人都能看出來(lái),這樣有一個(gè)很致命的問(wèn)題,這種模型處理小并發(fā)短時(shí)間可能不會(huì)出現(xiàn)問(wèn)題,但是假設(shè)有10w連接接入,我需要開(kāi)啟10w個(gè)線程,這樣會(huì)把系統(tǒng)直接壓崩!我們需要限制線程的數(shù)量,那么肯定就會(huì)想到線程池,我們來(lái)優(yōu)化一下這個(gè)模型吧!

        III. 優(yōu)化偽異步阻塞IO模型

        這個(gè)模型是JDK1.4之前,沒(méi)有NIO的時(shí)候的一個(gè)經(jīng)典Socket模型,服務(wù)端接收到客戶端新連接會(huì)后,將Socket連接以及業(yè)務(wù)邏輯包裝為任務(wù)提交到線程池,由線程池開(kāi)始執(zhí)行,同時(shí)服務(wù)端繼續(xù)接收新連接!這樣能夠解決上一步因?yàn)榫€程爆炸所引發(fā)的問(wèn)題,但是我們回想下線程池的的提交步驟:當(dāng)核心線程池滿了之后會(huì)將任務(wù)放置到隊(duì)列,當(dāng)隊(duì)列滿了之后,會(huì)占用最大線程數(shù)的數(shù)量繼續(xù)開(kāi)啟線程,當(dāng)達(dá)到最大線程數(shù)的時(shí)候開(kāi)始拒絕策略!  證明我最大的并發(fā)數(shù)只有1500個(gè),其余的都在隊(duì)列里面占1024個(gè),假設(shè)現(xiàn)在的連接數(shù)是1w個(gè),并且使用的是丟棄策略,那么會(huì)有近6000的連接任務(wù)被丟棄掉,而且1500個(gè)線程,線程之間的切換也是一個(gè)特別大的開(kāi)銷!這是一個(gè)致命的問(wèn)題!

        上述的三種模型除了有上述的問(wèn)題之外,還有一個(gè)特別致命的問(wèn)題,他是阻塞的!

        在哪里阻塞的呢?

        • 連接的時(shí)候,當(dāng)沒(méi)有客戶端連接的時(shí)候是阻塞的!沒(méi)有客戶端連接的時(shí)候,線程只能傻傻的阻塞在哪里等待新連接接入!
        • 等待數(shù)據(jù)寫(xiě)入的時(shí)候是阻塞的,當(dāng)一個(gè)新連接接入后但是不寫(xiě)入數(shù)據(jù),那么線程會(huì)一直等待數(shù)據(jù)寫(xiě)入,直到數(shù)據(jù)寫(xiě)入完成后才會(huì)停止阻塞! 假設(shè)我們使用 優(yōu)化后的偽異步線程模型 ,1000個(gè)連接可能只有 100個(gè)連接會(huì)頻繁寫(xiě)入數(shù)據(jù),剩余900個(gè)連接都很少寫(xiě)入,那么就會(huì)有900個(gè)線程在傻傻等待客戶端寫(xiě)入數(shù)據(jù),所以,這也是一個(gè)很嚴(yán)重的性能開(kāi)銷!

        現(xiàn)在我們總結(jié)一下上述模型的問(wèn)題:

        1. 線程開(kāi)銷浪費(fèi)嚴(yán)重!
        2. 線程間的切換頻繁,效率低下!
        3. read/write執(zhí)行的時(shí)候會(huì)進(jìn)行阻塞!
        4. accept會(huì)阻塞等待新連接

        那么,我們是否有一種方案,用很少的線程去管理成千上萬(wàn)的連接,read/write會(huì)阻塞進(jìn)程,那么就會(huì)進(jìn)入到下面的模型

        2. 同步非阻塞I/O

        同步非阻塞I/O模型就必須使用java NIO來(lái)實(shí)現(xiàn)了,看一段簡(jiǎn)單的代碼:

        public static void main(String[] args) throws IOException {
            //新接連池
            List<SocketChannel> socketChannelList = new ArrayList<>(8);
            //開(kāi)啟服務(wù)端Socket
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.bind(new InetSocketAddress(8098));
            //設(shè)置為非阻塞
            serverSocketChannel.configureBlocking(false);
            while (true) {
                //探測(cè)新連接,由于設(shè)置了非阻塞,這里即使沒(méi)有新連接也不會(huì)阻塞,而是直接返回null
                SocketChannel socketChannel = serverSocketChannel.accept();
                //當(dāng)返回值不為null的時(shí)候,證明存在新連接
                if(socketChannel!=null){
                    System.out.println("新連接接入");
                    //將客戶端設(shè)置為非阻塞  這樣read/write不會(huì)阻塞
                    socketChannel.configureBlocking(false);
                    //將新連接加入到線程池
                    socketChannelList.add(socketChannel);
                }
                //迭代器遍歷連接池
                Iterator<SocketChannel> iterator = socketChannelList.iterator();
                while (iterator.hasNext()) {
                    ByteBuffer byteBuffer = ByteBuffer.allocate(128);
                    SocketChannel channel = iterator.next();
                    //讀取客戶端數(shù)據(jù) 當(dāng)客戶端數(shù)據(jù)沒(méi)有寫(xiě)入完成的時(shí)候也不會(huì)阻塞,長(zhǎng)度為0
                    int read = channel.read(byteBuffer);

                    if(read > 0) {
                        //當(dāng)存在數(shù)據(jù)的時(shí)候打印數(shù)據(jù)
                        System.out.println(new String(byteBuffer.array()));
                    }else if(read == -1) {
                        //客戶端退出的時(shí)候刪除該連接
                        iterator.remove();
                        System.out.println("斷開(kāi)連接");
                    }
                }
            }
        }

        上述代碼我們可以看到一個(gè)關(guān)鍵的邏輯:serverSocketChannel.configureBlocking(false); 這里被設(shè)置為非阻塞的時(shí)候無(wú)論是 accept還是read/write都不會(huì)阻塞!具體的為什么會(huì)非阻塞,我放到文章后面說(shuō),我們看一下這種的實(shí)現(xiàn)邏輯有什么問(wèn)題!

        看這里,我們似乎的確使用了一條線程處理了所有的連接以及讀寫(xiě)操作,但是假設(shè)我們有10w連接,活躍連接(經(jīng)常read/write)只有1000,但是我們這個(gè)線程需要每次否輪詢10w條數(shù)據(jù)處理,極大的消耗了CPU!

        我們期待什么?期待的是,每次輪詢值輪詢有數(shù)據(jù)的Channel, 沒(méi)有數(shù)據(jù)的就不管他,比如剛剛的例子,只有1000個(gè)活躍連接,那么每次就只輪詢這1000個(gè),其他的有讀寫(xiě)了有數(shù)據(jù)就輪詢,沒(méi)讀寫(xiě)就不輪詢!

        3. 多路復(fù)用模型

        多路復(fù)用模型是JAVA NIO 推薦使用的經(jīng)典模型,內(nèi)部通過(guò) Selector進(jìn)行事件選擇,Selector事件選擇通過(guò)系統(tǒng)實(shí)現(xiàn),具體流程看一段代碼:

        public static void main(String[] args) throws IOException {
            //開(kāi)啟服務(wù)端Socket
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.bind(new InetSocketAddress(8098));
            //設(shè)置為非阻塞
            serverSocketChannel.configureBlocking(false);
            //開(kāi)啟一個(gè)選擇器
            Selector selector = Selector.open();
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            while (true) {
                // 阻塞等待需要處理的事件發(fā)生
                selector.select();
                // 獲取selector中注冊(cè)的全部事件的 SelectionKey 實(shí)例
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                //獲取已經(jīng)準(zhǔn)備完成的key
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()) {
                    SelectionKey next = iterator.next();
                    //當(dāng)發(fā)現(xiàn)連接事件
                    if(next.isAcceptable()) {
                        //獲取客戶端連接
                        SocketChannel socketChannel = serverSocketChannel.accept();
                        //設(shè)置非阻塞
                        socketChannel.configureBlocking(false);
                        //將該客戶端連接注冊(cè)進(jìn)選擇器 并關(guān)注讀事件
                        socketChannel.register(selector, SelectionKey.OP_READ);
                        //如果是讀事件
                    }else if(next.isReadable()){
                        ByteBuffer allocate = ByteBuffer.allocate(128);
                        //獲取與此key唯一綁定的channel
                        SocketChannel channel = (SocketChannel) next.channel();
                        //開(kāi)始讀取數(shù)據(jù)
                        int read = channel.read(allocate);
                        if(read > 0){
                            System.out.println(new String(allocate.array()));
                        }else if(read == -1){
                            System.out.println("斷開(kāi)連接");
                            channel.close();
                        }
                    }
                    //刪除這個(gè)事件
                    iterator.remove();
                }
            }
        }

        相比上面的同步非阻塞IO,這里多了一個(gè)selector選擇器,能夠?qū)﹃P(guān)注不同事件的Socket進(jìn)行注冊(cè),后續(xù)如果關(guān)注的事件滿足了條件的話,就將該socket放回到到里面,等待客戶端輪詢!

        NIO底層在JDK1.4版本是用linux的內(nèi)核函數(shù)select()或poll()來(lái)實(shí)現(xiàn),跟上面的NioServer代碼類似,selector每次都會(huì)輪詢所有的sockchannel看下哪個(gè)channel有讀寫(xiě)事件,有的話就處理,沒(méi)有就繼續(xù)遍歷,JDK1.5開(kāi)始引入了epoll基于事件響應(yīng)機(jī)制來(lái)優(yōu)化NIO,首先我們會(huì)將我們的SocketChannel注冊(cè)到對(duì)應(yīng)的選擇器上并選擇關(guān)注的事件,后續(xù)操作系統(tǒng)會(huì)根據(jù)我們?cè)O(shè)置的感興趣的事件將完成的事件SocketChannel放回到選擇器中,等待用戶的處理!那么它能夠解決上述的問(wèn)題嗎?

        肯定是可以的,因?yàn)?strong>上面的一個(gè)同步非阻塞I/O痛點(diǎn)在于CPU總是在做很多無(wú)用的輪詢,在這個(gè)模型里被解決了!這個(gè)模型從selector中獲取到的Channel全部是就緒的,后續(xù)只需要也就是說(shuō)他每次輪詢都不會(huì)做無(wú)用功!

        深入 底層概念解析
        select模型

        如果要深入分析NIO的底層我們需要逐步的分析,首先,我們需要了解一種叫做select()函數(shù)的模型,它是什么呢?他也是NIO所使用的多路復(fù)用的模型之一,是JDK1.4的時(shí)候所使用的一種模型,他是epoll模型之前所普遍使用的一種模型,他的效率不高,但是當(dāng)時(shí)被普遍使用,后來(lái)才會(huì)被人優(yōu)化為epoll!

        他是如何做到多路復(fù)用的呢?如圖:

        1. 首先我們需要了解操作系統(tǒng)有一個(gè)叫做工作隊(duì)列的概念,由CPU輪流執(zhí)行工作隊(duì)列里面的進(jìn)程,我們平時(shí)書(shū)寫(xiě)的Socket服務(wù)端客戶端程序也是存在于工作隊(duì)列的進(jìn)程中,只要它存在于工作隊(duì)列,它就會(huì)被CPU調(diào)用執(zhí)行!我們下文將該網(wǎng)絡(luò)程序稱之為進(jìn)程A
        image-20210310223623730
        1. 他的內(nèi)部會(huì)維護(hù)一個(gè) Socket列表,當(dāng)調(diào)用系統(tǒng)函數(shù)select(socket[])的時(shí)候,操作系統(tǒng)會(huì)將進(jìn)程A加入到Socket列表中的每一個(gè)Socket的等待隊(duì)列中,同時(shí)將進(jìn)程A從工作隊(duì)列移除,此時(shí),進(jìn)程A處于阻塞狀態(tài)!

        2. 當(dāng)網(wǎng)卡接收到數(shù)據(jù)之后,觸發(fā)操作系統(tǒng)的中斷程序,根據(jù)該程序的Socket端口取對(duì)應(yīng)的Socket列表中尋找該進(jìn)程A,并將進(jìn)程A從所有的Socket列表中的等待隊(duì)列移除,并加入到操作系統(tǒng)的工作隊(duì)列!

          image-20210310223932406
        3. 此時(shí)進(jìn)程A被喚醒,此時(shí)知道至少有一個(gè)Socket存在數(shù)據(jù),開(kāi)始依次遍歷所有的Socket,尋找存在數(shù)據(jù)的Socket并進(jìn)行后續(xù)的業(yè)務(wù)操作

          image-20210310224109432

        該種結(jié)構(gòu)的核心思想是,我先讓所有的Socket都持有這個(gè)進(jìn)程A的引用,當(dāng)操作系統(tǒng)觸發(fā)Socket中斷之后,基于端口尋找到對(duì)應(yīng)的Socket,就能夠找到該Socket對(duì)應(yīng)的進(jìn)程,再基于進(jìn)程,就能夠找到所有被監(jiān)控的Socket!   要注意,當(dāng)進(jìn)程A被喚醒,就證明一件事,操作系統(tǒng)發(fā)生了Socket中斷,就至少有一個(gè)Socket的數(shù)據(jù)準(zhǔn)備就緒,只需要將所有的Socket遍歷,就能夠找到并處理本次客戶端傳入的數(shù)據(jù)!

        但是,你會(huì)發(fā)現(xiàn),這種操作極為繁瑣,中間似乎存在了很多遍歷,先將進(jìn)程A加入的所有的Socket等待隊(duì)列需要遍歷一次,發(fā)生中斷之后需要遍歷一次Socket列表,將所有對(duì)于進(jìn)程A的引用移除,并將進(jìn)程A的引用加入到工作隊(duì)列!因?yàn)榇藭r(shí)進(jìn)程A并不知道哪一個(gè)Socket是有數(shù)據(jù)的,所以,由需要再次遍歷一遍Socket列表,才能真正的處理數(shù)據(jù),整個(gè)操作總共遍歷了3此Socket,為了保證性能,所以1.4版本種,最多只能監(jiān)控1024個(gè)Socket,去掉標(biāo)準(zhǔn)輸出輸出和錯(cuò)誤輸出只剩下1021個(gè),因?yàn)槿绻鸖ocket過(guò)多勢(shì)必造成每次遍歷消耗性能極大!

        epoll模型

        epoll總共分為三個(gè)比較重要的函數(shù):

        1. epoll_create 對(duì)應(yīng)JDK NIO代碼種的Selector.open()
        2. epoll_ctl 對(duì)應(yīng)JDK NIO代碼中的socketChannel.register(selector,xxxx);
        3. epoll_wait 對(duì)應(yīng)JDK NIO代碼中的 selector.select();

        感興趣的可以下載一個(gè)open-jdk-8u的源代碼,也可以關(guān)注公眾號(hào)回復(fù)openJdk獲取源碼壓縮包!

        他是如何優(yōu)化select的呢?

        1. epoll_create:這些系統(tǒng)調(diào)用將返回一個(gè)非負(fù)文件描述符,他也和Socket一樣,存在一個(gè)等待隊(duì)列,但是,他還存在一個(gè)就緒隊(duì)列!

          image-20210310231234730
        2. epoll_ctl :添加Socket的監(jiān)視,對(duì)應(yīng)Java中將SocketChannel注冊(cè)到Selector中,他會(huì)將創(chuàng)建的文件描述符的引用添加到Socket的等待隊(duì)列!這點(diǎn)比較難理解,注意是將EPFD(Epoll文件描述符)放到Socket的等待隊(duì)列!

          image-20210310231305931
        3. 當(dāng)操作系統(tǒng)發(fā)生中斷程序后,基于端口號(hào)(客戶端的端口號(hào)是唯一的)尋找到對(duì)應(yīng)的Socket,獲取到EPFD的引用,將該Socket的引用加入到EPFD的就序列表!

          image-20210310232256771
        4. epoll_wait:查看EPFD的就緒列表是否存在Socket的引用,如果存在就直接返回,不存在就將進(jìn)程A加入到EPFD的等待隊(duì)列,并移除進(jìn)程A再工作隊(duì)列的引用!

        image-20210310231400214
        image-20210310231425286
        1. 當(dāng)網(wǎng)卡再次接收到數(shù)據(jù),發(fā)生中斷,進(jìn)行上述步驟,將該Socket的因引用加入到就序列表,并喚醒進(jìn)程A,移除該EPFD等待隊(duì)列的進(jìn)程A,將進(jìn)程A加入到工作隊(duì)列,程序繼續(xù)執(zhí)行!

          image-20210310232111376

        4. 異步非阻塞I/O

        異步非阻塞模型是用戶應(yīng)用只需要發(fā)出對(duì)應(yīng)的事件,并注冊(cè)對(duì)應(yīng)的回調(diào)函數(shù),由操作系統(tǒng)完成后,回調(diào)回調(diào)函數(shù),完成具體的約為操作!先看一段代碼

        public static void main(String[] args) throws Exception {
                final AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(9000));
          //監(jiān)聽(tīng)連接事件,并注冊(cè)回調(diào)
                serverChannel.accept(nullnew CompletionHandler<AsynchronousSocketChannel, Object>() {
                    @Override
                    public void completed(AsynchronousSocketChannel socketChannel, Object attachment) {
                        try {
                            System.out.println("2--"+Thread.currentThread().getName());
                            // 再此接收客戶端連接,如果不寫(xiě)這行代碼后面的客戶端連接連不上服務(wù)端
                            serverChannel.accept(attachment, this);
                            System.out.println(socketChannel.getRemoteAddress());
                            ByteBuffer buffer = ByteBuffer.allocate(1024);
                            //監(jiān)聽(tīng)read事件并注冊(cè)回調(diào)
                            socketChannel.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
                                @Override
                                public void completed(Integer result, ByteBuffer buffer) {
                                    System.out.println("3--"+Thread.currentThread().getName());
                                    buffer.flip();
                                    System.out.println(new String(buffer.array(), 0, result));
                                    //向客戶端回寫(xiě)一個(gè)數(shù)據(jù)
                                    socketChannel.write(ByteBuffer.wrap("HelloClient".getBytes()));
                                }
              //發(fā)生錯(cuò)誤調(diào)這個(gè)
                                @Override
                                public void failed(Throwable exc, ByteBuffer buffer) {
                                    exc.printStackTrace();
                                }
                            });
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
           //發(fā)生錯(cuò)誤調(diào)這個(gè)
                    @Override
                    public void failed(Throwable exc, Object attachment) {
                        exc.printStackTrace();
                    }
                });

                System.out.println("1--"+Thread.currentThread().getName());
                Thread.sleep(Integer.MAX_VALUE);
            }
        }

        AIO客戶端

        public static void main(String... args) throws Exception {
            AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open();
            socketChannel.connect(new InetSocketAddress("127.0.0.1"9000)).get();
            socketChannel.write(ByteBuffer.wrap("HelloServer".getBytes()));
            ByteBuffer buffer = ByteBuffer.allocate(512);
            Integer len = socketChannel.read(buffer).get();
            if (len != -1) {
                System.out.println("客戶端收到信息:" + new String(buffer.array(), 0, len));
            }
        }
        image-20210310233152285

        整體邏輯就是,告訴系統(tǒng)我要關(guān)注一個(gè)連接的事件,如果有連接事件就調(diào)用我注冊(cè)的這個(gè)回調(diào)函數(shù),回調(diào)函數(shù)中獲取到客戶端的連接,然后再次注冊(cè)一個(gè)read請(qǐng)求,告訴系統(tǒng),如果有可讀的數(shù)據(jù)就調(diào)用我注冊(cè)的這個(gè)回調(diào)函數(shù)!當(dāng)存在數(shù)據(jù)的時(shí)候,執(zhí)行read回調(diào),并寫(xiě)出數(shù)據(jù)!

        為什么Netty使用NIO而不是AIO?

        在Linux系統(tǒng)上,AIO的底層實(shí)現(xiàn)仍使用Epoll,沒(méi)有很好實(shí)現(xiàn)AIO,因此在性能上沒(méi)有明顯的優(yōu)勢(shì),而且被JDK封裝了一層不容易深度優(yōu)化,Linux上AIO還不夠成熟。Netty是異步非阻塞框架,Netty在NIO上做了很多異步的封裝。簡(jiǎn)單來(lái)說(shuō),現(xiàn)在的AIO實(shí)現(xiàn)比較雞肋!


        才疏學(xué)淺,如果文章中理解有誤,歡迎大佬們私聊指正!歡迎關(guān)注作者的公眾號(hào),一起進(jìn)步,一起學(xué)習(xí)!


               
        ??「轉(zhuǎn)發(fā)」「在看」,是對(duì)我最大的支持??



        瀏覽 69
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 中文无码熟妇人妻 | 露娜用孙悟空的金箍棒戳哪 | 久热9| 免费看欧美成人A片无码 | 成人午夜福利在线观看 |