基於Netty實現Web容器-Netty版Tomcat(一)

在正式進入主題之前,先要看看一些基本的理論。這裏旨在明確這些基礎的概念,好更深刻的進一步理解Netty。

首先,什麼是IO?其實平常其實工作中用得也是比較多的了,這裏簡單做個總結。

I:InputStream,字節輸入流 ,用於讀取數據爲字節流《Reads the next byte of data from the input stream》

O:OutputStream,字節輸出流,用於將字節流寫入到流《Writes the specified byte to this output stream》

流,啥是流?日常生活中,聽過比較多的就是河流、水流、車流等等,這裏就拿車流做比喻。

假定小C要結婚了,請了一個豪華車隊,去新娘老家接新娘到酒店,於是就有了這麼個概念:這裏有一車隊從新娘家,經過一路到達到酒店。這裏有起始點,有結束點,可以理解爲數據的產出點和數據的接收點。這個過程,可以理解爲建立一個傳輸通道(車隊走過的一路),通過流的方式,將數據從一個地方傳輸到了另外一個地方。當有這麼一種情況,每一輛車只能載一個人,同時,一輛車裝完一個人,然後就開車出發,然後下一輛車,在上一個人,出發,持續這麼過程,可以理解爲字節輸入流(一個上車),到達目的地後,下一個人,可以理解爲字節輸出流(一個人下車)。這時候,可能就太慢了,於是新郎新娘決定,一輛車多上些人,把一家人的那種單獨上一輛車,此時,可以理解爲字符流(以字符的方式輸入輸出),與此同時,車隊上滿了之後,一個車隊一起走,理解這種情況爲緩衝流(一個車隊可能就一次把人都帶走了)。於是,流就可以簡單分爲字節流和字符流,同時兩者都可以通過包含緩衝區的方式傳輸,也就都存在字節緩衝流和字符緩衝流。

下面看下其相互依賴關係(這裏僅僅截取常用到的,實際並不僅僅這些,詳細請查看API)

字節流:

在這裏插入圖片描述
字符流:
在這裏插入圖片描述
那麼接下來家假設這麼一種情況,小C邀請其好友小D全程協助車隊出發到達這麼一個過程,把小D全程協助的過程,稱爲線程,也就是小D線程來做這件事,於是車隊上車開始,小D就只能在這裏逐個看着每個車上乘客,然後沿着一條路到達酒店,假定這一路屬於鄉村公路,也就是單行道,往酒店走的,則只能往酒店走,反之亦然。
IO(BIO)、NIO、NIO2(AIO)
有需要的同學,可關注公衆號“依蕁”,不定期分享技術乾貨
在這裏插入圖片描述
基於上述過程,可以理解爲傳統IO的一個操作,也叫BIO,即同步阻塞IO,同步在於,讀的時候不能寫,寫的時候不能讀(類似上述過程,單行道車流),當然這裏也可以用多線程去做一個僞異步,這裏不過這種探討;阻塞在於,讀和寫這個過程阻塞的,讀/寫需要線程把所有的數據全部讀/寫完畢,或者發生異常,這個線程才能結束(類似上述過程,小D全程協助時,在車隊上乘客的時候,小D只能等着乘客都上車完畢後,他才能繼續做下一步操作)。

同步阻塞IO在實際應用中,會存在很多弊端,用之前寫的小demo(詳情請見《基於socket通信編寫的聊天工具》)爲例,解釋下這種同步阻塞存在的一些問題和弊端

看服務端的主要代碼,在可以獲取多個客戶端連接的情況下,採用了多線程監聽:
在這裏插入圖片描述
在這裏插入圖片描述
如果存在大量客戶端連接的情況下,服務端是會開闢很多線程來監聽並獲取數據,首先多線程的線程間交替本身就很好系統性能,再者過多的服務端線程易造成堆棧溢出,創建線程失敗等情況的發生,嚴重甚至發生服務器宕機等情況的發生

由於讀寫操作屬於阻塞的(不僅僅是讀寫操作,還有上圖中,等待連接的時候,也是線程阻塞的),那麼就有可能存在大量連接的線程出現在一個阻塞的情況,服務端處理速度過慢,後續的客戶端連接容易出現連接不上服務端的情況。同時寫的時候不能讀,讀數據的時候不能寫,這也是一種很不良好的體驗。
如何比較良好的解決這些問題?

小C的新娘告訴他,現在政府新修建了高速公路,雙向車道,於是車可以從上面過,同時基於雙車道,車輛可以方便在接送切換,不需要再去遵循哪條路是來的路,哪條路是去的路,再者小D覺得這樣子挨個守着太累而且費時,於是留下每個車師傅的電話,輪詢的方式瞭解到車輛目前的狀態(是否上好了人,是否到達目的地等等),方便指揮車輛的下一個操作。

對於上述改進的接送人操作,映射到JDK1.4以後的NIO,同樣對傳統的IO操作,進行類似的改進,相對於傳統的流(單向鄉間小道),變成了現在的通道Channel(牛叉的雙向高速公路)傳輸數據,傳統的阻塞讀寫操作(守候車輛上下人),變成了選擇器-多路複用器Selector輪詢當前通道Channel中的數據狀態,進行後續IO操作(遠程輪詢車輛狀態,安排工作)

通道(Channel)、多路複用器(Selector)是NIO(官方稱爲new IO,也稱爲non-blocking IO)的三大重要概念中的2種了,下面介紹下另外一個概念–緩衝區(buffer)

傳統BIO數據讀寫主要針對字節/字符操作,而NIO讀寫數據則是針對緩衝區操作,任何的NIO都是操作緩衝區,緩衝區內部是數組,並且提供了對數據結構化訪問及其維護讀寫位置信息

以下是幾個常用的緩衝區的相互依賴關係:
在這裏插入圖片描述
這裏用代碼描述幾個核心屬性:

package com.lgli.nio.api;

import java.nio.ByteBuffer;

/**
 * NioApi properties
 * capacity:緩衝區中的最大容量
 * limit:限制,緩衝區中最大可操作容量
 * position:位置,當前操作的緩衝區的位置
 * mark:標記,記錄讀取的緩衝區的位置
 * @author lgli
 * @since 1.0
 */
public class NioApi {
    public static void main(String[] args) {
        String str = "hello,世界";
        // 1 分配一塊指定大小的非直接緩衝區
        ByteBuffer allocate = ByteBuffer.allocate(1024);
        // ByteBuffer allocates = ByteBuffer.allocateDirect(1024);
        System.out.println("分配非直接緩衝區後緩衝區容量(capacity):"+allocate.capacity());
        System.out.println("分配非直接緩衝區後緩衝區限制(limit):"+allocate.limit());
        System.out.println("分配非直接緩衝區後緩衝區位置(position):"+allocate.position());
        // 2 向緩衝區中放入數據
        allocate.put(str.getBytes());
        System.out.println("緩衝區中寫入數據後緩衝區容量(capacity):"+allocate.capacity());
        System.out.println("緩衝區中寫入數據後緩衝區限制(limit):"+allocate.limit());
        System.out.println("緩衝區中寫入數據後緩衝區位置(position):"+allocate.position());
        // 3 切換讀寫模式,由前面的寫切換爲讀
        allocate.flip();
        System.out.println("切換讀取數據後緩衝區容量(capacity):"+allocate.capacity());
        System.out.println("切換讀取數據後緩衝區限制(limit):"+allocate.limit());
        System.out.println("切換讀取數據後緩衝區位置(position):"+allocate.position());

        // 4 讀取緩衝區中的數據
        ByteBuffer byteBuffer = allocate.get(str.getBytes(), allocate.position(), allocate.limit());
        System.out.println("讀取緩衝區中的數據爲:"+new String(byteBuffer.array()));
        System.out.println("讀取緩衝區後緩衝區容量(capacity):"+allocate.capacity());
        System.out.println("讀取緩衝區後緩衝區限制(limit):"+allocate.limit());
        System.out.println("讀取緩衝區後緩衝區位置(position):"+allocate.position());

        //切換爲重新讀取緩衝區中的數據
        allocate.rewind();
        System.out.println("切換爲重新讀取緩衝區中的數據後緩衝區容量(capacity):"+allocate.capacity());
        System.out.println("切換爲重新讀取緩衝區中的數據後緩衝區限制(limit):"+allocate.limit());
        System.out.println("切換爲重新讀取緩衝區中的數據後緩衝區位置(position):"+allocate.position());

        //再次讀取緩衝區中的數據
        byte [] strArr = new byte[allocate.limit()];
        byteBuffer.get(strArr);
        System.out.println("再次讀取緩衝區中的數據:"+new String(strArr,0,allocate.limit()));
        System.out.println("再次讀取緩衝區中的數據後緩衝區容量(capacity):"+allocate.capacity());
        System.out.println("再次讀取緩衝區中的數據後緩衝區限制(limit):"+allocate.limit());
        System.out.println("再次讀取緩衝區中的數據後緩衝區位置(position):"+allocate.position());
    }
}

上述代碼同時也描述了NIO在讀寫文件操作的具體過程,即:

在這裏插入圖片描述
上述創建緩衝區的方式有2種:

java.nio.ByteBuffer#allocate

java.nio.ByteBuffer#allocateDirect

這兩種方式,均是獲取內存中指定大小的緩衝區,其中allocate是指在JVM堆內存中分配一塊兒緩衝區,如下源碼:
在這裏插入圖片描述
而allocateDirect是指在JVM堆外內存中分配內存空間,源碼如下:

在這裏插入圖片描述
點擊進去:
在這裏插入圖片描述
有興趣的朋友可以追根到底的瞭解下JVM堆內內存和堆外內存的具體詳細區別和差異,網上有對此做出大量分析的博文。這裏只做簡單說明(如果有不正確的請指出),Java的IO操作(不僅僅只是IO),通常會在JVM堆中和堆外存在2份數據,即JVM堆中存在一份數據,然後拷貝到堆外,真正操作系統層面所操作到的數據是堆外的數據,因爲JVM堆中存在的數據受到MinorGC影響較大,導致數據移動較大,同時Java的IO操作最底層的byte數組也不一定是一個連續的地址空間, 然後Java的IO操作,最終都會調用到操作系統的IO接口,而操作系統的IO操作,需要一個準確連續的數據地址,所以相對堆中的數據地址對於操作系統而言,極大程度來說就不是一個正確的數據地址空間,所以Java的IO操作,在堆內內存中需要一份操作的數據,然後複製一份到堆外內存中。
此時,allocate方法就是按照常規的在JVM堆內中分配空間,然後copy到堆外,供操作系統層面操作
而allocateDirect則是省掉了JVM對內分配空間然後拷貝到堆外內存的操作,直接在堆外內存分配內存
這個時候,有個疑問,爲啥需要在JVM堆內分配空間,然後copy,直接allocateDirect多好。其實兩者有弊有利。
allocateDirect,對於讀寫文件效率高,但是分配和取消內存所耗費成本較大。
allocate,效率偏低,但是直接操作在JVM內存中,其耗費承成本較低。
兩者差異其實在讀取字節數量級較小的時候,差距並不是那麼明顯,只有當Buffer容量到達一定的級別後,差距才比較突出,這裏引用別人的一張圖片描述下,就不做過多深究了
在這裏插入圖片描述
一般來說,應用程序在讀取文件操作,首先通過操作系統接口層面讀取文件接口將文件加載到物理內核中,在通過一個複製操作,將文件緩衝到應用程序內存,供應用程序操作,然後應用程序操作後,複製到物理內核中,最後將文件讀出到磁盤,即下面過程:
在這裏插入圖片描述
NIO基於文件操作,實現了一種利用內存映射文件的方式來讀取寫入文件,其實現機制主要通過直接操作內存映射文件,而不需要應用程序和系統接口層面同時拷貝操作文件,下面用圖解表示這一過程:

在這裏插入圖片描述
這裏用3段代碼描述下,同樣的複製一個文件,描述三者的區別:

package com.lgli.nio.copy;

import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

/**
 * NioCopyFile
 * 比較複製複製文件性能
 * @author lgli
 * @since 1.0
 */
public class NioCopyFile {


    public static void main(String[] args) throws Exception{
//        copyByAllocate();
//        copyByAllocateDirect();
        copyByAllocateDirectMapped();
    }


    /**
     * 此方法通過分配非直接緩衝區,然後來複制文件
     * @throws Exception Exception
     */
    private static void copyByAllocate() throws Exception{
        long start = System.currentTimeMillis();
        //獲取文件讀取通道,方法需要傳入參數,路徑+文件模式《這裏表示讀取》
        FileChannel in = FileChannel.open(Paths.get("F:\\entertainment\\movie\\馬達加斯加的企鵝.mkv"),
                StandardOpenOption.READ);
        //獲取文件寫入通道,方法傳入參數,路徑+文件模式《這裏可讀可寫,後面表示創建或者替換(已經存在則替換)》
        FileChannel out = FileChannel.open(Paths.get("F:\\entertainment\\movie\\馬達加斯加的企鵝-01.mkv"),
                StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE);
        ByteBuffer byteBuffer = ByteBuffer.allocate(2048);
        //讀取文件至緩衝區
        while(in.read(byteBuffer)>0){
            //切換至讀取模式
            byteBuffer.flip();
            //把緩衝區中的數據寫出
            out.write(byteBuffer);
            //清空緩衝區,再次讀取文件
            byteBuffer.clear();
        }
        in.close();
        out.close();
        System.out.println("非直接緩衝區方式複製文件耗費:"+(System.currentTimeMillis()-start)+"毫秒!");
    }


    /**
     * 此方法通過分配直接緩衝區複製文件
     * @throws Exception Exception
     */
    private static void copyByAllocateDirect() throws Exception{
        long start = System.currentTimeMillis();
        //獲取文件讀取通道,方法需要傳入參數,路徑+文件模式《這裏表示讀取》
        FileChannel in = FileChannel.open(Paths.get("F:\\entertainment\\movie\\馬達加斯加的企鵝.mkv"),
                StandardOpenOption.READ);
        //獲取文件寫入通道,方法傳入參數,路徑+文件模式《這裏可讀可寫,後面表示創建或者替換(已經存在則替換)》
        FileChannel out = FileChannel.open(Paths.get("F:\\entertainment\\movie\\馬達加斯加的企鵝-02.mkv"),
                StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE);
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(Integer.MAX_VALUE);
        //讀取文件至緩衝區
        while(in.read(byteBuffer)>0){
            //切換至讀取模式
            byteBuffer.flip();
            //把緩衝區中的數據寫出
            out.write(byteBuffer);
            //清空緩衝區,再次讀取文件
            byteBuffer.clear();
        }
        in.close();
        out.close();
        System.out.println("直接緩衝區方式複製文件耗費:"+(System.currentTimeMillis()-start)+"毫秒!");
    }

    /**
     * 方法通過內存映射文件複製文件
     * @throws Exception Exception
     */
    private static void  copyByAllocateDirectMapped() throws Exception{

        long start = System.currentTimeMillis();
        //獲取文件讀取通道,方法需要傳入參數,路徑+文件模式《這裏表示讀取》
        FileChannel in = FileChannel.open(Paths.get("F:\\entertainment\\movie\\馬達加斯加的企鵝.mkv"),
                StandardOpenOption.READ);
        //獲取文件寫入通道,方法傳入參數,路徑+文件模式《這裏可讀可寫,後面表示創建或者替換(已經存在則替換)》
        FileChannel out = FileChannel.open(Paths.get("F:\\entertainment\\movie\\馬達加斯加的企鵝-03.mkv"),
                StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE);
        //獲取讀通道的緩衝區
        MappedByteBuffer inMapped = in.map(FileChannel.MapMode.READ_ONLY, 0, in.size());
        //獲取寫通道緩衝區
        MappedByteBuffer outMapped = out.map(FileChannel.MapMode.READ_WRITE, 0, in.size());
        byte[] bytes = new byte[inMapped.limit()];
        inMapped.get(bytes);
        outMapped.put(bytes);
        in.close();
        out.close();
        System.out.println("MappedByteBuffer映射文件方式複製文件耗費:"+(System.currentTimeMillis()-start)+"毫秒!");

    }
}

運行上述代碼,通過時間對比可以看出,當操作的Buffer的容量不是很大的時候,其實非直接緩衝區和直接緩衝區複製文件差不多,但是利用內存映射文件的方式,效率是高了很多,但是內存映射文件呢存在的一個問題就是,在運行的時候,會佔用較大的CPU內存,易出現程序假死狀態(這時候文件複製其實已經完成)。
在這裏插入圖片描述
下面,基於NIO通信,對之前的聊天系統進行改造

其客戶端和服務端連接方式改爲如下:
在這裏插入圖片描述
基於服務端,主要思想如下:
在這裏插入圖片描述
輪詢期間,可根據不同的狀態,做不一樣的事,而不是排隊等待

客戶端,主要思想如下:

在這裏插入圖片描述
下面看代碼,服務端:

package com.lgli.nio.chart.server;



import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
import java.util.Set;

/**
 * NioChartService
 * @author lgli
 * @since 1.0
 */
public class NioChartServer {

    //多路複用器
    private Selector selector;

    private int port = 8080;




    public NioChartServer() {
        try {
            //打開多路複用器
            selector = Selector.open();
            //打開數據傳輸管道,服務端打開serversocket
            ServerSocketChannel socketChannel = ServerSocketChannel.open();
            //設置管道非阻塞
            socketChannel.configureBlocking(false);
            //將管道綁定到socket上,建立服務器監聽
            socketChannel.bind(new InetSocketAddress(port));
            //將數據傳輸管道註冊到多路複用器上,狀態爲等待連接
            socketChannel.register(selector, SelectionKey.OP_ACCEPT);
            //輸出打印服務端提示
            System.out.println("服務端啓動服務連接監聽,端口:"+port);
            Runnable runnable = ()->{
                while(true){
                    try{
                        //等待時間100毫秒,返回可用數據傳輸管道的數量
                        int channelCounts = selector.select(100);
                        if(channelCounts <= 0){
                            //沒有則繼續循環監聽
                            continue;
                        }
                        //獲取所有的選擇通道集合
                        Set<SelectionKey> selectionKeys = selector.selectedKeys();
                        //循環處理通道中的事件
                        //java新特性寫法
                        //selectionKey是循環的每個參數對象;param是需要傳入的其他參數
                        //selectionKeys.forEach(this::dealWith);
                        selectionKeys.forEach(selectionKey->dealWith(selectionKey,socketChannel));
                        //清空處理過的事件
                        selectionKeys.clear();
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            };
            new Thread(runnable).start();
            Scanner scanner = new Scanner(System.in);
            while(scanner.hasNextLine()){
                //服務端發送數據到客戶端
                String s = scanner.nextLine();
                SocketChannel accept = socketChannel.accept();
                System.out.println(accept);
                accept.write(StandardCharsets.UTF_8.encode(s));
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    private void dealWith(SelectionKey selectionKey,ServerSocketChannel socketChannel) {

        if(null == selectionKey){
            return;
        }
        if(!selectionKey.isValid()){
            //無效連接,直接退出
            return;
        }
        if(selectionKey.isAcceptable()){
            //管道處於可接受狀態,
            try{
                //獲取數據傳輸管道
                SocketChannel sc = socketChannel.accept();
                //設置爲非阻塞
                sc.configureBlocking(false);
                //註冊多路複用器,設置爲可讀取模式,即當前連接的數據傳輸管道,註冊到多路複用器上,之後好處理這個連接中的數據傳輸
                sc.register(selector,SelectionKey.OP_READ);
                //將對應的此管道設置爲其他連接可連接
                selectionKey.interestOps(SelectionKey.OP_ACCEPT);
                //打印輸出,服務端獲取到此連接
                System.out.println("服務端收到連接:"+sc.getRemoteAddress());
                //歡迎語發送給客戶端
                sc.write(StandardCharsets.UTF_8.encode("歡迎您:"+sc.getRemoteAddress()));
            }catch (Exception e){
                e.printStackTrace();
            }
        }else if(selectionKey.isReadable()){
            //管道有數據傳輸過來,即,服務端可以讀取數據
            //獲取到管道
            SocketChannel sc = (SocketChannel) selectionKey.channel();
            ByteBuffer byteBuffer = ByteBuffer.allocate(2048);
            StringBuilder stringBuilder = new StringBuilder();
            try{
                while(sc.read(byteBuffer)>0){
                    //切換讀寫模式
                    byteBuffer.flip();
                    stringBuilder.append(StandardCharsets.UTF_8.decode(byteBuffer));
                }
                //打印客戶端回覆的數據
                System.out.println("服務端收到"+sc.getRemoteAddress()+"說:"+stringBuilder.toString());

                //將此對應的數據傳輸管道設置爲下一次可讀取數據
                selectionKey.interestOps(SelectionKey.OP_READ);
            }catch (Exception e){
                selectionKey.cancel();
                if(selectionKey.channel() != null){
                    try {
                        selectionKey.channel().close();
                    }catch (Exception es){
                        es.printStackTrace();
                    }
                }
                e.printStackTrace();
            }
            if(stringBuilder.length()>0){
                //將客戶端發送的數據,發送給其他客戶端
                System.out.println("準備發送數據.....");
                Set<SelectionKey> keys = selector.keys();
                keys.forEach(keyKeys-> {
                    try {
                        sendMsg(keyKeys,sc,sc.getRemoteAddress()+":"+stringBuilder.toString());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                });
            }
        }
    }

    private void sendMsg(SelectionKey keyKeys, SocketChannel thisSc,String toString) {
        //獲取所有註冊到多路複用器中的通道
        Channel channel = keyKeys.channel();
        if(!(channel instanceof SocketChannel)){
            return;
        }
        SocketChannel sc = (SocketChannel) channel;
        try {
            //發送給非當前通道連接的客戶端
            boolean isEq = sc.getRemoteAddress().toString().equals(thisSc.getRemoteAddress().toString());
            if(isEq){
                return;
            }
            sc.write(StandardCharsets.UTF_8.encode(toString));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    public static void main(String[] args) {
        new NioChartServer();
    }




}

客戶端:

package com.lgli.nio.chart.client;


import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
import java.util.Set;

/**
 * NioChartClient
 * @author lgli
 * @since 1.0
 */
public class NioChartClient {

    private Selector selector;

    private SocketChannel socketChannel;


    public NioChartClient(int port) {
        try{
            //打開多路複用器
            selector = Selector.open();
            //打開連接服務端的數據傳輸管道
            socketChannel = SocketChannel.open(new InetSocketAddress("localhost",port));
            //設置非阻塞
            socketChannel.configureBlocking(false);
            //將傳輸管道註冊到多路複用器上
            socketChannel.register(selector, SelectionKey.OP_READ);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    private void monitor() {
        //接受服務端發過來的數據,這裏由於鍵盤輸入是線程阻塞的,所以這裏新起一個線程來進行這個操作
        Runnable runnable = () ->{
            while(true){
                try{
                    int select = selector.select(100);
                    if(select <= 0){
                        continue;
                    }
                    Set<SelectionKey> selectionKeys = selector.selectedKeys();
                    selectionKeys.forEach(this :: dealWith);
                    selectionKeys.clear();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        };
        new Thread(runnable).start();

        //監聽鍵盤輸入向服務端寫入數據,
        Scanner scanner = new Scanner(System.in);
        try{
            while(scanner.hasNext()){
                String next = scanner.next();
                if(!"".equals(next)){
                    socketChannel.write(StandardCharsets.UTF_8.encode(next));
                }
            }

        }catch (Exception e){
            e.printStackTrace();
        }

    }

    private void dealWith(SelectionKey sc) {
        if(!sc.isValid()){
            //無效退出
            System.out.println("無效連接退出");
            return;
        }
        if(sc.isReadable()){
            //可讀,則讀取服務端發送來的數據
            ByteBuffer byteBuffer = ByteBuffer.allocate(2048);
            //獲取通道
            SocketChannel scl = (SocketChannel)sc.channel();
            StringBuilder sb = new StringBuilder();
            try{
                while(scl.read(byteBuffer)>0){
                    byteBuffer.flip();
                    sb.append(StandardCharsets.UTF_8.decode(byteBuffer));
                }
                if(sb.length()>0){
//                    System.out.println(scl.getRemoteAddress()+"說:"+sb.toString());
                    System.out.println(sb.toString());
                }
            }catch (Exception e){
                e.printStackTrace();
            }
            sc.interestOps(SelectionKey.OP_READ);
        }
    }

    public static void main(String[] args) {
        new NioChartClient(8080).monitor();

    }


}

然後運行服務端,接着運行多個客戶端,這裏用IntelliJ IDEA的小夥伴,提示下,可以並行跑相同代碼,只需要編輯配置即可(部分低版本的不行)
在這裏插入圖片描述
把這個勾上即可,於是達到了理想的狀態了

這兒本來有個視頻效果的,公衆號裏面可以看,這裏不能上傳視頻

下面簡單瞭解下NIO2,其實NIO是在傳統的BIO基礎上實現的同步非阻塞IO,那麼對於java1.7之後引入的NIO2(也叫AIO),就是一個異步非阻塞的IO,更加的優化了許多的操作,比較重大的改進是:1)比較全面的文件IO操作和對文件系統訪問的支持;2)異步通道的IO

對於NIO2的詳細介紹、及其Netty的引入將在後續中更新,本次若有不正確的地方,煩請指出。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章