Netty入门指南之NIO Channel详解

作者简介:☕️大家好,我是Aomsir,一个爱折腾的开发者!
个人主页Aomsir_Spring5应用专栏,Netty应用专栏,RPC应用专栏-CSDN博客
当前专栏Netty应用专栏_Aomsir的博客-CSDN博客

参考文献

前言

从本篇文章开始,我们将深入学习 NIO(非阻塞I/O)编程的相关内容,从头到尾详尽分析,包括Selector和Reactor模型,这将为我们后续学习Netty等更高层次的网络编程库打下坚实的基础。NIO编程的核心元素主要包括ChannelBuffer

NIO编程使用Channel来进行通信。在服务端,我们还引入了Selector选择器,它帮助我们主动监控客户端的Channel,确保这些Channel能够正常通信(即正常连接且没有阻塞)。通过监控客户端的请求链路,Selector的作用是,一旦发现某个客户端阻塞,它可以将分配给该客户端的线程重新分配给其他可用客户端,这样一个线程就能为多个客户端提供服务。需要注意的是,每个客户端都拥有自己独立的Channel,不共享一个Channel。这篇文章将深入学习和理解Channel的概念,为后续的内容打下坚实的基础。

Channel

简介

Channel是一种用于IO通信的管道,类似于传统的InputStreamOutputStream。然而,与传统IO流不同,其中有输入流和输出流的方向性,NIO中的Channel是 无方向性。在传统IO开发中,为了读取文件并在JVM中进行操作后将结果写回文件,我们需要一个InputStream输入流将文件读入JVM,然后使用OutputStream输出流将结果写回文件。这些流是单向的,每个用于特定的目的。而在NIO中,Channel既可以用于读取数据,也可以用于写入数据,这为通用的双向数据传输提供了更大的灵活性
在这里插入图片描述

常见Channel

  • 文件IO操作
    • FileChannel:读和写文件中的数据
  • 网络IO操作
    • ServerSocketChannel:监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接创建一个SocketChannel
    • SocketChannel:通过TCP读写网络中的数据
    • DatagramChannel:通过UDP读写网络中的数据

Channel获取方式

  • 文件IO操作
    • FileInputStream/FileOutputStream获取
    • RandonAccessFile获取
  • 网络通信
    • Socket获取
    • ServerSocket获取
    • DatagramSocket获取
FileChannel channel = new FileInputStream(TestNIO1.class.getClassLoader().getResource("data.txt").getFile()).getChannel();

FileChannel channel = new RandomAccessFile("data.txt", "rw").getChannel();

Buffer简介

因在下一篇文章中,我们将详细讲解Buffer,因为它是NIO中的一个非常核心的概念。掌握好Buffer对于理解后续的内容,包括Netty,至关重要。

Buffer可以被看作是JVM内存中的一块区域,它类似于一个缓冲区。在传统的流通信中,我们通常使用字节数组来装载接收到的数据。Buffer也类似于这个字节数组,但不同之处在于它具有读写指针,用于标识内存中的数据是用于读取还是写入。这一特性使得Buffer非常适用于NIO中的数据处理

演示案例

IO流

如下是使用Stream流的方式的进行读操作

public class TestNIO1 {
    private static final Logger log = LoggerFactory.getLogger(TestNIO1.class);

    public static void main(String[] args) throws IOException {
        // 创建输入流
        InputStream inputStream = TestNIO1.class.getClassLoader().getResourceAsStream("data.txt");

        // 创建缓冲区
        byte[] bytes = new byte[1024];

        // 读取数据到缓冲区
        while (inputStream.read(bytes) != -1){
            String s = new String(bytes);
            log.info("s = {}", s);
        }
    }
}

Channel

错误案例

在下面的案例中,我们设置Buffer的大小为10个字节。但是,如果我们尝试读取一个13字节的数据流,那么后面的3个字节将一直保留在缓冲区中,无法读取。动态调整Buffer的大小可能不是一个明智的选择,因为这会引入复杂性。

为了解决这个问题,我们可以采用改进案例中的方法。不再固定Buffer的大小,而是使用循环不断读取,直到所有数据被消耗。这种方式可以有效地处理不定长度的数据,而不需要频繁地调整缓冲区大小

public class TestNIO1 {
    private static final Logger log = LoggerFactory.getLogger(TestNIO1.class);

    public static void main(String[] args) throws IOException {
       // 1.创建Channel通道 - FileChannel
       FileChannel channel = new FileInputStream("/Users/aomsir/MyStudyProject/Java/Netty/netty-basic-01/data.txt").getChannel();

       // 2.创建Buffer缓冲区,容量为10字节,具体根据文件编码来定
       ByteBuffer buffer = ByteBuffer.allocate(10);

       while (true) {
           // 3.把channel读取的数据放入buffer,读完以后返回的是-1
           int read = channel.read(buffer);
           if (-1 == read) {
               break;
           }

           // 4.设置buffer为读模式,代表程序从buffer中读取数据
           buffer.flip();

           // 5.循环读取缓冲区数据
           while (buffer.hasRemaining()) {
               byte b = buffer.get();
               System.out.println((char) b);
           }

           // 6.操作之后将buffer设置为写模式
           buffer.clear();
       }
    }
}

改进案例

改进案例非常简单,我们可以通过循环复用Buffer来处理未读取的数据。这意味着我们不需要不断调整Buffer的大小,而是在一个循环中不断读取数据,直到所有数据都被处理。这种方法能够有效地解决数据流长度不确定的情况,确保不会漏掉任何数据。同时上一个错误案例没有做异常处理,此改进版本做了异常处理。

public class TestNIO2 {
    private static final Logger log = LoggerFactory.getLogger(TestNIO2.class);

    public static void main(String[] args) {

        FileChannel channel = null;
        try {

            // 1.通过FileInputStream获取对应的FileChannel管道
            channel = new FileInputStream(TestNIO1.class.getClassLoader().getResource("data.txt").getFile()).getChannel();

            // 2.创建Buffer缓冲区,容量为10字节,具体根据文件编码来定
            ByteBuffer buffer = ByteBuffer.allocate(10);

            while (true) {
                // 3.让buffer从channel中读取数据,如果没有读到数据则返回-1
                int read = channel.read(buffer);
                if (-1 == read) {
                    break;
                }

                // 4.设置buffer为读模式,代表程序从buffer中读取数据
                buffer.flip();

                // 5.循环读取缓冲区数据
                while (buffer.hasRemaining()) {
                    byte b = buffer.get();
                    log.info("(char)b = {}", (char)b);
                }

                // 6.操作之后将buffer设置为写模式,方便下一次写入数据
                buffer.clear();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (channel != null) {
                try {
                    channel.close();
                } catch (IOException e) {
                    throw new RuntimeException();
                }
            }
        }
    }
}

注意

在Buffer中,通过使用flip()方法,我们可以切换为读操作的模式。这表示其他程序可以从Buffer中读取数据。另一方面,要切换为写操作模式,我们可以使用clear()方法。这表示后续的程序可以向Buffer中写入数据,或者从Channel中读取数据并放入Buffer中进行进一步处理。

总结

在本篇文章中,我们将深入研究NIO中的Channel,探讨其双向可读可写的特性,介绍一些常见的Channel类型以及它们的创建方式。我们还将详细演示FileChannel的使用,而在后续的内容中,我们将逐渐学习SocketChannel、ServerSocketChannel等更多内容。这些知识将有助于我们更好地理解和充分利用NIO中的通道,为后续的学习和应用奠定坚实的基础。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Aomsir

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

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

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

打赏作者

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

抵扣说明:

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

余额充值