1. 前言
1.1 什么是Netty
Netty是一个异步的事件驱动的网络应用程序框架,它提供了异步的、事件驱动的网络应用程序框架和工具,用于快速开发高性能的网络应用程序。
Netty提供了对NIO的抽象,提供了对TCP和UDP的支持,同时也支持基于SSL/TLS的安全传输。Netty提供了对HTTP、HTTP2、WebSocket和文件传输的支持。Netty可以帮助我们快速开发高性能的网络应用程序。
1.2 Netty的特点
- 高性能:Netty的性能比JDK的NIO框架要高。Netty的核心是Epoll,而非NIO。
- 高可靠性:Netty提供了一种基于事件和回调机制的高可靠性的TCP和UDP协议栈。
- 功能丰富:Netty提供了对HTTP、HTTP2、WebSocket和文件传输的支持。
- 易于使用:Netty提供了易于使用的API,同时提供了对线程模型的抽
2. NIO 编程
学习Netty之前,可以先了解一下Java中NIO的一些概念和基础用法,可以更好的帮助我们学习Netty框架,以及Netty是怎么样封装NIO的。
2.1 其他IO模型
- BIO(Blocking I/O):同步阻塞I/O,数据的读取写入必须阻塞在一个线程内等待其完成。是一个比较传统的通信方式,模式简单,使用方便。但并发处理能力低,通信耗时,依赖网速。
- NIO(New I/O):同步非阻塞I/O,在Java1.4中引入了NIO,NIO 与原来的 I/O 有同样的作用和目的, 他们之间最重要的区别是数据打包和传输的方式。原来的 I/O 以流的方式处理数据,而 NIO 以块的方式处理数据。
面向流的 I/O 系统一次一个字节地处理数据。一个输入流产生一个字节的数据,一个输出流消费一个字节的数据。
面向块的 I/O 系统以块的形式处理数据。每一个操作都在一步中产生或者消费一个数据块。按块处理数据比按(流式的)字节处理数据要快得多。但是面向块的 I/O - 缺少一些面向流的 I/O 所具有的优雅性和简单性。 - AIO(Asynchronous I/O):异步非阻塞I/O,在Java7中,提供了AsynchronousChannel,可以让一个线程从而不阻塞来处理多个任务。是一种非阻塞异步的通信模式。在NIO的基础上引入了新的异步通道的概念,并提供了异步文件通道和异步套接字通道的实现。
2.2 什么是NIO
NIO(Non-blocking I/O)是一种异步非阻塞的I/O模型,在Java中,NIO的实现是通过Channel(通道)和Buffer(缓冲区)来完成的。
2.3 NIO的特点
- 非阻塞式:NIO使用非阻塞I/O操作,可以在等待I/O完成时继续执行其他任务,而不需要一直等待。这允许程序能够更高效地利用CPU资源。
- 通道和缓冲区:NIO引入了通道(Channel)和缓冲区(Buffer)的概念。通道是数据源和目标的抽象,可以进行读取和写入操作。缓冲区是用于存储数据的对象,通过缓冲区进行数据的读取和写入。
- 多路复用:NIO提供了选择器(Selector)的概念,通过选择器可以同时监听多个通道的事件,实现多路复用。这样可以使用较少的线程来处理多个连接,提高系统的扩展性和性能。
- 内存映射文件:NIO提供了内存映射文件(MappedByteBuffer)的功能,可以将文件直接映射到内存中。这样可以在文件和内存之间快速地进行数据传输,提高I/O的效率。
- 异步I/O:NIO还支持异步I/O操作,通过使用Future、CompletionHandler等机制,可以在I/O操作完成后得到通知,而不需要一直等待。这使得程序可以并发地执行其他任务,提高系统的并发性能。
3. NIO 的基本开发方式
3.1 通道(Channel)
通道(Channel)是对原IO中的流进行了抽象,Channel本身是一个接口,它负责缓冲区的数据传输。
Channel分为三种:
- FileChannel:用于文件操作,可以从文件中读取数据,也可以向文件中写入数据。
- SocketChannel:用于TCP网络编程,可以向TCP服务器发送数据,也可以从TCP服务器读取数据。
- DatagramChannel:用于UDP套接字的读写操作。
以 FileChannel 为例
public static void main(String[] args) throws IOException {
//1 创建Channel通道 FileChannel
FileChannel channel = new FileInputStream("file.text").getChannel();
//2 创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(10);
while (true) {
//3把通道内获取的文件数据,写入缓冲区
int read = channel.read(buffer);
if (read == -1) break;
//4.程序读取buffer内容,后续的操作。设置buffer为读模式。
buffer.flip();
//5.循环读取缓冲区中的数据
while (buffer.hasRemaining()) {
byte b = buffer.get();
System.out.println("(char)b = " + (char) b);
}
//6. 设置buffer的写模式
buffer.clear();
}
}
3.2 缓冲区(Buffer)
缓冲区(Buffer)是一块连续的内存区域,用于在通道(Channel)和应用程序之间进行数据传输。缓冲区提供了一种有效地存储和操作数据的方式。
以下是关于缓冲区的一些特点和用法:
- 内存结构:缓冲区在内存中是一个数组,可以存储多种基本数据类型的数据,如字节、字符、整数等。缓冲区还包含了一些用于管理数据的指针(position、limit、capacity)。
- 读写操作:缓冲区提供了读取和写入数据的方法。可以通过
put()方法将数据写入缓冲区,通过get()方法从缓冲区读取数据。读写操作都是相对于当前位置(position)进行的。 - 位置(position):位置表示缓冲区中下一个要读取或写入数据的索引。读取数据后,位置会自动向后移动,写入数据后,位置也会自动向后移动。可以使用
position()方法获取当前位置,使用position(int newPosition)方法设置新的位置。 - 限制(limit):限制表示缓冲区中有效数据的末尾索引。在写入数据时,限制可以设置为缓冲区的容量,表示缓冲区已满。在读取数据时,限制可以设置为当前位置,表示缓冲区中有效数据的末尾。可以使用
limit()方法获取当前限制,使用limit(int newLimit)方法设置新的限制。 - 容量(capacity):容量表示缓冲区的最大容量,即可以存储的最大数据量。可以使用
capacity()方法获取当前容量。 - 翻转(flip):翻转是将缓冲区从写模式切换到读模式的操作。在翻转之前,会将限制设置为当前位置,位置设置为0,以便从缓冲区的开头开始读取数据。
- 清空(clear):清空是将缓冲区从读模式切换到写模式的操作。在清空之前,会将限制设置为容量,位置设置为0,以便从缓冲区的开头开始写入数据。
- 可读可写:缓冲区可以用于读取数据到通道,也可以用于将数据从通道写入缓冲区。
常见Buffer:
- ByteBuffer (常用)
- CharBuffer (常用)
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
- MappedByteBuffer (内存映射文件)
3.3 选择器(Selector)
选择器(Selector)是一种用于多路复用的机制,可以同时监听多个通道(Channel)的事件。选择器允许单个线程处理多个通道的I/O操作,从而提高系统的扩展性和性能。以下是关于选择器的一些特点和用法:
- 监听多个通道:选择器可以注册和管理多个通道,如SocketChannel、ServerSocketChannel等。通过选择器,可以监听通道上的事件,例如读取可用数据、连接建立等。
- 非阻塞操作:选择器使用非阻塞模式进行操作。在非阻塞模式下,可以立即返回就绪的通道,而不需要一直等待。这使得程序可以同时处理多个通道的I/O操作,提高系统的并发性能。
- 事件类型:选择器监听的事件类型包括读取事件(SelectionKey.OP_READ)、写入事件(SelectionKey.OP_WRITE)、连接事件(SelectionKey.OP_CONNECT)和接受连接事件(SelectionKey.OP_ACCEPT)等。
- SelectionKey:每个注册到选择器的通道都会对应一个SelectionKey对象,它表示了通道和选择器之间的关联关系。SelectionKey包含了通道和选择器的状态信息,可以用于识别就绪的事件和操作对应的通道。
- 就绪集合:选择器会维护一个就绪集合(selected set),其中包含了就绪的通道和对应的事件类型。通过选择器的select()方法可以获取就绪集合,并进行相应的处理。
- 轮询就绪事件:选择器的select()方法是阻塞的,直到至少一个通道就绪或超时。可以使用selectNow()方法进行非阻塞的轮询,或者使用带有超时参数的select(long timeout)方法进行有限时间的阻塞。
- 取消注册:通道可以通过调用SelectionKey的cancel()方法来取消在选择器上的注册。这样可以停止监听该通道的事件。
总结
至此,关于NIO的三个核心 缓冲区(Buffer)、选择器(Selector)和通道(Channel)就讲完了,它们共同构成了高性能I/O操作的基础。下一篇再详细展开这三个核心的使用和关键点,怎么使用NIO编程,以及使用原生NIO编程需要注意的点。
感谢观看,您的点赞是对我最好的支持。
文章参考:孙帅suns Netty应用