type
status
date
urlname
summary
tags
category
icon
password
catalog
sort
在上一章NIO Channel篇中我们解释了它在NIO中的作用。Channel(通道)是一个核心概念,它提供了与IO设备(如文件、网络套接字等)进行数据传输的能力。下面我将从多个方面详细讲解NIO的Channel。Channel本身不直接存储数据,它通过与Buffer(缓冲区)的交互来实现数据的读写。数据首先被读入到Buffer中,然后再从Buffer中写入到Channel,或者从Channel读入到Buffer。
Channel的工作原理
  • 与Buffer的交互‌:Channel本身不直接存储数据,它通过与Buffer(缓冲区)的交互来实现数据的读写。数据首先被读入到Buffer中,然后再从Buffer中写入到Channel,或者从Channel读入到Buffer。
  • 非阻塞模式‌:Channel支持非阻塞模式,这意味着线程可以在等待IO操作完成时继续执行其他任务,而不是被阻塞。这大大提高了程序的并发处理能力。
  • Selector机制‌:NIO还提供了Selector机制,它允许单个线程同时处理多个Channel的IO事件。Selector会不断地轮询注册在其上的Channel,检查是否有IO事件发生,并通知相应的线程进行处理。
 
 

Channel(通道)的主要类型

这里不对Java NIO全部通道类型进行过多的描述,仅仅聚焦于介绍其中最为重要的四种Channel(通道)实现:FileChannel、SocketChannel、ServerSocketChannel、DatagramChannel。
对于以上四种通道,说明如下:
  1. FileChannel文件通道,用于文件的数据读写;
  1. SocketChannel套接字通道,用于Socket套接字TCP连接的数据读写;
  1. ServerSocketChannel服务器套接字通道(或服务器监听通道),允许我们监听TCP连接请求,为每个监听到的请求,创建一个SocketChannel套接字通道;
  1. DatagramChannel数据报通道,用于UDP协议的数据读写。
这个四种通道,涵盖了文件IO、TCP网络、UDP IO三类基础IO读写操作。下面从通道的获取、读取、写入、关闭四个重要的操作入手,对四种通道进行简单的介绍。

FileChannel文件通道

FileChannel是专门操作文件的通道。通过FileChannel,既可以从一个文件中读取数据,也可以将数据写入到文件中。特别申明一下,FileChannel为阻塞模式,不能设置为非阻塞模式。
下面分别介绍:FileChannel的获取、读取、写入、关闭四个操作。
  1. 获取FileChannel通道
    1. 可以通过文件的输入流、输出流获取FileChannel文件通道,示例如下:
 
2. 读取FileChannel通道 在大部分应用场景,从通道读取数据都会调用通道的int read(ByteBufferbuf)方法,它从通道读取到数据写入到ByteBuffer缓冲区,并且返回读取到的数据量。
以上代码channel.read(buf)虽然是读取通道的数据,对于通道来说是读取模式,但是对于ByteBuffer缓冲区来说则是写入数据,这时,ByteBuffer缓冲区处于写入模式。
 
  1. 写入FileChannel通道
写入数据到通道,在大部分应用场景,都会调用通道的write(ByteBuffer)方法,此方法的参数是一个ByteBuffer缓冲区实例,是待写数据的来源。
write(ByteBuffer)方法的作用,是从ByteBuffer缓冲区中读取数据,然后写入到通道自身,而返回值是写入成功的字节数。
在以上的outchannel.write(buf)调用中,对于入参buf实例来说,需要从其中读取数据写入到outchannel通道中,所以入参buf必须处于读取模式,不能处于写入模式。
 
  1. 关闭通道
当通道使用完成后,必须将其关闭。关闭非常简单,调用close( )方法即可。
  1. 强制刷新到磁盘
在将缓冲区写入通道时,出于性能原因,操作系统不可能每次都实时将写入数据落地(或刷新)到磁盘,完成最终的数据保存。
如果在将缓冲数据写入通道时,需要保证数据能落地写入到磁盘,可以在写入后调用一下FileChannel的force()方法。
 
案例
除了FileChannel的通道操作外,还需要注意代码执行过程中隐藏的ByteBuffer的模式切换。由于新建的ByteBuffer是写入模式,才可作为inChannel.read(ByteBuffer)方法的参数,inChannel.read(…)方法将从通道inChannel读到的数据写入到ByteBuffer。然后,需要调用缓冲区的flip方法,将ByteBuffer从写入模式切换成读取模式,才能作为outchannel.write(ByteBuffer)方法的参数,以便从ByteBuffer读取数据,最终写入到outchannel输出通道。
完成一次复制之后,在进入下一次复制前,还要进行一次缓冲区的模式切换。此时,需要将通过clear方法将Buffer切换成写入模式,才能进入下一次的复制。所以,在示例代码中,每一轮外层的while循环,都需要两次ByteBuffer模式切换:第一次模式切换时,翻转buf,变成读取模式;第二次模式切换时,清除buf,变成写入模式。
上面的示例代码,主要的目的在于:演示文件通道以及字节缓冲区的使用。然而,作为文件复制的程序来说,以上实战代码的效率不是最高的。更高效的文件复制,可以调用文件通道的transferFrom方法。

SocketChannel套接字通道

在NIO中,涉及网络连接的通道有两个:一个是SocketChannel负责连接的数据传输,另一个是ServerSocketChannel负责连接的监听。
其中,NIO中的SocketChannel传输通道,与OIO中的Socket类对应;NIO中的ServerSocketChannel监听通道,对应于OIO中的ServerSocket类。
ServerSocketChannel仅仅应用于服务器端,而SocketChannel则同时处于服务器端和客户端,所以,对应于一个连接,两端都有一个负责传输的SocketChannel传输通道。
无论是ServerSocketChannel,还是SocketChannel,都支持阻塞和非阻塞两种模式。如何进行模式的设置呢?调用configureBlocking方法,具体如下:
  1. socketChannel.configureBlocking(false)设置为非阻塞模式。
  1. socketChannel.configureBlocking(true)设置为阻塞模式。
在阻塞模式下,SocketChannel通道的connect连接、read读、write写操作,都是同步的和阻塞式的,在效率上与Java旧的OIO的面向流的阻塞式读写操作相同。因此,在这里不介绍阻塞模式下的通道的具体操作。在非阻塞模式下,通道的操作是异步、高效率的,这也是相对于传统的OIO的优势所在。下面仅仅详细介绍在非阻塞模式下通道的打开、读写和关闭操作等操作。
  1. 获取SocketChannel传输通道
在客户端,先通过SocketChannel静态方法open()获得一个套接字传输通道;然后,将socket套接字设置为非阻塞模式;最后,通过connect()实例方法,对服务器的IP和端口发起连接。
非阻塞情况下,与服务器的连接可能还没有真正建立,socketChannel.connect方法就返回了,因此需要不断地自旋,检查当前是否是连接到了主机:
在服务器端,如何获取与客户端对应的传输套接字呢?
在连接建立的事件到来时,服务器端的ServerSocketChannel能成功地查询出这个新连接事件,并且通过调用服务器端ServerSocketChannel监听套接字的accept()方法,来获取新连接的套接字通道:
NIO套接字通道,主要用于非阻塞的传输场景。所以,基本上都需要调用通道的configureBlocking(false)方法,将通道从阻塞模式切换为非阻塞模式。
 
  1. 读取SocketChannel传输通道
当SocketChannel传输通道可读时,可以从SocketChannel读取数据,具体方法与前面的文件通道读取方法是相同的。调用read方法,将数据读入缓冲区ByteBuffer。
在读取时,因为是异步的,因此我们必须检查read的返回值,以便判断当前是否读取到了数据。read()方法的返回值是读取的字节数,如果返回-1,那么表示读取到对方的输出结束标志,对方已经输出结束,准备关闭连接。实际上,通过read方法读数据,本身是很简单的,比较困难的是,在非阻塞模式下,如何知道通道何时是可读的呢?这就需要用到NIO的新组件——Selector通道选择器。
 
  1. 写入到SocketChannel传输通道
和前面的把数据写入到FileChannel文件通道一样,大部分应用场景都会调用通道的int
write(ByteBufferbuf)方法。
  1. 关闭SocketChannel传输通道
在关闭SocketChannel传输通道前,如果传输通道用来写入数据,则建议调用一次shutdownOutput()终止输出方法,向对方发送一个输出的结束标志(-1)。然后调用socketChannel.close()方法,关闭套接字连接。
 
 
服务端案例
 
客户端案例
 

DatagramChannel数据报通道

在Java中使用UDP协议传输数据,比TCP协议更加简单。和Socket套接字的TCP传输协议不同,UDP协议不是面向连接的协议。使用UDP协议时,只要知道服务器的IP和端口,就可以直接向对方发送数据。在Java NIO中,使用DatagramChannel数据报通道来处理UDP协议的数据传输。
  1. 获取DatagramChannel数据报通道
获取数据报通道的方式很简单,调用DatagramChannel类的open静态方法即可。然后调用configureBlocking(false)方法,设置成非阻塞模式。
如果需要接收数据,还需要调用bind方法绑定一个数据报的监听端口,具体如下:
  1. 读取DatagramChannel数据报通道数据
当DatagramChannel通道可读时,可以从DatagramChannel读取数据。和前面的SocketChannel读取方式不同,这里不调用read方法,而是调用receive(ByteBufferbuf)方法将数据从DatagramChannel读入,再写入到ByteBuffer缓冲区中。
通道读取receive(ByteBufferbuf)方法虽然读取了数据到buf缓冲区,但是其返回值是SocketAddress类型,表示返回发送端的连接地址(包括IP和端口)。通过receive方法读取数据非常简单,但是,在非阻塞模式下,如何知道DatagramChannel通道何时是可读的呢?和SocketChannel一样,同样需要用到NIO的新组件—Selector通道选择器
 
  1. 写入DatagramChannel数据报通道
向DatagramChannel发送数据,和向SocketChannel通道发送数据的方法也是不同的。这里不是调用write方法,而是调用send方法。示例代码如下:
由于UDP是面向非连接的协议,因此,在调用send方法发送数据的时候,需要指定接收方的地址(IP和端口)。 4. 关闭DatagramChannel数据报通道 这个比较简单,直接调用close()方法,即可关闭数据报通道。
 
下面是一个使用DatagramChannel数据包通到发送数据的客户端示例程序代码。其功能是:获取用户的输入数据,通过DatagramChannel数据报通道,将数据发送到远程的服务器。客户端的完整程序代码如下:
通过示例程序代码可以看出,在客户端使DatagramChannel数据报通道发送数据,比起在客户端使用套接字SocketChannel发送数据简单很多。
接下来看看在服务器端应该如何使用DatagramChannel数据包通道接收数据呢?
下面贴出服务器端通过DatagramChannel数据包通道接收数据的程序代码,可能大家目前不一定可以看懂,因为代码中用到了Selector选择器,但是不要紧,下一个小节就介绍它。
服务器端的接收功能是:通过DatagramChannel数据报通道,绑定一个服务器地址(IP+端口),接收客户端发送过来的UDP数据报。服务器端的完整代码如下:
在服务器端,首先调用了bind方法绑定datagramChannel的监听端口。当数据到来后,调用了receive方法,从datagramChannel数据包通道接收数据,再写入到ByteBuffer缓冲区中。
 
Keycloak 客户端授权服务Java IO — NIO Buffer
Loading...
Honesty
Honesty
人道洛阳花似锦,偏我来时不逢春
最新发布
Java IO — NIO Buffer
2024-10-21
Java IO — NIO Channel
2024-10-21
Java IO — IO/NIO模型
2024-10-21
Java异步编程方式介绍
2024-10-21
Elasticsearch — 索引(Mapping Index)
2024-10-19
Elasticsearch — 如何存储数据并保持一致性?
2024-10-19