面試官竟然問我BIO、NIO與Netty

掃描下方二維碼或者微信搜索公衆號菜鳥飛呀飛,便可關注微信公衆號,閱讀更多Spring源碼分析Java併發編程文章。java

微信公衆號

前言

年初的時候給本身定了一個目標,就是學習Netty的源碼,所以在Q2的OKR上,其中一個目標就是學習Netty源碼,而且在部門內進行一次Netty相關的學習分享。然而,出生牛犢不怕虎,當時甚至都不知道Netty怎麼用,上來就是看源碼,看了不到半天就懵了,連學下去的信心都沒了。到Q2結束,個人Netty學習進度幾乎爲0。Q3的OKR上再次定下了要看Netty源碼的目標,而後,整個Q3中,這個口號依舊只是在嘴上喊喊,從沒付出行動。到了Q4,依舊在OKR上寫上了Netty源碼學習的目標,眼瞅着Q4還剩下一個月,Netty源碼的學習產出依舊爲0。連着失信兩次,在Q二、Q3上空喊了兩個季度的口號,常言道事不過三,終於決定動筆寫寫Netty相關的源碼分析。linux

結合我自身學習Netty的經歷,在正式進入Netty的源碼分析,決定有必要先說說BIO、NIO的關係。在平時工做中,不多直接寫網絡編程的代碼,即便要寫也都是使用成熟的網絡框架,不多直接進行Socket編程。下面將結合一個Demo示例,來熟悉下網絡編程相關知識。在Demo示例中,客戶端每隔三秒向服務端發送一條Hello World的消息,服務端收到後進行打印。編程

BIO

BIO是阻塞IO,也稱之爲傳統IO,在Java的網絡編程中,指的就是ServerSocket、Socket套接字實現的網絡通訊,這也是咱們最開始學Java時接觸到的網絡編程相關的類。服務端在有新鏈接接入時或者在讀取網絡消息時,它會對主線程進行阻塞。下面是經過BIO來實現上面場景的代碼。bootstrap

服務端代碼BioServer.javawindows

/** * @author liujinkun * @Title: BioServer * @Description: BIO 服務端 * @date 2019/11/24 2:54 PM */
public class BioServer {

    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8080);
        while(true){
            // accept()方法是個阻塞方法,若是沒有客戶端來鏈接,線程就會一直阻塞在這兒
            Socket accept = serverSocket.accept();
            InputStream inputStream = accept.getInputStream();
            byte[] bytes = new byte[1024];
            int len;
            // read()方法是一個阻塞方法,當沒有數據可讀時,線程會一直阻塞在read()方法上
            while((len = inputStream.read(bytes)) != -1){
                System.out.println(new String(bytes,0,len));
            }
        }
    }
}
複製代碼

在服務端的代碼中,建立了一個ServerSocket套接字,並綁定端口8080,而後在while死循環中,調用ServerSocket的accept()方法,讓其不停的接收客戶端鏈接。accept()方法是一個阻塞方法,當沒有客戶端來鏈接時,main線程會一直阻塞在accept()方法上(現象:程序一直停在accept()方法這一行,不往下執行)。當有客戶端鏈接時,main線程解阻塞,程序繼續向下運行。接着調用read()方法來從客戶端讀取數據,read()方法也是一個阻塞方法,若是客戶端發送來數據,則read()能讀取到數據;若是沒有數據可讀,那麼main線程就又會一直停留在read()方法這一行。數組

這裏使用了兩個while循環,外層的while循環是爲了保證能不停的接收鏈接,內層的while循環是爲了保證不停的從客戶端中讀取數據。性能優化

客戶端代碼BioClient.java服務器

/** * @author liujinkun * @Title: BioClient * @Description: BIO 客戶端 * @date 2019/11/24 2:54 PM */
public class BioClient {

    public static ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();

    public static void main(String[] args) throws IOException {

        Socket socket = new Socket("127.0.0.1",8080);
        // 採用具備定時執行任務的線程池,來模擬客戶端每隔一段時間來向服務端發送消息
        // 這裏是每隔3秒鐘向服務端發送一條消息
        executorService.scheduleAtFixedRate(()->{
            try {
                OutputStream outputStream = socket.getOutputStream();
                // 向服務端發送消息(消息內容爲:客戶端的ip+端口+Hello World,示例:/127.0.0.1:999999 Hello World)
                String message = socket.getLocalSocketAddress().toString() + " Hello World";
                outputStream.write(message.getBytes());
                outputStream.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }
        },0,3,TimeUnit.SECONDS);

    }
}
複製代碼

在客戶端的代碼中,建立了一個Socket套接字,並指定要鏈接的服務器地址和端口號。而後經過使用一個具備定時執行任務功能的線程池,讓客戶端每隔3秒鐘向服務端發送一條數據。微信

這裏爲了每隔3秒發送一次數據,使用了具備定時執行任務功能的線程池,也能夠不使用它,採用線程的sleep()方法來模擬3秒鐘。若是有對線程池不夠了解的朋友,能夠參考這兩篇文章: 線程池ThreadPoolExecutor的實現原理爲何《阿里巴巴Java開發手冊》上要禁止使用Executors來建立線程池網絡

而後咱們分別啓動服務端和一個客戶端,從服務端的控制檯就能夠看到,每一個3秒鐘就會打印一行 客戶端的ip+端口+Hello World的日誌。

/127.0.0.1:99999 Hello World
/127.0.0.1:99999 Hello World
/127.0.0.1:99999 Hello World
複製代碼

爲了模擬多個客戶端的接入,而後咱們再啓動一個客戶端,這個時候咱們期待在服務端的控制檯,會打印出兩個客戶端的ip+端口+Hello World,因爲服務端和兩個客戶端都是在同一臺機器上,所以這個時候打印出來的兩個客戶端的ip是相同的,可是端口口應該是不同的。咱們指望的日誌輸出應該是這樣的。

/127.0.0.1:99999 Hello World
/127.0.0.1:88888 Hello World
/127.0.0.1:99999 Hello World
/127.0.0.1:88888 Hello World
複製代碼

然而,這只是咱們指望的,實際現象,並不是如此,當咱們再啓動一個客戶端後,發現控制檯始終只會出現一個客戶端的端口號,並不是兩個。

那麼爲何呢?緣由就在於服務端的代碼中,read()方法是一個阻塞方法,當第一個客戶端鏈接後,讀取完第一個客戶端的數據,因爲第一個客戶端一直不釋放鏈接,所以服務端也不知道它還有沒有數據要發送過來,這個時候服務端的main線程就一直等在read()方法處,當有第二個客戶端接入時,因爲main線程一直阻塞在read()方法處,所以它沒法執行到accept()方法來處理新的鏈接,因此此時咱們看到的現象就是,只會打印一個客戶端發送來的消息。

那麼咱們該怎麼辦呢?既然知道了問題出現main線程阻塞在read()方法處,也就是在讀數據的時候出現了阻塞。而要解決阻塞的問題,最直接的方式就是利用多線程技術了,所以咱們就在讀數據的時候新開啓一條線程來進行數據的讀取。升級以後的服務端代碼以下。

服務端代碼BioServerV2.java

/** * @author liujinkun * @Title: BioServer * @Description: BIO 服務端 * @date 2019/11/24 2:54 PM */
public class BioServerV2 {

    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8080);
        while (true) {
            // accept()方法是個阻塞方法,若是沒有客戶端來鏈接,線程就會一直阻塞在這兒
            Socket accept = serverSocket.accept();
            // 用另一個線程來讀寫數據
            handleMessage(accept);
        }

    }

    private static void handleMessage(Socket socket) {
        // 新建立一個線程來讀取數據
        new Thread(() -> {
            try {
                InputStream inputStream = socket.getInputStream();
                byte[] bytes = new byte[1024];
                int len;
                while ((len = inputStream.read(bytes)) != -1) {
                    System.out.println(new String(bytes, 0, len));
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();

    }
}
複製代碼

客戶端的代碼不變,依然使用BioClient。在服務端的代碼BioServerV2中,讀數據的操做咱們提取到了handleMessage()方法中進行處理,在該方法中,會新建立一個線程來讀取數據,這樣就不會形成main線程阻塞在read()方法上了。啓動服務端和兩個客戶端進行驗證。控制檯打印結果以下。

/127.0.0.1:99999 Hello World
/127.0.0.1:88888 Hello World
/127.0.0.1:99999 Hello World
/127.0.0.1:88888 Hello World
複製代碼

雖然解決了多個客戶端同時接入的問題,可是其中的缺點咱們也很容易發現:每當有一個新的客戶端來鏈接服務端時,咱們都須要爲這個客戶端建立一個線程來處理讀數據的操做,當併發度很高時,咱們就須要建立不少的線程,這顯然這是不可取的。線程是服務器的寶貴資源,建立和銷燬都須要花費很長時間,當線程過多時,CPU的上線文切換也更加頻繁,這樣就會形成服務響應緩慢。當線程過多時,甚至還會出現句柄溢出、OOM等異常,最終致使服務宕機。另外,咱們在讀取數據時,是基於IO流來讀取數據的,每次只能讀取一個或者多個字節,性能較差。

所以BIO的服務端,適用於併發度不高的應用場景,可是對於高併發,服務負載較重的場景,使用BIO顯然是不適合的。

NIO

爲了解決BIO沒法應對高併發的問題,JDK從1.4開始,提供了一種新的網絡IO,即NIO,一般稱它爲非阻塞IO。然而NIO的代碼極爲複雜和難懂,下面簡單介紹寫NIO中相關的類。

NIO相關的類均在java.nio包下。與BIO中ServerSocket、Socket對應,NIO中提供了ServerSocketChannle、SocketChannel分別表示服務端channel和客戶端channel。與BIO中不一樣的是,NIO中出現了Selector輪詢器的概念,不一樣的操做系統有不一樣的實現方式(在windows平臺底層實現是select,在linux內核中採用epoll實現,在MacOS中採用poll實現)。另外NIO是基於ByteBuffer來讀寫數據的。

介紹了這麼多關於NIO的類,估計你也懵逼了,下面先看看如何用NIO來實現上面的場景。

服務端代碼NioServer.java

/** * @author liujinkun * @Title: NioServer * @Description: NIO 服務端 * @date 2019/11/24 2:55 PM */
public class NioServer {

    public static void main(String[] args) throws IOException {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        // 輪詢器,不一樣的操做系統對應不一樣的實現類
        Selector selector = Selector.open();
        // 綁定端口
        serverSocketChannel.bind(new InetSocketAddress(8080));
        serverSocketChannel.configureBlocking(false);
        // 將服務端channel註冊到輪詢器上,並告訴輪詢器,本身感興趣的事件是ACCEPT事件
        serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);

        while(true){
            // 調用輪詢器的select()方法,是讓輪詢器從操做系統上獲取全部的事件(例如:新客戶端的接入、數據的寫入、數據的寫出等事件)
            selector.select(200);
            // 調用select()方法後,輪詢器將查詢到的事件所有放入到了selectedKeys中
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            // 遍歷全部事件
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while(iterator.hasNext()){
                SelectionKey key = iterator.next();

                // 若是是新鏈接接入
                if(key.isAcceptable()){
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    System.out.println("有新客戶端來鏈接");
                    socketChannel.configureBlocking(false);
                    // 有新的客戶端接入後,就樣將客戶端對應的channel所感興趣的時間是可讀事件
                    socketChannel.register(selector,SelectionKey.OP_READ);
                }
                // 若是是可讀事件
                if(key.isReadable()){
                    // 從channel中讀取數據
                    SocketChannel channel = (SocketChannel) key.channel();
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                    channel.read(byteBuffer);
                    byteBuffer.flip();
                    System.out.println(Charset.defaultCharset().decode(byteBuffer));
                    // 讀完了之後,再次將channel所感興趣的時間設置爲讀事件,方便下次繼續讀。當若是後面要想往客戶端寫數據,那就註冊寫時間:SelectionKey.OP_WRITE
                    channel.register(selector,SelectionKey.OP_READ);
                }
                // 將SelectionKey從集合中移除,
                // 這一步很重要,若是不移除,那麼下次調用selectKeys()方法時,又會遍歷到該SelectionKey,這就形成重複處理了,並且最終selectionKeys這個集合的大小會愈來愈大。
                iterator.remove();
            }


        }
    }
}
複製代碼

看完代碼實現,你可能更加懵逼了。這麼簡單的一個Hello World,竟然要寫這麼多的代碼,並且還全都看不懂。(我第一次接觸這些代碼和API時,就是這種想法,以致於我Q二、Q3季度都不想去學和NIO相關的東西)。下面簡答解釋下這段代碼。

    1. 首先經過ServerSocketChannel.open()這行代碼建立了一個服務端的channel,也就是服務端的Socket。(在計算機網路的7層模型或者TCP/IP模型中,上層的應用程序經過Socket來和底層溝通)
    1. 經過Selector.open()建立了一個輪詢器,這個輪詢器就是後續用來從操做系統中,遍歷有哪些socket準備好了。這麼說有點抽象,舉個栗子。坐火車時,一般都會有補票環節。每過一站後,乘務員就會在車箱中吼一嗓子,哪些人須要補票的?須要補票的人去幾號車箱辦理。這個乘務員就對應NIO中輪詢器,定時去車箱中吼一嗓子(也就是去操做系統中「吼一嗓子」),這個時候若是有人須要補票(有新的客戶端接入或者讀寫事件發生),那麼它就會去對應的車箱辦理(這些接入事件或者讀寫事件就會跑到selectKeys集合中)。而對應BIO,每對應一個新客戶端,都須要新建一個線程,也就是說每出現一個乘客,咱們都要爲它配備一個乘務員,這顯然是不合理的,不可能有那麼多的乘務員。所以這就是NIO對BIO的一個巨大優點。
    1. 而後爲服務端綁定端口號,並設置爲非阻塞模式。咱們使用NIO的目的就是爲了使用它的非阻塞特性,所以這裏須要調用 serverSocketChannel.configureBlocking(false)設置爲非阻塞
    1. 而後將ServerSocketChannel註冊到輪詢器selector上,並告訴輪詢器,它感興趣的事件是ACCEPT事件(服務端的Channel就是用來處理客戶端接入的,所以它感興趣的事件就是ACCEPT事件。爲何要把它註冊到輪詢器上呢?前面已經說到了,輪詢器會按期去操做系統中「吼一嗓子,誰要補票」,若是不註冊到輪詢器上(不上火車),輪詢器吼一嗓子的時候,你怎麼聽得見呢?)
    1. 接着就是在一個while循環中,每過一段時間讓輪詢器去操做系統中輪詢有哪些事件發生。select()方法就是去操做系統中輪詢(吼一嗓子),它能夠傳入一個參數,表示在操做系統中等多少毫秒,若是在這段時間中沒有事件發生(沒有人要補票),那麼就從操做系統中返回。若是有事件發生,那麼就將這些事件方法放到輪詢器的publicSelectedKeys屬性中,當調用selector.selectedKeys()方法時,就將這些事件返回。
    1. 接下來就是判斷事件是哪一種事件,是接收事件仍是讀事件,亦或是寫事件,而後針對每種不一樣的事件作不一樣的處理。
    1. 最後將key從集合中移除。爲何移除,見代碼註釋。 好了,解釋了那麼多,裏面的細節還有不少,此時估計你仍是佷懵。很懵就對了,這就是JDK中NIO的特色,操做繁瑣,涉及到的類不少,API也不少,開發者想要掌握這些,難度很大。關鍵是掌握了,不必定代碼寫得好;寫得好,不必定BUG少;BUG少,不必定性能高。JDK的NIO寫法,一不當心就容易BUG滿天飛。 上面是服務端NIO的寫法,這個時候,能夠直接利用BIO的客戶端去進行測試。固然NIO也有客戶端寫法。雖然NIO的寫法很複雜,但一回生,二回熟,多見幾次就習慣了,因此下面仍是貼出了NIO客戶端的寫法。

NIO客戶端NioClient.java

/** * @author liujinkun * @Title: NioClient * @Description: NIO客戶端 * @date 2019/11/24 2:55 PM */
public class NioClient {

    private static ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();

    public static void main(String[] args) throws IOException {
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.configureBlocking(false);
        boolean connect = socketChannel.connect(new InetSocketAddress("127.0.0.1", 8080));
        // 由於鏈接是一個異步操做,因此須要在下面一行判斷鏈接有沒有完成。若是鏈接尚未完成,就進行後面的操做,會出現異常
        if(!connect){
            // 若是鏈接未完成,就等待鏈接完成
            socketChannel.finishConnect();
        }
        // 每一個3秒向服務端發送一條消息
        executorService.scheduleAtFixedRate(() -> {
            try {
                String message = socketChannel.getLocalAddress().toString() + " Hello World";
                // 使用ByteBuffer進行數據發送
                ByteBuffer byteBuffer = ByteBuffer.wrap(message.getBytes());
                socketChannel.write(byteBuffer);
            } catch (IOException e) {
                e.printStackTrace();
            }

        }, 0, 3, TimeUnit.SECONDS);
    }
}
複製代碼

NIO客戶端的寫法相對比較簡單,利用SocketChannel進行IP和端口的綁定,而後調用connect()方法進行鏈接到服務端。最後利用ByteBuffer裝載數據,經過SocketChannel將數據寫出去。

相比BIO而言,NIO不須要爲每一個鏈接去建立一個線程,它是經過輪詢器,按期的從操做系統中獲取到準備好的事件,而後進行批量處理。同時NIO是經過ByteBuffer來讀寫數據,相比於BIO中經過流來一個字節或多個字節的對數據,NIO的效率更高。可是ByteBuffer的數據結構設計,有點反人類,一不當心就會出BUG。關於ByteBuffer詳細的API操做,有興趣的朋友能夠本身去嘗試寫寫。關於BIO和NIO的區別,能夠用下面一張圖表示。

BIO與NIO示意圖

Netty

BIO在高併發下不適合用,而NIO雖然能夠應對高併發的場景,可是它一方面由於寫法複雜,掌握難度大,更重要的是還存在空輪詢的BUG(產生空輪詢的緣由是操做系統的緣故),所以Netty出現了。Netty是目前應該使用最普遍的一款網絡框架,用官方術語講就是:它是一款基於事件驅動的高性能的網絡框架。實際上它是一款將NIO包裝了的框架,同時它規避了JDK中空輪訓的BUG。雖然它是對NIO的包裝,可是它對不少操做進行了優化,其性能更好。目前在不少Java流行框架中,底層都採用了Netty進行網絡通訊,好比RPC框架中Dubbo、Motan,Spring5的異步編程,消息隊列RocketMQ等等都使用了Netty進行網絡通訊。

既然Netty這麼好,怎麼用呢?那麼接下來就用Netty實現上面的場景。 服務端NettyServer.java

/** * @author liujinkun * @Title: NettyServer * @Description: Netty服務端 * @date 2019/11/24 2:56 PM */
public class NettyServer {

    public static void main(String[] args) {
        // 負責處理鏈接的線程組
        NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
        // 負責處理IO和業務邏輯的線程組
        NioEventLoopGroup workerGroup = new NioEventLoopGroup(8);
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    // 添加日誌打印,用來觀察Netty的啓動日誌
                    .handler(new LoggingHandler(LogLevel.INFO))
                    // 添加用來處理客戶端channel的處理器handler
                    .childHandler(new ChannelInitializer<NioSocketChannel>() {
                        @Override
                        protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
                            ChannelPipeline pipeline = nioSocketChannel.pipeline();
                            // 字符串解碼器
                            pipeline.addLast(new StringDecoder())
                                    // 自定義的handler,用來打印接收到的消息
                                    .addLast(new SimpleChannelInboundHandler<String>() {
                                        @Override
                                        protected void channelRead0(ChannelHandlerContext channelHandlerContext, String message) throws Exception {
                                            System.out.println(message);
                                        }

                                        @Override
                                        public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
                                            super.channelRegistered(ctx);
                                            System.out.println("有新客戶端鏈接");
                                        }
                                    });
                        }
                    });
            // 綁定端口,並啓動
            ChannelFuture channelFuture = serverBootstrap.bind(8080).sync();
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            // 關閉線程組
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}
複製代碼

上面的代碼雖然看起來也很長,可是這段代碼幾乎是不變的,它幾乎適用於全部場景,咱們只須要修改childHandler()這一行相關的方法便可。這裏面的代碼纔是處理咱們自定義的業務邏輯的。具體代碼細節,就再也不詳細解釋了,後面會單獨寫文章來分別從源碼的解讀來分析Netty服務端的啓動、新鏈接的接入、數據的處理、半包拆包等問題。

啓動NettyServer,能夠直接使用BioClient或者NioClient來測試NettyServer。固然,Netty也有客戶端的寫法。代碼以下。

Netty客戶端NettyClient

/** * @author liujinkun * @Title: NettyClient * @Description: Netty客戶端 * @date 2019/11/24 2:57 PM */
public class NettyClient {

    private static ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();

    public static void main(String[] args) {
        // 客戶端只須要一個線程組便可
        NioEventLoopGroup nioEventLoopGroup = new NioEventLoopGroup();
        try {
            // 採用Bootstrap而不是ServerBootstrap
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(nioEventLoopGroup)
                    // 設置客戶端的SocketChannel
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<NioSocketChannel>() {
                        @Override
                        protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
                            ChannelPipeline pipeline = nioSocketChannel.pipeline();
                            // 添加一個字符串編碼器
                            pipeline.addLast(new StringEncoder());
                        }
                    });
            ChannelFuture channelFuture = bootstrap.connect("", 8080).sync();

            Channel channel = channelFuture.channel();

            executorService.scheduleAtFixedRate(()->{
                String message = channel.localAddress().toString() + " Hello World";
                channel.writeAndFlush(message);
            },0,3,TimeUnit.SECONDS);

            channel.closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            nioEventLoopGroup.shutdownGracefully();
        }

    }
}
複製代碼

實際上客戶端的代碼也幾乎是固定的,全部場景均可以複用這段代碼,惟一須要修改的就是handler()方法這一塊,須要針對本身的業務邏輯去添加不一樣的處理器。

相比於NIO的寫法,Netty的寫法更加簡潔,代碼量相對更少,幾行簡單的代碼就搞定了服務的啓動,新鏈接接入,數據讀寫,編解碼等問題。這也是爲何Netty使用這麼普遍的緣由。相比於NIO,Netty有以下幾點優勢。

    1. JDK的NIO存在空輪詢的BUG,而Netty則巧妙的規避了這一點;
    1. JDK的API複雜,開發人員使用起來比較困難,更重要的是,很容易寫出BUG;而Netty的API簡單,容易上手。
    1. Netty的性能更高,它在JDK的基礎上作了不少性能優化,例如將selector中的publicSelectedKeys屬性的數據結構由Set集合改爲了數組。
    1. Netty底層對IO模型能夠隨意切換,針對Reactor三種線程模型,只須要經過修改參數就能夠實現IO模型的切換。
    1. Netty通過了衆多高併發場景的考驗,如Dubbo等RPC框架的驗證。
    1. Netty幫助咱們解決了TCP的粘包拆包等問題,開發人員不用去關心這些問題,只需專一於業務邏輯開發便可。
    1. Netty支持不少協議棧。JDK自帶的對象序列化性能不好,序列化後碼流較大,而是用其餘方式的序列化則性能較高,例如protobuf等。
    1. 優勢還有不少...

總結

  • 本文經過一個示例場景,從BIO的實現到NIO實現,最後到Netty的實現,分別對比了它們的優缺點,毫無疑問,Netty是網絡編程的首選框架,至於有點不少。

  • 最後總結一下,Netty的學習很難,涉及到的知識點、名詞不少,須要耐下性子,仔細琢磨,多寫示例代碼。筆者看了兩套視頻,一本《Netty權威指南》,以及《Netty權威指南》做者李林鋒在InfoQ中發表的全部netty相關的文章,甚至還去學了學計算機網絡與操做系統中關於網絡這一塊的知識,最後才感受本身剛入門。固然了,主要是筆者比較菜。

  • 若是想學好Netty,仍是建議多看看JDK中NIO相關的API,雖然這些API的寫法很複雜,可是對於學習Netty會有很大幫助。而後難用,學會了能夠不用,但不能夠不學。

  • 若是有須要關於Netty學習教程的朋友,能夠關注公衆號,聯繫做者領取免費視頻。

微信公衆號
相關文章
相關標籤/搜索