netty實戰筆記 第四章 傳輸

探索Netty所提供的不同類型的傳輸,以及如何選擇一個最適合你的應用程序的傳輸。

本章主要內容

OIO 阻塞傳輸
NIO 異步傳輸
Local jvm內部的通信機制
Embedded 測試你的Channelhandler

寫一個案例:
java 寫一個應用程序簡單地接收連接,向客戶端寫Hi,然後關閉連接。
我們分爲java的阻塞IO(OIO),NIO,netty的OIO和NIO分別來實現。


java NIO版本

package com.moyang.netty.transport;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class NIOSocketDemo {

    public void server(int port) throws IOException {

        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        serverSocketChannel.configureBlocking(false);

        ServerSocket serverSocket = serverSocketChannel.socket();

        InetSocketAddress address = new InetSocketAddress(port);

        serverSocket.bind(address);

        // 打開Selector來處理Channel
        Selector selector = Selector.open();
        // 將ServerSocket註冊到Selector以接受連接
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        ByteBuffer msg = ByteBuffer.wrap("hi!\r\n".getBytes());

        for (; ; ) {
            try {
                selector.select();
            } catch (IOException e) {
                e.printStackTrace();
                break;
            }

            // 獲取所有接受時間的selectionKey實例
            Set<SelectionKey> readyKeys = selector.selectedKeys();

            Iterator<SelectionKey> iterator = readyKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                iterator.remove();
                try {
                    if (key.isAcceptable()) {
                        ServerSocketChannel server = (ServerSocketChannel) key.channel();
                        SocketChannel client = server.accept();

                        client.configureBlocking(false);
                        client.register(selector, SelectionKey.OP_ACCEPT | SelectionKey.OP_READ, msg.duplicate());
                        System.out.println("accept connection from " + client);
                    }

                    if (key.isWritable()) {
                        SocketChannel client = (SocketChannel) key.channel();
                        ByteBuffer buffer = (ByteBuffer) key.attachment();

                        if (buffer.hasRemaining()) {
                            if (client.write(buffer) == 0) {
                                break;
                            }
                        }
                        client.close();
                    }

                }catch (Exception e){
                    key.cancel();
                    key.channel().close();
                }
            }
        }
    }

    public static void main(String[] args) {
        try {
            new NIOSocketDemo().server(8000);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

netty OIO版本

package com.moyang.netty.transport;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.oio.OioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.oio.OioServerSocketChannel;

import java.nio.charset.Charset;

/**
 * 使用netty實現的阻塞版網絡處理
 */
public class NettyOIODemo {

    public void server(int port) {

        final ByteBuf buf = Unpooled.unreleasableBuffer(Unpooled.copiedBuffer("Hi\r\n", Charset.defaultCharset().forName("UTF-8")));

        EventLoopGroup group = new OioEventLoopGroup();

        try {
            // 創建ServerBootstrap
            ServerBootstrap b = new ServerBootstrap();

            b.group(group)
                    .channel(OioServerSocketChannel.class)
                    .localAddress(port)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        protected void initChannel(SocketChannel ch) throws Exception {

                            ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { // 添加一個ChannelInboundHandler來攔截和處理事件
                                @Override
                                public void channelActive(ChannelHandlerContext ctx) throws Exception {
                                    ctx.writeAndFlush(buf.duplicate())
                                            //將消息重寫到客戶端,並添加ChannelFutureListener,以便消息一被寫完就關閉連接。
                                            .addListener(ChannelFutureListener.CLOSE);
                                }
                            });
                        }
                    });

            ChannelFuture f = b.bind().sync();
            f.channel().closeFuture().sync();
        } catch (InterruptedException e) {
        } finally {
            try {
                group.shutdownGracefully().sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        new NettyOIODemo().server(8080);
    }
}

netty NIO版本

package com.moyang.netty.transport;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

import java.nio.charset.Charset;

public class NettyNIODemo {

    public void server(int port) {

        final ByteBuf buf = Unpooled.unreleasableBuffer(Unpooled.copiedBuffer("Hi\r\n",
                Charset.forName("UTF-8")));

        // 注意:使用了不同EventLoopGroup
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            // 創建ServerBootstrap
            ServerBootstrap b = new ServerBootstrap();
            b.group(group)
                    // 使用NioServerSocketChannel,非阻塞模式
                    // 注意和OIO模式相比
                    .channel(NioServerSocketChannel.class)
                    .localAddress(port)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                                @Override
                                public void channelActive(ChannelHandlerContext ctx) throws Exception {
                                    // 將消息寫完關閉連接。
                                    ctx.writeAndFlush(buf).addListener(ChannelFutureListener.CLOSE);
                                }
                            });
                        }
                    });

            ChannelFuture f = b.bind().sync();
            f.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                group.shutdownGracefully().sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        new NettyNIODemo().server(8081);
    }
}

以上就是簡單的對Netty的使用。接下來詳細的學習一下Netty的API

4.2 傳輸API

傳輸API的核心是Channel接口。public interface Channel extends AttributeMap, ChannelOutboundInvoker, Comparable

在這裏插入圖片描述

每個Channel都會被分配一個ChannelPipeline和ChannelConfig。ChannelConfig包含了該Channel的所有配置,並且支持熱更新。可以接收一個ChannelConfig的子類。
Channel實現Comparable接口,如果兩個不同的Channel實例返回了相同的散列碼就會報錯。
ChannelPipeline持有將所有的入站和出站的數據及事件的Channel實例。這些Channel實例實現了應用處理狀態邊和數據處理的邏輯。

ChannelHandler的典型用途:

  • 將數據從一種格式轉換爲另一中格式。
  • 提供異常的通知。
  • 提供Channel變爲活動的或者非活動的通知。
  • 提供當Channel註冊到EventLoop或者EventLoop註銷時的通知
  • 提供有關用戶自定義事件的通知。

Netty的Channel實現是線程安全的.

4.3 內置的傳輸

netty內置了一些開箱急用的傳輸。但是並不是他們所有的傳輸都支持每一種協議。所以你必須選擇一個和你的應用程序所有的協議相容的傳輸。

表格: netty所提供的傳輸

名稱 描述
NIO io.netty.channel.socket.nio 使用java.nio.channels包作爲基礎,基於選擇器的方式
Epoll io.netty.channel.epoll 由JNI驅動的epoll()和非阻塞IO。這個傳輸支持只有在Liunx上可用的多種特性,如SO_REUSEOIRT比NIO傳輸更快,而且完全是非阻塞的。
OIO io.netty.channel.socket.oio 使用java.net包作爲基礎–使用阻塞流
Local io.netty.channel.local 可以在VM內部通過管道進行通信的本地傳輸
Embedded io.netty.channel.embedded Embedded傳輸,允許使用Channelhandler而用不需要一個真正的基於網網絡的傳輸,在測試你的ChannelHandler實現時非常有用

4.3.1 NIO – 非阻塞IO

NIO提供了一個所有IO操作的全異步的實現,是基於選擇器的API。
選擇器的基本概念是充當一個註冊表,在請求在Channel的狀態發生變化的時候得到通知。可能變化有:

  • 新的Channel已被接受並且就緒。
  • Channel連接已經完成
  • Channel有已經就緒的可供讀取的數據。
  • Channel可用於寫數據。

選擇器運行一個檢查狀態變化並對其做出響應的線程上,在應用程序對狀態的改變作出響應之後,選擇器將會被重置,並重復這個過程。
在這裏插入圖片描述
在這裏插入圖片描述

4.3.2 Epoll–用於Liunx的本地非阻塞傳輸

性能要比NIO高。
使用的方式,替換爲EpollServerSocketChannel.class即可。

4.3.3 OIO – 舊的阻塞IO

Netty利用了SO_TIMEOUT這個Socket標誌,它指定了等待一個I/O操作完成的最大毫秒數。如果操作在指定的時間間隔內沒有完成,則將會拋出一個SocketTimeout Exception。Netty將捕獲這個異常並繼續處理循環。在EventLoop下一次運行時,它將再次嘗試。這實際上也是類似於Netty這樣的異步框架能夠支持OIO的唯一方式。

在這裏插入圖片描述

4.3.4 用於JVM內容通信的Local傳輸

在這個傳輸中,和服務器Channel 相關聯的SocketAddress 並沒有綁定物理網絡地址;相反,只要服務器還在運行,它就會被存儲在註冊表裏,並在Channel 關閉時註銷。因爲這個傳輸並不接受真正的網絡流量,所以它並不能夠和其他傳輸實現進行互操作。因此,客戶端希望連接到(在同一個JVM 中)使用了這個傳輸的服務器端時也必須使用它。除了這個限制,它的使用方式和其他的傳輸一模一樣。

4.3.5 Embedded傳輸

Netty提供了一種額外的傳輸,使得你可以將一組CHannelHandler作爲幫助器類嵌入到其他的ChannelHandler內部、通過這種方式,你將可以擴展一個ChannelHandler的功能,而又不需要修改內部的代碼。
Embedded傳輸的關鍵是一個被稱爲EmbeddedChannel的具體Channel實現。

下一章,介紹ByteBuf和ButeBufHolder-Netty的數據容器。

最後

如果你覺得寫的還不錯,就關注下公衆號唄,關注後,有點小禮物回贈給你。
你可以獲得5000+電子書,java,springCloud,adroid,python等各種視頻教程,IT類經典書籍,各種軟件的安裝及破解教程。
希望一塊學習,一塊進步!
在這裏插入圖片描述

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