你好, 我是彤哥, 本篇是 netty 系列的第五篇.
简介
上一章我们一起学习了如何使用 Java 原生 NIO 实现群聊系统, 这章我们一起来看看 Java NIO 的核心组件之一 --Channel.
思维转变
首先, 我想说的最重要的一个点是, 学习 NIO 思维一定要从 BIO 那种一个连接一个线程的模式转变成多个连接 (Channel) 共用一个线程来处理的这种思维.
1 个 Connection = 1 个 Socket = 1 个 Channel, 这几个概念可以看作是等价的, 都表示一个连接, 只不过是用在不同的场景中.
如果单从阻塞 / 非阻塞的角度来看的话, IO 可以分成两大类, 一类是 Blocking IO, 一类是 Non-blocking IO, 像 IO 五种模型中的后四种其实都可以看作是非阻塞型 IO, 只是各自使用的手段不相同罢了.
在 Java 中, 我们说的非阻塞 IO 或者说 NIO(New IO)主要是指多路复用 IO, 底层可以使用 select/poll/epoll 等技术实现.
另外, 在 Java1.7 的时候引入了 NIO2, 这个主要是指异步 IO 模型, 也就是我们常说的 AIO, 底层完全使用异步回调的方式来实现.
但是, 由于 AIO 这项技术在 Linux 操作系统上还不太成熟, 所以我们通常也不会说太多关于这方面的内容.
在后面我们学习 Netty 的时候会再次讲到这三种 IO 模型, 可以看到 Netty 是完全支持三种 IO 的, 但是它把 OIO(BIO)和 AIO 都给 deprecated 了, 也进一步说明了 AIO 的不成熟性.
好了, 扯了一下思维转变的问题, 下面正式进入今天的内容 --Java NIO 核心组件之 Channel.
Channel
概念
我们先来看看 Java 中对于 Channel 的定义, 位于 java.nio.channels.Channel 类的注释上:
A nexus for I/O operations.
本文来源工从号彤哥读源码
A channel represents an open connection to an entity such as a hardware device, a file, a network socket, or a program component that is capable of performing one or more distinct I/O operations, for example reading or writing.
第一句, 它是 IO 操作的一种连接.
nexus, the means of connection between things linked in series.
第二句, Channel 代表到实体的开放连接, 这个实体可以是硬件, 文件, 网络套接字, 或者程序组件, 并且可以执行一个或多个不同的 IO 操作, 例如, 读或写.
简单点讲, Channel 就是实体与实体之间的连接, 比如, 操作文件可以使用 FileChannel, 操作网络可以使用 SocketChannel 等.
与流的区别
BIO 是面向流 (Stream) 编程的, 流又分成 InputStream 和 OutputStream, 那么 Channel 和 Stream 有什么区别呢?
Channel 可以同时支持读和写, 而 Stream 只能支持单向的读或写(所以分成 InputStream 和 OutputStream)
Channel 支持异步读写, Stream 通常只支持同步
Channel 总是读向(read into)Buffer, 或者写自(write from)Buffer(有点绕, 以 Channel 为中心, 从 Channel 中读出数据到 Buffer, 从 Buffer 中往 Channel 写入数据)
实现方式
下面列举了 JDK 中比较重要的实现方式:
FileChannel: 操作文件
DatagramChannel:UDP 协议支持
SocketChannel:TCP 协议支持
ServerSocketChannel: 监听 TCP 协议 Accept 事件, 之后创建 SocketChannel
例子
- public class FileChannelTest {
- public static void main(String[] args) throws IOException {
- // 从文件获取一个 FileChannel
- FileChannel fileChannel = new RandomAccessFile("D:\\object.txt", "rw").getChannel();
- // 声明一个 Byte 类型的 Buffer
- ByteBuffer buffer = ByteBuffer.allocate(10);
- // 将 FileChannel 中的数据读出到 buffer 中,-1 表示读取完毕
- // buffer 默认为写模式, 本文来源工从号彤哥读源码
- // read()方法是相对 channel 而言的, 相对 buffer 就是写
- while ((fileChannel.read(buffer)) != -1) {
- // buffer 切换为读模式
- buffer.flip();
- // buffer 中是否有未读数据
- while (buffer.hasRemaining()) {
- // 未读数据的长度
- int remain = buffer.remaining();
- // 声明一个字节数组
- byte[] bytes = new byte[remain];
- // 将 buffer 中数据读出到字节数组中
- buffer.get(bytes);
- // 打印出来
- System.out.println(new String(bytes, StandardCharsets.UTF_8));
- }
- // 清空 buffer, 为下一次写入数据做准备
- // clear()会将 buffer 再次切换为写模式
- buffer.clear();
- }
- }
- }
可以看到, Channel 与 Buffer 是息息相关的. 注意这里的读写方法, 调用者是谁就以谁为核心, channel.read()就表示从 channel 读出数据, buffer.get()就表示从 buffer 读出数据, 这跟传统编程的角度不太一样的地方.
总结
今天我们学习了 Java NIO 核心组件之 Channel, 它与传统 BIO 中的流很类似但又有所区别, 且经常与 Buffer 联合起来使用, Buffer 又是什么呢? 请听下回分解.
参考
挺不错的一个网站:
http://tutorials.jenkov.com/java-nio/channels.html
最后, 也欢迎来我的工从号彤哥读源码系统地学习源码 & 架构的知识.
来源: https://www.cnblogs.com/tong-yuan/p/11968419.html