NIO 的引入
在传统的 Java I/O 模型(BIO)中,I/O 操作是以阻塞的方式进行的。当一个线程执行一个 I/O 操作时,它会被阻塞直到操作完成。这种阻塞模型在处理多个并发连接时可能会导致性能瓶颈,因为需要为每个连接创建一个线程,而线程的创建和切换都是有开销的。
为了解决这个问题,在 Java1.4 版本引入了 NIO
(New I/O or Non-Blocking I/O)java.nio
。提供了一种基于缓冲区、选择器和非阻塞 IO 模型的 IO 处理方式。相比于之前的 BIO
模型,NIO
可以实现更高的并发、更低的延迟以及更少的资源消耗。
I/O 包和 NIO
已经很好地集成了,java.io
也已经以 NIO
为基础重新实现了,所以现在它可以利用 NIO
的一些特性。例如,java.io
包中的一些类包含以块的形式读写数据的方法,这使得即使在面向流的系统中,处理速度也会更快。
Java NIO 概要介绍:初识 Java NIO
使用 NIO 并不一定意味着高性能,它的性能优势主要体现在高并发和高延迟的网络环境下。当连接数较少、并发程度较低或者网络传输速度较快时,NIO 的性能并不一定优于传统的 BIO 。
Channel
NIO 主要由两个核心部分组成:Buffer
(缓冲区) 和 Channel
(通道)
在 NIO 中,并不是以流的方式来处理数据的,而是以 buffer
缓冲区和 Channel
通道配合使用来处理数据的。
通道(Channel)是 NIO
(New Input/Output)模型中的一个重要概念。通道代表着与底层 I/O 设备(如文件、网络套接字等)之间的连接,用于将数据传输到缓冲区或从缓冲区传输数据。
Channel 不与数据打交道,它只负责运输数据
通道在 NIO
中起到了桥梁作用,负责将数据从缓冲区传输到通道或者从通道传输到缓冲区。它是一个双向的数据传输通道。
通道与流的不同之处在于,流只能在一个方向上移动(一个流必须是 InputStream
或者 OutputStream
的子类),而通道是双向的,可以用于读、写或者同时用于读写。
通常,通道可以分为两大类:[[#文件通道]]和[[#套接字通道]]。
文件通道
FileChannel
是 Java NIO 中用于读写文件的通道。它是通过[[IO流FileInputStream、FileOutputStream 或 Java IO 文件类的 RandomAccessFile 等类来获取的。
FileChannel
允许在文件的任意位置进行数据传输,支持文件锁定以及内存映射文件等高级功能。但 FileChannel
无法设置为非阻塞模式,因此它只适用于阻塞式文件操作。
FileChannel
提供了一些方法来进行文件的读写操作,包括读取、写入、位置操作和文件截断等。
常用方法
-
static FileChannel open(Path path, OpenOption... options)
:使用 指定选项 打开一个文件,并返回对应的FileChannel
对象。 -
isOpen()
:检查通道是否打开 -
close()
:关闭通道 -
size()
:返回文件的大小 -
position()
:获取通道的当前位置 -
position(long pos)
:设置通道的位置 -
read(ByteBuffer target)
:从通道读取数据并将其放入一个缓冲区 -
write(ByteBuffer source)
:将数据从缓冲区写入通道 -
truncate(long size)
:将文件截取到指定大小。需要有效的读写权限才能操作文件或通道的大小,并且不支持在只读通道上进行操作。如果 s i z e < 原始文件或通道的大小 size < 原始文件或通道的大小 size<原始文件或通道的大小,则将其截断为指定的大小。如果 s i z e > 原始文件或通道的大小 size > 原始文件或通道的大小 size>原始文件或通道的大小,则在必要时将文件或通道的大小扩展为指定的大小,并填充空字节。
-
force(boolean metaData)
:将所有修改过的缓冲区数据强制刷回到磁盘,如果 metaData 为 true,同时也会把文件的元数据写回到磁盘。 -
int read(ByteBuffer dst)
: 从文件当前位置开始读取数据,并将其写入指定的 ByteBuffer 缓冲区,返回实际读取的字节数。 -
long read(ByteBuffer[] dsts, int offset, int length)
: 从文件当前位置开始读取数据,将其写入给定 ByteBuffer 数组中调用该方法时,从dsts[offset]
开始填充数据,并轮流填充每个缓冲区,直到填满所有缓冲区或者遇到文件末尾。 -
int write(ByteBuffer src)
: 从文件当前位置开始写入 ByteBuffer 缓冲区中的数据,返回实际写入的字节数。 -
long write(ByteBuffer[] srcs, int offset, int length)
: 将给定 ByteBuffer 数组中的数据写入到文件中,调用该方法时,从srcs[offset]
开始写入数据,并轮流写入每个缓冲区,直到填满所有缓冲区。 -
transferFrom(ReadableByteChannel src, long position, long count)
: 从给定的 ReadableByteChannel 中读取数据,并写入文件中,从 position 开始写入,最多写入 count 个字节。 -
transferTo(long position, long count, WritableByteChannel target)
: 从文件中读取数据,并写入给定的 WritableByteChannel 中,从 position 开始读取, 最多读取 count 个字节。
零拷贝
transferFrom
和 transferTo
方法在底层使用了操作系统提供的零拷贝功能(如 Linux 的 sendfile()
系统调用),可以大幅提高文件传输性能。但是,不同操作系统和 JVM 实现可能会影响零拷贝的可用性和性能,因此实际性能可能因环境而异。
零拷贝(Zero-Copy)是一种优化数据传输性能的技术,它最大限度地减少了在数据传输过程中的 CPU 和内存开销。在传统的数据传输过程中,数据通常需要在用户空间和内核空间之间进行多次拷贝,这会导致额外的 CPU 和内存开销。
零拷贝技术通过避免这些多余的拷贝操作,实现了更高效的数据传输。
OpenOption
OpenOption
是 Java NIO 中用于指定文件打开选项的接口。它是一个枚举类型,定义了一些常用的选项。以下是几个常见的 OpenOption 接口的实现类:
-
StandardOpenOption.READ: 以只读模式打开文件。如果文件不存在则抛出 NoSuchFileException 异常。
-
StandardOpenOption.WRITE: 以写入模式打开文件。如果文件不存在则创建新文件,如果文件存在则截断文件。
-
StandardOpenOption.CREATE: 如果文件不存在则创建新文件,如果文件已存在则不进行任何操作。
-
StandardOpenOption.CREATE_NEW: 创建新文件,如果文件已存在则抛出 FileAlreadyExistsException 异常。
-
StandardOpenOption.APPEND: 在文件末尾追加数据。
-
StandardOpenOption.TRUNCATE_EXISTING: 如果文件已存在,则清空文件内容。
这些选项可以在使用 FileChannel
的 open()
方法时作为可变参数传入,以控制文件的打开方式和行为。
套接字通道
用于 TCP 或 UDP 套接字 I/O 的通道,支持非阻塞模式,可以与 Selector
一起使用,实现高效的网络通信。
SocketChannel
SocketChannel
是 Java NIO(New Input/Output)库中的一个类,是用于 TCP 套接字 I/O 的通道,对传统的 [[Socket|Java Socket类]] 的增强
SocketChannel 支持非阻塞模式,可以与 [[#Selector]] 一起使用,实现高效的网络通信。SocketChannel 允许连接到远程主机,进行数据传输。可以实现客户端和服务器之间的双向通信。SocketChannel 可以被视为半双工通道,即它可以在一个通道中同时接收和发送数据。
常用方法
-
open():静态方法,用于创建一个新的未绑定的 SocketChannel 对象。
-
open(SocketAddress remote):创建一个绑定到指定地址和端口号的
SocketChannel
对象。 -
connect(SocketAddress remote):连接到指定的远程主机。由于是非阻塞的,可能无法立即完成连接。
-
finishConnect():用于确保客户端 SocketChannel 完成连接的操作。该方法是一个阻塞方法,它会一直阻塞直到连接完成或连接失败。若返回
true
,表示连接已经成功地建立。返回false
,表示连接还没有完成,可能需要继续等待。finishConnect()
方法只能在connect()
方法成功调用后才能调用。如果在调用finishConnect()
之前就调用了finishConnect()
,将会抛出ConnectException
。 -
isOpen():检查 SocketChannel 是否处于打开状态。
-
isConnected():检查 SocketChannel 是否已连接到远程主机。
-
configureBlocking(boolean blocking):设置 SocketChannel 的阻塞模式。如果将其设置为 true,则为阻塞模式,如果将其设置为 false,则为非阻塞模式。
-
bind(SocketAddress endpoint):将 ServerSocket 绑定到指定的本地地址和端口。
-
register(Selector selector, int interestOps):将SocketChannel 注册到指定的
Selector
上,以便可以在Selector 上监听它的事件。interestOps 参数指定了要监听的事件类型,如读取、写入等。 -
register(Selector sel, int interestOps, Object att):用于将当前 channel 注册到一个
Selector
对象上,以便 Selector 监听此 channel 上的事件。该方法的返回值为SelectionKey
对象,表示当前 channel 在 Selector 对象中的唯一标识符。
-
sel
:要注册的 Selector 对象。 -
interestOps
:要注册的事件类型,可以通过位运算符 ‘|’ 来组合多个事件。
常见的 interestOps 选项有以下几种:-
SelectionKey.OP_CONNECT:表示连接已经建立,适用于客户端的 SocketChannel。
-
SelectionKey.OP_ACCEPT:表示通道已经准备好接受新的连接请求,适用于服务端的 ServerSocketChannel。
-
SelectionKey.OP_READ:表示通道已经准备好进行读操作,即可以从通道中读取数据。
-
SelectionKey.OP_WRITE:表示通道已经准备好进行写操作,即可以向通道中写入数据。
-
-
att
:要绑定到此 channel 上的对象,通常是一个可以用于标识此 channel 的对象,可以为 null。
-
read(ByteBuffer dst):从 SocketChannel 中读取数据,并将读取的数据存储到指定的 ByteBuffer 中。
-
write(ByteBuffer src):将指定的 ByteBuffer 中的数据写入到SocketChannel 中,发送给远程主机。
-
close():关闭SocketChannel。
-
getLocalAddress():获取本地地址(IP地址和端口号)。
-
getRemoteAddress():获取远程地址(IP地址和端口号)。
使用流程
以下是使用 SocketChannel 的简单流程:
-
打开 SocketChannel:通过
SocketChannel.open()
方法打开 SocketChannel,与远程主机建立 TCP 连接。也可以通过
SocketChannel.open(SocketAddress remote)
方法打开 SocketChannel,并指定远程主机的地址和端口。
SocketChannel channel = SocketChannel.open();
- 连接远程主机:通过
SocketChannel.connect(SocketAddress remote)
方法连接远程主机。该方法会阻塞,直到连接成功建立或出现连接错误。
channel.connect(new InetSocketAddress("localhost",8888));
- 写入数据:通过
SocketChannel.write(ByteBuffer src)
方法写入数据,将 ByteBuffer 缓冲区中的数据发送到远程主机。
ByteBuffer buffer = ByteBuffer.allocate();
// 发送信息给服务器
String message = "Hello, Server!";
buffer.put(message.getBytes());
// 切换为读模式
buffer.flip();
channel.write(buffer);
System.out.println("Send message: " + message);
- 读取数据:通过
SocketChannel.read(ByteBuffer dst)
方法读取数据,将远程主机响应的数据从 SocketChannel 中读取到 ByteBuffer 缓冲区中。
// 清空缓冲区
buffer.clear();
// 接收服务器发送的数据
channel.read(buffer);
buffer.flip();
String response = new String(buffer.array()).trim();
System.out.println("Server responses: " + response);
buffer.array()
方法返回的字节数组中可能会包含其他数据或者填充的空字节,这些数据并不是有效的 HTTP 响应内容。因此应对此部分代码进行修改:
String response = new String(buffer.array(),
0,
buffer.limit(),
StandardCharsets.UTF_8).trim();
这样可以确保返回的字符串数据是有效的,不包含未使用的数据。
也可以使用 CharsetDecoder
类将字节数据解码为字符数据,这种方式更加灵活和安全:
buffer.clear();
channel.read(buffer);
// 创建 CharsetDecoder
CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
// 解码字节数据为字符数据
buffer.flip();
CharBuffer charBuffer = decoder.decode(buffer);
// 对解码后的字符进行操作
String response = charBuffer.toString().trim();
使用 CharsetDecoder
,可以更加灵活地处理字符编码和解码的需求,避免了直接使用字节数组转换为字符串可能存在的问题。同时,它还提供了更多的编码配置和异常处理机制,能够更好地适应不同的编码场景。
- 关闭
SocketChannel
:通过SocketChannel.close()
方法关闭SocketChannel
,释放网络资源。
channel.close();
注意事项
-
在进行读写操作之前,需要先将
ByteBuffer
缓冲区清空并重新定位。 -
在进行网络通信时,
SocketChannel
的读写操作可能会因为网络问题或其他原因出现异常,必须进行异常处理。 -
由于
SocketChannel
是非阻塞的通道类型,因此在进行数据读写时必须通过轮询 Selector 或者其他方式判断SocketChannel
是否已经准备好。 -
在进行数据读取时,
SocketChannel
可能只读取到数据的一部分,需要关注ByteBuffer
缓冲区的remaining()
方法来确定是否还有更多的数据需要读取。 -
在进行数据写入时,需要判断写入操作是否已经将数据全部发送出去,可以通过
ByteBuffer
缓冲区的remaining()
方法来判断。
SocketSeverChannel
ServerSocketChannel
是用于监听并接收传入 TCP 套接字连接的通道,支持非阻塞模式,并可以与 Selector
一起使用。
在传统的阻塞式 IO 中,要监听和接受传入的连接,需要使用 Java Socket 中的 ServerSocket 类。而在非阻塞式 IO 中,ServerSocketChannel
负责监听新的连接请求,接收到连接请求后,可以创建一个新的 SocketChannel
以处理数据传输。
使用 ServerSocketChannel
可以实现高效的并发网络服务器。它的非阻塞模式和选择器可以让服务器在单线程或有限线程池的情况下监听和处理多个连接。它也可以与其他 NIO 组件(如 ByteBuffer
)一起使用,提供更灵活的数据处理和传输能力
常用方法
ServerSocketChannel
类提供了以下常用方法:
-
open():静态工厂方法,用于打开一个未绑定的 ServerSocketChannel 对象。返回值为 ServerSocketChannel 对象。
-
configureBlocking(boolean block):用于设置 ServerSocketChannel 的阻塞方式。
如果参数为 true,则表示使用阻塞模式;如果参数为 false,则表示使用非阻塞模式。返回值为 ServerSocketChannel 对象。
-
bind(SocketAddress local):用于将 ServerSocketChannel 绑定到指定的本地地址和端口号上,以便开始监听传入连接。参数 local 表示本地地址和端口号,可以是 InetSocketAddress 对象或其子类。返回值为 ServerSocketChannel 对象。
-
bind(SocketAddress endpoint, int backlog):将
ServerSocket
绑定到一个指定的网络地址(SocketAddress
)上,并设置连接请求队列的最大长度(backlog
)当调用
bind()
方法时,如果endpoint
参数表示的地址已经被占用,将会抛出一个BindException
异常。如果设置的backlog
大于操作系统所允许的最大值,那么实际使用的连接请求队列长度将会被截断为操作系统所允许的最大值。 -
accept():用于接受传入连接。如果当前为阻塞模式,则该方法会一直阻塞直到有连接就绪;如果当前为非阻塞模式,则该方法会立即返回,如果当前没有连接就绪,则返回值为 null。返回值为 SocketChannel 对象,表示连接到客户端的通道。
-
socket():用于获取 ServerSocketChannel 的 ServerSocket 对象。ServerSocket 是用于监听传入连接的类,通过该方法可以获得 ServerSocket 对象并进行设置和其他操作。
-
validOps():用于返回 ServerSocketChannel 支持的操作集合。返回值为整型数值,可用于操作类别判断和位运算操作。
-
close():用于关闭 ServerSocketChannel 通道。返回值为 void 类型。
使用流程
以下是使用 ServerSocketChannel
的简单流程:
- 打开 ServerSocketChannel:通过
ServerSocketChannel.open()
方法打开 ServerSocketChannel。
ServerSocketChannel serverChannel = ServerSocketChannel.open();
- 绑定端口:通过
ServerSocketChannel.bind(SocketAddress local)
方法将 ServerSocketChannel 与指定的本地端口绑定,开始监听客户端的连接请求。可以通过SocketAddress
对象的createUnresolved(String host, int port)
方法创建本地端口的主机地址和端口号。
serverChannel.bind(new InetSocketAddress("127.0.0.1",8888),
50);
- 接受连接:通过
ServerSocketChannel.accept()
方法接受一个客户端的连接请求,返回一个新的 SocketChannel 对象。如果没有连接请求,该方法会阻塞至有新的连接请求到来。
SocketChannel channel = serverChannel.accept();
- 读取数据:通过接受到的 SocketChannel 对象的
read(ByteBuffer dst)
方法读取客户端发送的数据,将数据从 SocketChannel 中读取到 ByteBuffer 缓冲区中。
ByteBuffer buffer = ByteBuffer.allocate(1024);
channel.read(buffer);
buffer.flip();
CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
CharBuffer charBuffer = decoder.decode(buffer);
String response = charBuffer.toString.trim();
- 写入数据:通过接受到的 SocketChannel 对象的
write(ByteBuffer src)
方法写入数据,将 ByteBuffer 缓冲区中的数据发送给客户端。
buffer.clear();
String message = "Hello, Client! This is Server!";
buffer.put(message.getBytes());
buffer.filp();
channel.write(buffer);
- 关闭 SocketChannel:通过接受到的 SocketChannel 对象的
close()
方法关闭 SocketChannel,释放网络资源。
channel.close();
- 关闭 ServerSocketChannel:通过
ServerSocketChannel.close()
方法关闭 ServerSocketChannel,停止监听客户端的连接请求,释放网络资源。
serverChannel.close();
注意事项
-
在进行读写操作之前,需要先将
ByteBuffer
缓冲区清空并重新定位。 -
在进行网络通信时,
SocketChannel
的读写操作可能会因为网络问题或其他原因出现异常,必须进行异常处理。 -
在
ServerSocketChannel
监听端口时,需要关注bind()
方法的backlog
参数,控制客户端连接请求的排队数量。当客户端尝试连接到此ServerSocket
时,它们将排队等待被服务器接受,直到连接被接受或超时(超过指定的队列长度)。 -
在进行数据读取时,
SocketChannel
可能只读取到数据的一部分,需要关注ByteBuffer
缓冲区的remaining()
方法来确定是否还有更多的数据需要读取。 -
在进行数据写入时,需要判断写入操作是否已经将数据全部发送出去,可以通过
ByteBuffer
缓冲区的remaining()
方法来判断。 -
注意及时关闭
SocketChannel
和ServerSocketChannel
,以释放占用的网络资源。
DatagramChannel
DatagramChannel
是用于 UDP 协议 套接字 I/O 的通道,支持非阻塞模式,将数据以数据报形式发送和接收,适用于无连接的、不可靠的网络通信。
常用方法
以下是一些常用的 DatagramChannel
方法:
-
open():创建一个未绑定的 DatagramChannel 对象。
-
bind(SocketAddress local):将 DatagramChannel 绑定到指定的本地地址。
-
connect(SocketAddress remote):连接到指定的远程地址。
-
send(ByteBuffer src, SocketAddress target):向指定的目标地址发送数据报,数据保存在缓冲区
src
中。 -
receive(ByteBuffer dst):从 DatagramChannel 中读取数据报,数据保存在缓冲区
dst
中。 -
close():关闭 DatagramChannel。
-
getLocalAddress():获取 DatagramChannel 绑定的本地地址。
-
isOpen():判断 DatagramChannel 是否打开。
-
socket():返回与 DatagramChannel 相关联的套接字。
使用流程
以下是使用 DatagramChannel
进行网络编程的一般流程:
- 创建 DatagramChannel 对象:使用
open()
静态方法创建一个未绑定的 DatagramChannel 对象。
DatagramChannel channel = DatagramChannel.open();
- 绑定本地地址(可选):使用
bind()
方法将 DatagramChannel 绑定到本地地址,以便接收和发送数据报。
channel.bind(new InetSocketAddress("localhost",12345));
- 连接远程地址(可选):使用
connect()
方法将 DatagramChannel 连接到指定的远程地址。连接后,只能与该远程地址进行通信。
channel.connect(new InetSocketAddress("localhost",54321));
- 发送数据报:使用
send()
方法向指定的目标地址发送数据报,数据保存在 ByteBuffer 对象中。
String message = "Hello, DatagramChannel!";
ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
channel.send(buffer,new InetSocketAddress("localhost",54321));
- 接收数据报:使用
receive()
方法从 DatagramChannel 中读取数据报,数据保存在 ByteBuffer 对象中。可以在循环中反复调用 receive() 方法以接收多个数据报。
buffer.clear();
channel.receive(buffer);
buffer.flip();
byte[] responseBytes = new bytes[buffer.remaining()];
buffer.get(responseBytes);
System.out.println("Recieved: " + new String(responseBytes));
- 关闭 DatagramChannel:使用 close() 方法关闭 DatagramChannel。
channel.close();
AsynchronousSocketChannel
AsynchronousSocketChannel
是 Java NIO 中用于异步网络通信的类,在 Java 7 被引入,它提供了非阻塞的套接字通信功能,用于处理异步时的 SocketChannel
。
通过 AsynchronousSocketChannel
,可以在 I/O 操作进行时,执行其他任务实现异步地进行网络连接、读取和写入操作,并在操作完成时接收通知,提高了并发处理能力。
使用 AsynchronousSocketChannel
可以实现非阻塞的网络通信,通过异步的读写操作,程序能够在等待网络数据到达或者发送数据时继续执行其他任务。这种异步的网络通信方式可以提高程序的性能和资源利用率,尤其在需要同时处理多个连接或高并发的场景下。
常用方法
AsynchronousSocketChannel
提供了以下一些常用的方法:
-
open()
:打开一个异步套接字通道。 -
bind(SocketAddress local)
:将通道绑定到指定的本地地址。 -
Future<Void> connect(SocketAddress remote)
:发起异步连接请求。 -
void connect(SocketAddress remote, A attachment, CompletionHandler<Void, ? super A> handler)
:发起异步连接请求。 -
Future<Integer> read(ByteBuffer dst)
:从通道中异步读取数据。 -
void read(ByteBuffer dst, A attachment, CompletionHandler<Integer, ? super A> handler)
:从通道中异步读取数据。 -
Future<Integer> write(ByteBuffer src)
:向通道中异步写入数据。 -
void write(ByteBuffer src, A attachment, CompletionHandler<Integer, ? super A> handler)
:向通道中异步写入数据。 -
SocketAddress getRemoteAddress()
:获取远程(对方)套接字地址。 -
SocketAddress getLocalAddress()
:获取本地(自己)套接字地址。
使用流程
在使用 AsynchronousSocketChannel
进行异步网络通信时,通常需要结合使用 CompletionHandler 或者
Future 接口` 来处理异步操作完成后的结果,或者进行下一步的操作。同时,还需要适当处理异常情况和正确释放资源,以保证程序的健壮性和稳定性。
- 创建
AsynchronousSocketChannel
对象:使用静态的open()
方法创建一个异步套接字通道实例。
AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open();
- 建立连接:调用
connect()
方法发起异步的连接请求。可以使用Future
或CompletionHandler
来处理连接操作的结果。
使用 Future
的方式:
Future<Void> connectFuture = socketChannel.connect(remoteAddress);
// 阻塞等待连接完成
connectFuture.get();
或使用 CompletionHandler
的方式:
socketChannel.connect(remoteAddress, null, new CompletionHandler<Void, Void>() {
@Override
public void completed(Void result, Void attachment) {
// 连接成功的处理
}
@Override
public void failed(Throwable exc, Void attachment) {
// 连接失败的处理
}
});
- 读取数据:调用
read()
方法异步地从通道中读取数据。同样可以使用Future
或CompletionHandler
来处理读取操作的结果。
使用 Future
的方式:
ByteBuffer buffer = ByteBuffer.allocate(1024);
Future<Integer> readFuture = socketChannel.read(buffer);
// 阻塞等待读取完成,并获取读取的字节数
int bytesRead = readFuture.get();
或使用 CompletionHandler
的方式:
ByteBuffer buffer = ByteBuffer.allocate(1024);
socketChannel.read(buffer, null, new CompletionHandler<Integer, Void>() {
@Override
public void completed(Integer bytesRead, Void attachment) {
// 读取成功的处理
}
@Override
public void failed(Throwable exc, Void attachment) {
// 读取失败的处理
}
});
- 写入数据:调用
write()
方法异步地向通道中写入数据。同样可以使用Future
或CompletionHandler
来处理写入操作的结果。
使用 Future
的方式:
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("Hello, world!".getBytes());
buffer.flip();
Future<Integer> writeFuture = socketChannel.write(buffer);
// 阻塞等待写入完成,并获取写入的字节数
int bytesWritten = writeFuture.get();
或使用 CompletionHandler
的方式:
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("Hello, world!".getBytes());
buffer.flip();
socketChannel.write(buffer, null, new CompletionHandler<Integer, Void>() {
@Override
public void completed(Integer bytesWritten, Void attachment) {
// 写入成功的处理
}
@Override
public void failed(Throwable exc, Void attachment) {
// 写入失败的处理
}
});
- 关闭通道:在使用完毕后,调用
close()
方法关闭通道。
socketChannel.close();
AsynchronousServerSocketChannel
AsynchronousServerSocketChannel
是 Java NIO 中用于异步网络通信的类,在 Java 7 被引入,它表示一个异步服务器套接字通道,用于监听客户端连接,处理异步时的 ServerSocketChannel
。
使用 AsynchronousServerSocketChannel
可以实现异步的服务器端网络通信,通过异步地接受客户端连接请求,可以同时处理多个客户端连接,并以非阻塞的方式进行数据的读写操作。这种异步的服务器模型可以提高服务器的并发性能和资源利用率。
常用方法
-
static AsynchronousServerSocketChannel open()
:打开一个异步服务器套接字通道。 -
void bind(SocketAddress local)
:将通道绑定到指定的本地地址,并指定连接队列的最大长度。 -
Future<Void> bind(SocketAddress local, int backlog)
:将通道绑定到指定的本地地址,并指定连接队列的最大长度。 -
Future<AsynchronousSocketChannel> accept()
:异步接受客户端连接请求,并返回对应的异步套接字通道。 -
void accept(A attachment, CompletionHandler<AsynchronousSocketChannel, ? super A> handler)
:异步接受客户端连接请求,并返回对应的异步套接字通道。 -
boolean isOpen()
:判断通道是否处于打开状态。 -
SocketAddress getLocalAddress()
:获取本地(服务器)套接字地址。
使用流程
在使用 AsynchronousServerSocketChannel
进行异步网络通信时,通常需要结合使用 CompletionHandler
或者 Future
来处理异步操作完成后的结果,或者进行下一步的操作。同时,还需要适当处理异常情况和正确释放资源,以保证服务器的健壮性和稳定性。
- 打开异步服务器套接字通道:
AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open();
- 绑定服务器套接字通道到特定的主机地址和端口:
InetSocketAddress address = new InetSocketAddress("localhost", 8080);
serverChannel.bind(address);
- 接受客户端连接请求:
serverChannel.accept(attachment, new CompletionHandler<AsynchronousSocketChannel, Object>() {
public void completed(AsynchronousSocketChannel clientChannel, Object attachment) {
// 成功处理连接请求时调用的回调方法
// 在这里可以处理客户端通道的操作,例如读取/写入数据
// ...
}
public void failed(Throwable exc, Object attachment) {
// 处理连接请求失败时调用的回调方法
}
});
- 监听并接受客户端连接请求:
serverChannel.accept(attachment, new CompletionHandler<AsynchronousSocketChannel, Object>() {
public void completed(AsynchronousSocketChannel clientChannel, Object attachment) {
// 成功处理连接请求时调用的回调方法
// 在这里可以处理客户端通道的操作,例如读取/写入数据
// ...
// 继续监听并接受下一个客户端连接请求
serverChannel.accept(attachment, this);
}
public void failed(Throwable exc, Object attachment) {
// 处理连接请求失败时调用的回调方法
}
});
- 关闭异步服务器套接字通道:
serverChannel.close();
CompletionHandler
在 Java 中,CompletionHandler
是一个接口,它用于处理异步操作的完成。它位于 java.nio.channels
包中,并且通常与AsynchronousChannel
一起使用,以处理非阻塞I/O操作。
CompletionHandler
接口定义了两个方法:
-
void completed(V result, A attachment)
: 当异步操作成功完成时,将调用此方法。result
参数代表操作的结果,attachment
参数代表附加的上下文信息。 -
void failed(Throwable exc, A attachment)
: 当异步操作失败时,将调用此方法。exc
参数代表操作失败的异常,attachment
参数代表附加的上下文信息。
使用 CompletionHandler
可以简化异步操作的结果处理和错误处理。可以通过实现 CompletionHandler
接口并覆盖这两个方法来定义自己的处理逻辑。