Netty学习总结之NIO的核心组件

本文介绍了Netty中的Channel、Buffer和Selector概念及其工作原理。详细解释了不同类型的Channel,如SocketChannel和ServerSocketChannel,以及Buffer如何与Channel交互进行数据传输。此外还探讨了Selector在多路复用中的作用。

目录

Channel

Buffer

Selector


Channel

Channel 是一种 IO 操作的连接,它代表的是到实体的开放连接,这个实体可以是硬件设备、文件、网络套接字或者可执行 IO 操作(比如读、写)的程序组件。这有点类似与 Linux 系统中:一切皆文件,可以将 Netty 的 Channel 看作到文件的连接,并可以通过 IO 来操作文件。

 针对不同的文件类型又衍生出了不同类型的 Channel

  • SocketChannel:用于 TCP 协议,客户端与服务端之间的 Channel
  • ServerSocketChannel:用于 TCP 协议,仅用于服务端的 Channel
  • FileChannel:操作普通文件
  • DatagramChannel:用于 UDP 协议

ServerSocketChannel 和 SocketChannel 是专门用于 TCP 协议中的。

ServerSocketChannel 是一种服务端的 Channel,只能用在服务端,可以看作是到网卡的一种 Channel,它监听着网卡的某个端口。

SocketChannel 是一种客户端与服务端之间的 Channel,客户端连接到服务器的网卡之后,被服务端的 Channel 监听到,然后与客户端之间建立一个 Channel,这个 Channel 就是 SocketChannel。

不过 Channel 需要和 Buffer 组件一起使用,接下来就看一看什么是Buffer。

Buffer

Buffer 是一个容器,用于存放特定基本类型的数据。Buffer容器它是线性的,有限的序列,元素是某种基本类型的数据。Buffer容器主要有三个属性:capacity、limit、position(容量、极限、位置)。

capacity,比较好理解,Buffer 的容量,即能够容纳多少数据。

limit,这个稍微费脑一些,表示的是最大可写或者最大可读的数据。

position,这个就更难理解一些,表示下一次可使用的位置,针对读模式表示下一个可读的位置,针对写模式表示下一个可写的位置。

那为什么 Buffer 需要和 Channel 一起使用呢?这里打个比方,比如需要运送一批生鲜从 A 地到 B 地,将每只生鲜当作数据,A 地到 B 地之间的公路当作 Channel 通道,运货工具大货车、小货车看作成为不同的网络协议,大货车可以看成TCP、小货车可以看成UDP,而装生鲜的箱子可以看成Buffer,所以只有将箱子(Buffer)和公路(Channel)一起使用,并且通过货车(Protocol)协议才可以达到良好的效果。

Buffer的几个重要的方法:

分配一个 Buffer:allocate ()

写入数据:buf.put () 或者 channel.read (buf),read 为 read to 的意思,从 channel 读出并写入 buffer

切换为读模式:buf.flip (),调换这个buffer的位置,并且设置当前位置为0,将缓存字节数组的下标调增为0,以便从头读取缓存区中的所有数据

读取数据:buf.read () 或者 channel.write (buf),write 为 write from 的意思,从 buffer 读出并写入 channel

重新读取或重新写入:rewind (),重置 position 为 0,limit 和 capacity 保持不变,可以重新读取或重新写入数据

清空数据:buf.clear (),清空所有数据

压缩数据:buf.compact (),清除已读取的数据,并将未读取的数据往前移

Selector

Selector 和 Channel 之间有什么关系?Selector 和 Channel 是一对多的关系,一个 Selector 可以为多个 Channel 服务,监听它们准备好的事件。Selector 就像饭店中的服务员一样,一个服务员是可以服务于多位顾客的,时刻监听着顾客的吩咐。

 创建 selector 并将 相关事件注册到 selector 上

// 创建选择器
Selector selector = Selector.open();

// 设置为非阻塞状态
serverSocketChannel.configureBlocking(false);
// 将accept事件绑定到selector上
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

什么是事件?事件是 Channel 感兴趣的事情,比如读事件、写事件等等,在 Java 中,定义了四种事件,位于 SelectionKey 这个类中:

读事件:SelectionKey.OP_READ = 1 << 0 = 0000 0001

写事件:SelectionKey.OP_WRITE = 1 << 2 = 0000 0100

连接事件:SelectionKey.OP_CONNECT = 1 << 3 = 0000 1000

接受连接事件:SelectionKey.OP_ACCEPT = 1 << 4 = 0001 0000

Channel、Buffer、Selector共同使用

public static void main(String[] args) throws IOException {
        // 创建一个selector
        Selector selector = Selector.open();
        // 创建一个serverSocketChannel
        ServerSocketChannel channel = ServerSocketChannel.open();
        // 绑定8080端口
        channel.bind(new InetSocketAddress(8002));
        // 设置为非阻塞状态
        channel.configureBlocking(false);
        // 将channel注册到selector上,并注册Accept事件
        channel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("server start");
 
        while (true) {
            // 阻塞在select上(第一阶段阻塞)
            selector.select();
 
            // 如果使用的是select(timeout)或selectNow()需要判断返回值是否大于0
 
            // 有就绪的Channel
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            // 遍历selectKeys
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                // 如果是accept事件
                if (selectionKey.isAcceptable()) {
                    // 强制转换为ServerSocketChannel
                    ServerSocketChannel ssc = (ServerSocketChannel) selectionKey.channel();
                    SocketChannel socketChannel = ssc.accept();
                    System.out.println("accept new conn: " + socketChannel.getRemoteAddress());
                    socketChannel.configureBlocking(false);
                    // 将SocketChannel注册到Selector上,并注册读事件
                    socketChannel.register(selector, SelectionKey.OP_READ);
                } else if (selectionKey.isReadable()) {
                    // 如果是读取事件
                    // 强制转换为SocketChannel
                    SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                    // 创建Buffer用于读取数据
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    // 将数据读入到buffer中(第二阶段阻塞)
                    int length = socketChannel.read(buffer);
                    if (length > 0) {
                        buffer.flip();
                        byte[] bytes = new byte[buffer.remaining()];
                        // 将数据读入到byte数组中
                        buffer.get(bytes);
 
                        // 换行符会跟着消息一起传过来
                        String content = new String(bytes, "UTF-8").replace("\r\n", "");
                        System.out.println("receive msg: " + content);
                    }
                }
                iterator.remove();
            }
        }
    }

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lw中

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值