本文将为您提供关于JavaNIO-Channel的详细介绍,我们还将为您解释javaniochannels.overlanp的相关知识,同时,我们还将为您提供关于03.JavaNIOChannel通道
本文将为您提供关于Java NIO - Channel的详细介绍,我们还将为您解释javaniochannels.overlanp的相关知识,同时,我们还将为您提供关于03. Java NIO Channel 通道、06. Java NIO Channel to Channel Transfers 通道传输接口、3,Java NIO Channel、5. 彤哥说 netty 系列之 Java NIO 核心组件之 Channel的实用信息。
本文目录一览:- Java NIO - Channel(javaniochannels.overlanp)
- 03. Java NIO Channel 通道
- 06. Java NIO Channel to Channel Transfers 通道传输接口
- 3,Java NIO Channel
- 5. 彤哥说 netty 系列之 Java NIO 核心组件之 Channel
Java NIO - Channel(javaniochannels.overlanp)
前言
上文讲到Java NIO
一些基本概念。在标准的IO
中,都是基于字节流/字符流进行数据操作的,而在NIO
中则是是基于Channel
和Buffer
进行操作,其中的Channel
的虽然模拟了流的概念,实则大不相同。
本文将详细阐述NIO
中的通道Channel
的概念和具体的用法。
Channel和Stream的区别
区别 | Stream | Channel |
---|---|---|
是否支持异步 | 不支持 | 支持 |
是否支持双向数据传输 | 不支持,只能单向 | 支持,既可以从通道读取数据,也可以向通道写入数据 |
是否结合Buffer使用 | 不 | 必须结合Buffer使用 |
性能 | 较低 | 较高 |
Channel
用于在字节缓冲区和位于通道另一侧的服务(通常是文件或者套接字)之间以便有效的进行数据传输。借助通道,可以用最小的总开销来访问操作系统本身的I/O
服务。
需要注意的是Channel必须结合Buffer使用,应用程序不能直接向通道中读/写数据,也就是缓冲区充当着应用程序和通道数据流动的转换的角色。
正文
Channel的源码
查看Channel
的源码。所有的接口都实现于Channel
接口,从接口上来看,所有的通道都有这两种操作:检查通道的开启状态和关闭通道。
1 |
public interface Channel extends Closeable { |
Channel的分类
广义上来说通道可以被分为两类:文件I/O
和网络I/O
,也就是文件通道和套接字通道。如果分的更细致一点则是:
- FileChannel:从文件读写数据;
-
SocketChannel:通过
TCP
读写网络数据; -
ServerSocketChannel:可以监听新进来的
TCP
连接,并对每个链接创建对应的SocketChannel
; -
DatagramChannel:通过
UDP
读写网络中的数据。
Channel的特性
单向or双向@H_301_233@
通道既可以是单向的也可以是双向的。只实现ReadableByteChannel
接口中的read()
方法或者只实现WriteableByteChannel
接口中的write()
方法的通道皆为单向通道,同时实现ReadableByteChannel
和WriteableByteChannel
为双向通道,比如ByteChannel
。
1 |
public interface ByteChannel extends ReadableByteChannel,WritableByteChannel { |
对于Socket
通道来说,它们一直是双向的,而对于FileChannel
来说,它同样实现了ByteChannel
,但是通过FileInputStream
的getChannel()
获取的FileChannel
只具有文件的只读权限。
注意:调用FileChannel的write()方法会抛出了NonWriteChannelException异常。
阻塞or非阻塞@H_301_233@
通道的工作模式有两种:阻塞或非阻塞。在非阻塞模式下,调用的线程不会休眠,请求的操作会立刻返回结果;在阻塞模式下,调用的线程会产生休眠。
除FileChannel
不能运行在非阻塞模式下,其余的通道都可阻塞运行也可以以非阻塞的方式运行。
另外从SelectableChannel
引申出的类可以和支持有条件选择的Selector
结合使用,进而充分利用多路复用的I/O
(Multiplexed I/O
)来提高性能。
SelectableChannel
的源码中有以下几个抽象方法,可以看出支持配置两种工作模式:
1 |
public abstract class SelectableChannel extends AbstractInterruptibleChannel implements Channel { |
对于Socket
通道类来说,通常与Selector
共同使用以提高性能。需要注意的是通道不能被同时使用,一个打开的通道代表着与一个特定I/O
服务进行连接并封装了该连接的状态,通道一旦关闭,该连接便会断开。
通道的close()
比较特殊,无论在通道时在阻塞模式下还是非阻塞模式下,由于close()
方法的调用而导致底层I/O
的关闭都可能会造成线程的暂时阻塞。在一个已关闭的通道上调用close()
并没有任何意义,只会立即返回。
Channel的实战
对于Socket通道来说存在直接创建新Socket通道的方法,而对于文件通道来说,升级之后的FileInputStream、FileOutputStream和RandomAccessFile提供了getChannel()方法来获取通道。
FileChannel@H_301_233@
Java NIO
中的FileChannel
是一个连接到文件的通道,可以通过文件通道读写文件。文件通道总是阻塞式的,因此FileChannel无法设置为非阻塞模式。
文件读写
(一). 文件写操作:
1 |
public static void testWriteOnFileChannel() { |
(二). 文件读操作:
1 |
public static void testReadOnFileChannel() { |
文件读写测试:
1 |
public static void main(String[] args) { |
测试结果:
transferFrom和transferTo
(一). transferFrom()的使用
FileChannel
的transferFrom()
方法可以将数据从源通道传输到FileChannel
中。下面是一个简单的例子:
1 |
public static void testTransferFrom(){ |
(二). transferTo()的使用
transferTo()
方法将数据从FileChannel
传输到目标channel
中。下面是一个简单的例子:
1 |
public static void testTransferTo() { |
ServerSocketChannel@H_301_233@
Java NIO
中的ServerSocketChannel
是一个可以监听新进来的TCP连接的通道。它类似ServerSocket
,要注意的是和DatagramChannel
和SocketChannel
不同,ServerSocketChannel
本身不具备传输数据的能力,而只是负责监听传入的连接和创建新的SocketChannel
。
ServerSocketChannel的用法
(一). 创建ServerSocketChannel
通过ServerSocketChannel.open()
方法来创建一个新的ServerSocketChannel
对象,该对象关联了一个未绑定ServerSocket
的通道。通过调用该对象上的socket()
方法可以获取与之关联的ServerSocket
。
1 |
ServerSocketChannel socketChannel = ServerSocketChannel.open(); |
(二). 为ServerSocketChannel绑定监听端口号
在JDK 1.7
之前,ServerSocketChannel
没有bind()
方法,因此需要通过他关联的的socket
对象的socket()
来绑定。
1 |
// JDK1.7之前 |
从JDK1.7
及以后,可以直接通过ServerSocketChannel
的bind()
方法来绑定端口号。
1 |
// JDK1.7之后 |
(三). 设置ServerSocketChannel
的工作模式
ServerSocketChannel
底层默认采用阻塞的工作模式,它提供了一个configureBlocking()
方法,允许配置ServerSocketChannel
以非阻塞方式运行。
1 |
// 设置为非阻塞模式 |
进一步查看configureBlocking
源码如下:
1 |
public final SelectableChannel configureBlocking(boolean block) throws IOException { |
Javadoc解释configureBlocking()方法用于调整底层通道的工作模式,即阻塞和非阻塞,默认是阻塞工作模式。
如果block设置为true,直接返回当前的阻塞式的通道;如果block设置为false,configureBlocking()方法会调用implConfigureBlocking()方法。这里implConfigureBlocking()是由ServerSocketChannelImpl
实现,最终调用了IoUtil中的native方法configureBlocking()。
(四). 监听新进来的连接
通过ServerSocketChannel.accept()
方法监听新进来的连接,这里需要根据configureBlocking()
的配置区分两种工作模式的使用:
- 在阻塞模式下,当
accept()
方法返回的时候,它返回一个包含新连接的SocketChannel
,否则accept()
方法会一直阻塞到有新连接到达。 - 在非阻塞模式下,在没有新连接的情况下,
accept()
会立即返回null
,该模式下通常不会仅仅监听一个连接,因此需在while
循环中调用accept()
方法.
阻塞模式:
1 |
while(true) { |
非阻塞模式:
1 |
while(true) { |
(五). 关闭ServerSocketChannel
通过调用ServerSocketChannel.close()
方法来关闭ServerSocketChannel
。
1 |
serverSocketChannel.close(); |
ServerSocketChannel的完整示例
(一). 阻塞模式
代码示例:
1 |
public static void blockingTest() throws IOException { |
运行结果:
(二). 非阻塞模式
代码示例:
1 |
public static void nonBlockingTest() throws IOException { |
运行结果:
SocketChannel@H_301_233@
Java NIO
中的SocketChannel
是一个连接到TCP
网络套接字的通道,它是Socket
类的对等类。
通常SocketChannel
在客户端向服务器发起连接请求,每个SocketChannel
对象创建时都关联一个对等的Socket
对象。同样SocketChannel
也可以运行在非阻塞模式下。
SocketChannel的用法
SocketChannel
创建的方式有两种:
- 客户端主动创建:客户端打开一个
SocketChannel
并连接到某台服务器上; - 服务端被动创建:一个新连接到达
ServerSocketChannel
时,服务端会创建一个SocketChannel
。
(一). 创建SocketChannel
通过SocketChannel
的静态方法open()
创建SocketChannel
对象。此时通道虽然打开,但并未建立连接。此时如果进行I/O
操作会抛出NotYetConnectedException
异常。
1 |
SocketChannel socketChannel = SocketChannel.open(); |
(二). 连接指定服务器
通过SocketChannel
对象的connect()
连接指定地址。该通道一旦连接,将保持连接状态直到被关闭。可通过isConnected()
来确定某个SocketChannel
当前是否已连接。
- 阻塞模式:
如果在客户端的SocketChannel
阻塞模式下,即服务器端的ServerSocketChannel
也为阻塞模式:
1 |
socketChannel.connect(new InetSocketAddress("127.0.0.1",25000)); |
- 非阻塞模式:
两点需要注意:其一,SocketChannel需要通过configureBlocking()设置为非阻塞模式;其二,非阻塞模式下,connect()方法调用后会异步返回,为了确定连接是否建立,需要调用finishConnect()的方法。
1 |
socketChannel.configureBlocking(false); |
(三). 从SocketChannel读数据
利用SocketChannel
对象的read()
方法将数据从SocketChannel
读取到Buffer
。
1 |
ByteBuffer byteBuffer = ByteBuffer.allocate(1024); |
(四). 向SocketChannel写数据
利用SocketChannel
对象的write()
将Buffer
的数据写入SocketChannel
。
1 |
ByteBuffer byteBuffer = ByteBuffer.allocate(1024); |
(五). 关闭SocketChannel
利用SocketChannel
对象的close()
方法关闭SocketChannel
。
1 |
socketChannel.close(); |
SocketChannel的完整示例
(一). 阻塞模式
代码示例:
1 |
public static void blockingWrite() throws Exception { |
服务端打印结果:
(一). 非阻塞模式
代码示例:
1 |
public static void nonBlockingWrite() throws Exception { |
服务端打印结果:
DatagramChannel@H_301_233@
Java NIO
中的DatagramChannel
是一个能收发UDP
包的通道,其底层实现为DatagramSocket + Selector
。DatagramChannel
可以调用socket()
方法获取对等DatagramSocket
对象。DatagramChannel
对象既可以充当服务端(监听者),也可以充当客户端(发送者)。如果需要新创建的通道负责监听,那么该通道必须绑定一个端口(或端口组):
DatagramChannel的完整示例
数据报发送方:
1 |
public static void main(String[] args) throws Exception { |
数据报接收方:
1 |
public static void main(String[] args) throws Exception { |
先运行DatagramChannelReceiveTest
,再运行DatagramChannelSendTest
,观察控制台输出:
数据报发送方:
数据报接收方:
工具类Channels
NIO
通道提供了一个便捷的通道类Channels
,其中定义了几种静态的工厂方法以简化通道和流转换。其中常用的方法如下:
方法 | 返回 | 描述 |
---|---|---|
newChannel(InputStream in) | ReadableByteChannel | 返回一个将从给定的输入流读取数据的通道。 |
newChannel(OutputStream out) | WritableByteChannel | 返回一个将向给定的输出流写入数据的通道。 |
newInputStream(ReadableByteChannel ch) | InputStream | 返回一个将从给定的通道读取字节的流。 |
newOutputStream(WritableByteChannel ch) | OutputStream | 返回一个将向给定的通道写入字节的流。 |
newReader(ReadableByteChannel ch,CharsetDecoder dec,int minBufferCap) | Reader | 返回一个reader,它将从给定的通道读取字节并依据提供的字符集名称对读取到的字节进行解码。 |
newReader(ReadableByteChannel ch,String csName) | Reader | 返回一个reader,它将从给定的通道读取字节并依据提供的字符集名称将读取到的字节解码成字符。 |
newWriter(WritableByteChannel ch,CharsetEncoder dec,int minBufferCap) | Writer | 返回一个writer,它将使用提供的字符集名称对字符编码并写到给定的通道中。 |
newWriter(WritableByteChannel ch,String csName) | Writer | 返回一个writer,它将依据提供的字符集名称对字符编码并写到给定的通道中。 |
总结
本文针对NIO
中的通道的做了详细的介绍,对于文件通道FileChannel
,网络通道SocketChannel
、ServerSocketChannel
和DatagramChannel
进行了实战演示。
篇幅较长,可见NIO
提供的原生的通道API
在使用上并不是太容易。
03. Java NIO Channel 通道
Java NIO Channel通道和流非常相似,主要有以下几点区别:
- 通道可以度也可以写,流一般来说是单向的(只能读或者写)。
- 通达可以异步读写。
- 通道总是基于缓冲区Buffer来读写。
正如上面提到的,我们可以从通道中读取数据,写入到buffer;也可以中buffer内读数据,写入到通道中。下面有个示意图:
Java NIO: Channels read data into Buffers, and Buffers write data into Channels
Channel的实现(Channel Implementations)
下面列出Java NIO中最重要的集中Channel的实现:
- FileChannel
- DatagramChannel
- SocketChannel
- ServerSocketChannel
FileChannel用于文件的数据读写。 DatagramChannel用于UDP的数据读写。 SocketChannel用于TCP的数据读写。 ServerSocketChannel允许我们监听TCP链接请求,每个请求会创建会一个SocketChannel.
Channel的基础示例(Basic Channel Example)
这有一个利用FileChannel读取数据到Buffer的例子:
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf);
while (bytesRead != -1) {
System.out.println("Read " + bytesRead);
buf.flip();
while(buf.hasRemaining()){
System.out.print((char) buf.get());
}
buf.clear();
bytesRead = inChannel.read(buf);
}
aFile.close();
注意buf.flip()的调用。首先把数据读取到Buffer中,然后调用flip()方法。接着再把数据读取出来。在后续的章节中我们还会讲解先关知识。
06. Java NIO Channel to Channel Transfers 通道传输接口
在Java NIO中如果一个channel是FileChannel类型的,那么他可以直接把数据传输到另一个channel。逐个特性得益于FileChannel包含的transferTo和transferFrom两个方法。
transferFrom()
FileChannel.transferFrom方法把数据从通道源传输到FileChannel:
RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
FileChannel fromChannel = fromFile.getChannel();
RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
FileChannel toChannel = toFile.getChannel();
long position = 0;
long count = fromChannel.size();
toChannel.transferFrom(fromChannel, position, count);
transferFrom的参数position和count表示目标文件的写入位置和最多写入的数据量。如果通道源的数据小于count那么就传实际有的数据量。 另外,有些SocketChannel的实现在传输时只会传输哪些处于就绪状态的数据,即使SocketChannel后续会有更多可用数据。因此,这个传输过程可能不会传输整个的数据。
transferTo()
transferTo方法把FileChannel数据传输到另一个channel,下面是案例:
RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
FileChannel fromChannel = fromFile.getChannel();
RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
FileChannel toChannel = toFile.getChannel();
long position = 0;
long count = fromChannel.size();
fromChannel.transferTo(position, count, toChannel);
这段代码和之前介绍transfer时的代码非常相似,区别只在于调用方法的是哪个FileChannel.
SocketChannel的问题也存在与transferTo.SocketChannel的实现可能只在发送的buffer填充满后才发送,并结束。
3,Java NIO Channel
Java NIO Channels are similar to streams with a few differences:
You can both read and write to a Channels. Streams are typically one-way (read or write).
Channels can be read and written asynchronously.
Channels always read to, or write from, a Buffer.
As mentioned above, you read data from a channel into a buffer, and write data from a buffer into a channel. Here is an illustration of that:
Java NIO: Channels read data into Buffers, and Buffers write data into Channels
Channel Implementations
Here are the most important Channel implementations in Java NIO:
FileChannel
DatagramChannel
SocketChannel
ServerSocketChannel
The FileChannel
reads data from and to files.
The DatagramChannel
can read and write data over the network via UDP.
The SocketChannel
can read and write data over the network via TCP.
The ServerSocketChannel
allows you to listen for incoming TCP connections, like a web server does. For each incoming connection a SocketChannel
is created.
Basic Channel Example
Here is a basic example that uses a FileChannel
to read some data into a Buffer
:
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf);
while (bytesRead != -1) {
System.out.println("Read " + bytesRead);
buf.flip();
while(buf.hasRemaining()){
System.out.print((char) buf.get());
}
buf.clear();
bytesRead = inChannel.read(buf);
}
aFile.close();
Notice the buf.flip()
call. First you read into a Buffer. Then you flip it. Then you read out of it. I''ll get into more detail about that in the next text about Buffer
''s.
5. 彤哥说 netty 系列之 Java NIO 核心组件之 Channel
你好,我是彤哥,本篇是 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
最后,也欢迎来我的工从号彤哥读源码系统地学习源码 & 架构的知识。
今天关于Java NIO - Channel和javaniochannels.overlanp的讲解已经结束,谢谢您的阅读,如果想了解更多关于03. Java NIO Channel 通道、06. Java NIO Channel to Channel Transfers 通道传输接口、3,Java NIO Channel、5. 彤哥说 netty 系列之 Java NIO 核心组件之 Channel的相关知识,请在本站搜索。
本文标签: