Java 入门指南:Java NIO —— Channel(通道)

news2024/11/15 17:50:08

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 包中的一些类包含以块的形式读写数据的方法,这使得即使在面向流的系统中,处理速度也会更快。

![[BIO vs NIO.png]]
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 个字节。

零拷贝

transferFromtransferTo 方法在底层使用了操作系统提供的零拷贝功能(如 Linux 的 sendfile() 系统调用),可以大幅提高文件传输性能。但是,不同操作系统和 JVM 实现可能会影响零拷贝的可用性和性能,因此实际性能可能因环境而异。

零拷贝(Zero-Copy)是一种优化数据传输性能的技术,它最大限度地减少了在数据传输过程中的 CPU 和内存开销。在传统的数据传输过程中,数据通常需要在用户空间和内核空间之间进行多次拷贝,这会导致额外的 CPU 和内存开销。

零拷贝技术通过避免这些多余的拷贝操作,实现了更高效的数据传输。

OpenOption

OpenOption 是 Java NIO 中用于指定文件打开选项的接口。它是一个枚举类型,定义了一些常用的选项。以下是几个常见的 OpenOption 接口的实现类:

  1. StandardOpenOption.READ: 以只读模式打开文件。如果文件不存在则抛出 NoSuchFileException 异常。

  2. StandardOpenOption.WRITE: 以写入模式打开文件。如果文件不存在则创建新文件,如果文件存在则截断文件。

  3. StandardOpenOption.CREATE: 如果文件不存在则创建新文件,如果文件已存在则不进行任何操作。

  4. StandardOpenOption.CREATE_NEW: 创建新文件,如果文件已存在则抛出 FileAlreadyExistsException 异常。

  5. StandardOpenOption.APPEND: 在文件末尾追加数据。

  6. StandardOpenOption.TRUNCATE_EXISTING: 如果文件已存在,则清空文件内容。

这些选项可以在使用 FileChannelopen() 方法时作为可变参数传入,以控制文件的打开方式和行为。

套接字通道

用于 TCP 或 UDP 套接字 I/O 的通道,支持非阻塞模式,可以与 Selector 一起使用,实现高效的网络通信。

SocketChannel

SocketChannel 是 Java NIO(New Input/Output)库中的一个类,是用于 TCP 套接字 I/O 的通道,对传统的 [[Socket|Java Socket类]] 的增强

SocketChannel 支持非阻塞模式,可以与 [[#Selector]] 一起使用,实现高效的网络通信。SocketChannel 允许连接到远程主机,进行数据传输。可以实现客户端和服务器之间的双向通信。SocketChannel 可以被视为半双工通道,即它可以在一个通道中同时接收和发送数据

常用方法
  1. open():静态方法,用于创建一个新的未绑定的 SocketChannel 对象。

  2. open(SocketAddress remote):创建一个绑定到指定地址和端口号的 SocketChannel 对象。

  3. connect(SocketAddress remote):连接到指定的远程主机。由于是非阻塞的,可能无法立即完成连接。

  4. finishConnect():用于确保客户端 SocketChannel 完成连接的操作。该方法是一个阻塞方法,它会一直阻塞直到连接完成或连接失败。若返回 true,表示连接已经成功地建立。返回 false,表示连接还没有完成,可能需要继续等待。

    finishConnect() 方法只能在 connect() 方法成功调用后才能调用。如果在调用 finishConnect() 之前就调用了 finishConnect(),将会抛出 ConnectException

  5. isOpen():检查 SocketChannel 是否处于打开状态。

  6. isConnected():检查 SocketChannel 是否已连接到远程主机。

  7. configureBlocking(boolean blocking):设置 SocketChannel 的阻塞模式。如果将其设置为 true,则为阻塞模式,如果将其设置为 false,则为非阻塞模式。

  8. bind(SocketAddress endpoint):将 ServerSocket 绑定到指定的本地地址和端口。

  9. register(Selector selector, int interestOps):将SocketChannel 注册到指定的 Selector 上,以便可以在Selector 上监听它的事件。interestOps 参数指定了要监听的事件类型,如读取、写入等。

  10. 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。

  1. read(ByteBuffer dst):从 SocketChannel 中读取数据,并将读取的数据存储到指定的 ByteBuffer 中。

  2. write(ByteBuffer src):将指定的 ByteBuffer 中的数据写入到SocketChannel 中,发送给远程主机。

  3. close():关闭SocketChannel。

  4. getLocalAddress():获取本地地址(IP地址和端口号)。

  5. getRemoteAddress():获取远程地址(IP地址和端口号)。

使用流程

以下是使用 SocketChannel 的简单流程:

  1. 打开 SocketChannel:通过 SocketChannel.open() 方法打开 SocketChannel,与远程主机建立 TCP 连接。

    也可以通过 SocketChannel.open(SocketAddress remote) 方法打开 SocketChannel,并指定远程主机的地址和端口。

SocketChannel channel = SocketChannel.open(); 
  1. 连接远程主机:通过 SocketChannel.connect(SocketAddress remote) 方法连接远程主机。该方法会阻塞,直到连接成功建立或出现连接错误。
channel.connect(new InetSocketAddress("localhost",8888));
  1. 写入数据:通过 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);
  1. 读取数据:通过 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,可以更加灵活地处理字符编码和解码的需求,避免了直接使用字节数组转换为字符串可能存在的问题。同时,它还提供了更多的编码配置和异常处理机制,能够更好地适应不同的编码场景。

  1. 关闭 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 类提供了以下常用方法:

  1. open():静态工厂方法,用于打开一个未绑定的 ServerSocketChannel 对象。返回值为 ServerSocketChannel 对象。

  2. configureBlocking(boolean block):用于设置 ServerSocketChannel 的阻塞方式。

    如果参数为 true,则表示使用阻塞模式;如果参数为 false,则表示使用非阻塞模式。返回值为 ServerSocketChannel 对象。

  3. bind(SocketAddress local):用于将 ServerSocketChannel 绑定到指定的本地地址和端口号上,以便开始监听传入连接。参数 local 表示本地地址和端口号,可以是 InetSocketAddress 对象或其子类。返回值为 ServerSocketChannel 对象。

  4. bind(SocketAddress endpoint, int backlog):将 ServerSocket 绑定到一个指定的网络地址(SocketAddress)上,并设置连接请求队列的最大长度backlog

    当调用 bind() 方法时,如果 endpoint 参数表示的地址已经被占用,将会抛出一个 BindException 异常。如果设置的 backlog 大于操作系统所允许的最大值,那么实际使用的连接请求队列长度将会被截断为操作系统所允许的最大值。

  5. accept():用于接受传入连接。如果当前为阻塞模式,则该方法会一直阻塞直到有连接就绪;如果当前为非阻塞模式,则该方法会立即返回,如果当前没有连接就绪,则返回值为 null。返回值为 SocketChannel 对象,表示连接到客户端的通道。

  6. socket():用于获取 ServerSocketChannel 的 ServerSocket 对象。ServerSocket 是用于监听传入连接的类,通过该方法可以获得 ServerSocket 对象并进行设置和其他操作。

  7. validOps():用于返回 ServerSocketChannel 支持的操作集合。返回值为整型数值,可用于操作类别判断和位运算操作。

  8. close():用于关闭 ServerSocketChannel 通道。返回值为 void 类型。

使用流程

以下是使用 ServerSocketChannel 的简单流程:

  1. 打开 ServerSocketChannel:通过 ServerSocketChannel.open() 方法打开 ServerSocketChannel。
ServerSocketChannel serverChannel = ServerSocketChannel.open();
  1. 绑定端口:通过 ServerSocketChannel.bind(SocketAddress local) 方法将 ServerSocketChannel 与指定的本地端口绑定,开始监听客户端的连接请求。可以通过 SocketAddress 对象的 createUnresolved(String host, int port) 方法创建本地端口的主机地址和端口号。
serverChannel.bind(new InetSocketAddress("127.0.0.1",8888),
										50);
  1. 接受连接:通过 ServerSocketChannel.accept() 方法接受一个客户端的连接请求,返回一个新的 SocketChannel 对象。如果没有连接请求,该方法会阻塞至有新的连接请求到来。
SocketChannel channel = serverChannel.accept();
  1. 读取数据:通过接受到的 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();
  1. 写入数据:通过接受到的 SocketChannel 对象的 write(ByteBuffer src) 方法写入数据,将 ByteBuffer 缓冲区中的数据发送给客户端。
buffer.clear();
String message = "Hello, Client! This is Server!";
buffer.put(message.getBytes());
buffer.filp();
channel.write(buffer);
  1. 关闭 SocketChannel:通过接受到的 SocketChannel 对象的 close() 方法关闭 SocketChannel,释放网络资源。
channel.close();
  1. 关闭 ServerSocketChannel:通过 ServerSocketChannel.close() 方法关闭 ServerSocketChannel,停止监听客户端的连接请求,释放网络资源。
serverChannel.close();
注意事项
  • 在进行读写操作之前,需要先将 ByteBuffer 缓冲区清空并重新定位。

  • 在进行网络通信时,SocketChannel 的读写操作可能会因为网络问题或其他原因出现异常,必须进行异常处理。

  • ServerSocketChannel 监听端口时,需要关注 bind() 方法的 backlog 参数,控制客户端连接请求的排队数量。当客户端尝试连接到此 ServerSocket 时,它们将排队等待被服务器接受,直到连接被接受或超时(超过指定的队列长度)。

  • 在进行数据读取时,SocketChannel 可能只读取到数据的一部分,需要关注 ByteBuffer 缓冲区的 remaining() 方法来确定是否还有更多的数据需要读取。

  • 在进行数据写入时,需要判断写入操作是否已经将数据全部发送出去,可以通过 ByteBuffer 缓冲区的 remaining() 方法来判断。

  • 注意及时关闭 SocketChannelServerSocketChannel,以释放占用的网络资源。

DatagramChannel

DatagramChannel 是用于 UDP 协议 套接字 I/O 的通道,支持非阻塞模式,将数据以数据报形式发送和接收,适用于无连接的、不可靠的网络通信。

常用方法

以下是一些常用的 DatagramChannel 方法:

  1. open():创建一个未绑定的 DatagramChannel 对象。

  2. bind(SocketAddress local):将 DatagramChannel 绑定到指定的本地地址。

  3. connect(SocketAddress remote):连接到指定的远程地址。

  4. send(ByteBuffer src, SocketAddress target):向指定的目标地址发送数据报,数据保存在缓冲区 src 中。

  5. receive(ByteBuffer dst):从 DatagramChannel 中读取数据报,数据保存在缓冲区 dst 中。

  6. close():关闭 DatagramChannel。

  7. getLocalAddress():获取 DatagramChannel 绑定的本地地址。

  8. isOpen():判断 DatagramChannel 是否打开。

  9. socket():返回与 DatagramChannel 相关联的套接字。

使用流程

以下是使用 DatagramChannel 进行网络编程的一般流程:

  1. 创建 DatagramChannel 对象:使用 open() 静态方法创建一个未绑定的 DatagramChannel 对象。
DatagramChannel channel = DatagramChannel.open();
  1. 绑定本地地址(可选):使用 bind() 方法将 DatagramChannel 绑定到本地地址,以便接收和发送数据报。
channel.bind(new InetSocketAddress("localhost",12345));
  1. 连接远程地址(可选):使用 connect() 方法将 DatagramChannel 连接到指定的远程地址。连接后,只能与该远程地址进行通信。
channel.connect(new InetSocketAddress("localhost",54321));
  1. 发送数据报:使用 send() 方法向指定的目标地址发送数据报,数据保存在 ByteBuffer 对象中。
String message = "Hello, DatagramChannel!";
ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
channel.send(buffer,new InetSocketAddress("localhost",54321));
  1. 接收数据报:使用 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));
  1. 关闭 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 接口` 来处理异步操作完成后的结果,或者进行下一步的操作。同时,还需要适当处理异常情况和正确释放资源,以保证程序的健壮性和稳定性。

  1. 创建 AsynchronousSocketChannel 对象:使用静态的 open() 方法创建一个异步套接字通道实例。
AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open();
  1. 建立连接:调用 connect() 方法发起异步的连接请求。可以使用 FutureCompletionHandler 来处理连接操作的结果。

使用 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) {
        // 连接失败的处理
    }
});
  1. 读取数据:调用 read() 方法异步地从通道中读取数据。同样可以使用 FutureCompletionHandler 来处理读取操作的结果。

使用 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) {
        // 读取失败的处理
    }
});
  1. 写入数据:调用 write() 方法异步地向通道中写入数据。同样可以使用 FutureCompletionHandler 来处理写入操作的结果。

使用 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) {
        // 写入失败的处理
    }
});
  1. 关闭通道:在使用完毕后,调用 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 来处理异步操作完成后的结果,或者进行下一步的操作。同时,还需要适当处理异常情况和正确释放资源,以保证服务器的健壮性和稳定性。

  1. 打开异步服务器套接字通道:
AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open();
  1. 绑定服务器套接字通道到特定的主机地址和端口:
InetSocketAddress address = new InetSocketAddress("localhost", 8080);
serverChannel.bind(address);
  1. 接受客户端连接请求:
serverChannel.accept(attachment, new CompletionHandler<AsynchronousSocketChannel, Object>() {
    public void completed(AsynchronousSocketChannel clientChannel, Object attachment) {
        // 成功处理连接请求时调用的回调方法
        // 在这里可以处理客户端通道的操作,例如读取/写入数据
        // ...
    }

    public void failed(Throwable exc, Object attachment) {
        // 处理连接请求失败时调用的回调方法
    }
});
  1. 监听并接受客户端连接请求:
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) {
        // 处理连接请求失败时调用的回调方法
    }
});
  1. 关闭异步服务器套接字通道:
serverChannel.close();
CompletionHandler

在 Java 中,CompletionHandler 是一个接口,它用于处理异步操作的完成。它位于 java.nio.channels 包中,并且通常与AsynchronousChannel一起使用,以处理非阻塞I/O操作。

CompletionHandler 接口定义了两个方法:

  1. void completed(V result, A attachment): 当异步操作成功完成时,将调用此方法。result 参数代表操作的结果,attachment 参数代表附加的上下文信息。

  2. void failed(Throwable exc, A attachment): 当异步操作失败时,将调用此方法。exc 参数代表操作失败的异常,attachment参数代表附加的上下文信息。

使用 CompletionHandler 可以简化异步操作的结果处理和错误处理。可以通过实现 CompletionHandler 接口并覆盖这两个方法来定义自己的处理逻辑。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2086283.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

哈工大-操作系统L29

从生磁盘到文件 通过文件使用磁盘更加直观方便 一.映射的左右与实现 1.映射作用 如何从文件得到盘块号,用户看到的字符流,而操作系统看见的是盘块,所以建立字符流到盘块的映射 读写&#xff1a;电梯队列到内存缓冲区中&#xff0c;修改然后再放到电梯队列写出去 操作系统负…

web渗透:信息收集常用的工具

目录 如何根据特定网站的特性选择合适的信息收集工具&#xff1f; 网络安全专业人士通常会使用哪些信息收集工具进行漏洞挖掘&#xff1f; 数据分析师在进行市场研究时通常使用哪些信息收集工具&#xff1f; 数据收集工具 数据处理工具 数据分析工具 数据呈现工具 思维导…

艺术家林曦:“高枕无忧”的生活,需要遇见更好的自己

多年前&#xff0c;林曦老师在与朋友的交谈中&#xff0c;曾提到“想过高枕无忧的生活”。那种身心安逸、无所忧虑&#xff0c;坦然面对自己的状态&#xff0c;想来着实愉快。      或许&#xff0c;当焦虑变为当今社会难以避免的课题&#xff0c;“高枕无忧”才更成了我们…

数据结构:栈、队列详解篇

数据结构&#xff1a;栈、队列详解篇 一、栈&#xff08;一&#xff09;栈的概念&#xff08;二&#xff09;栈的实现1、结构定义2、功能实现&#xff08;1&#xff09;栈的初始化&#xff08;2&#xff09;栈的销毁&#xff08;3&#xff09;栈的扩容&#xff08;4&#xff09…

【大模型从入门到精通46】LLM部署运维(LLM Ops)使用Kubeflow Pipelines掌握LLM工作流3

这里写目录标题 功能概览函数定义实践示例&#xff1a;测试适当的拒绝最佳实践与建议适用于科学测验测试的修订函数科学测验测试函数定义执行与评估最佳实践与注意事项 功能概览 evaluate_request_refusal 函数模拟了系统应该基于预定义的标准拒绝生成测验的情景&#xff0c;这…

推荐9款AI论文写作推荐的论文指导!快速生成高质量初稿

在当前的学术写作领域&#xff0c;AI论文写作工具已经成为许多研究人员和学生的重要助手。这些工具不仅能够帮助用户快速生成高质量的论文初稿&#xff0c;还能在一定程度上简化学术写作流程&#xff0c;提高写作效率。以下是九款被广泛推荐的AI论文写作工具&#xff0c;它们各…

Docker续1:docker使用

一、打包传输 1.打包 [rootlocalhost ~]# systemctl start docker [rootlocalhost ~]# docker save -o centos.tar centos:latest [rootlocalhost ~]# ls anaconda-ks.cfg centos.tar 2.传输 [rootlocalhost ~]# scp centos.tar root192.168.1.100:/root 3.删除镜像 [r…

付费自习室管理小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;商家管理&#xff0c;类型管理&#xff0c;自习室管理&#xff0c;订单管理&#xff0c;系统管理 微信端账号功能包括&#xff1a;系统首页&#xff0c;自习室&#xff0c;我的 …

python-旋转木马(赛氪OJ)

[题目描述] 我们要做一个旋转木马&#xff01; 输入一个 nn 的字符矩阵&#xff0c;将其顺时针旋转 90 度后输出。输入格式&#xff1a; 输入共 n1 行。 第一行一个整数 n&#xff0c;表示矩阵大小。 接下来 n 行&#xff0c;每行一个长度为 n 的字符串&#xff0c;仅包含小写字…

Linux高性能服务器编程 总结索引 | 第2章:IP协议详解

IP头部信息 IP数据报的路由和转发 1、IP 服务的特点 1、IP 协议是 TCP/IP 协议族的动力&#xff0c;它为上层协议提供 无状态、无连接、不可靠的服务 2、无状态 是指 IP 通信双方不同步传输数据的状态信息&#xff0c;因此 所有 IP 数据报的发送、传输和接收都是相互独立、没…

【Linux应用编程实战】常见函数应用

介绍一些Linux应用编程实战遇到的&#xff0c;常见要用的函数&#xff0c;进行概况总结。 目录 main&#xff08;&#xff09; lseek&#xff08;&#xff09; poll&#xff08;&#xff09; struct pollfd 结构体返回值典例 mmap&#xff08;&#xff09; munmap&#xff08;…

kylin-麒麟操作系统-安装内存泄露补丁-以及kylin-kms-activation.service服务不断重启解决思路

文章目录 前言1. 问题现象1.1 使用journalctl命令查看更详细的日志信息 2. 解决思路2.1 思路一&#xff1a;2.2 思路二&#xff1a;2.3 合理的解法: 3. 扩展-修复内存泄露3.1 查看自己使用的镜像3.2 到麒麟官网下载相应的补丁包3.3 安装步骤3.4 重启kylin-kms-activation.servi…

python如何另起一行

python 字符串换行的三种方式&#xff1a; 第一种&#xff1a;三个单引号 print 我是一个程序员 我刚开始学习python 第二种&#xff1a;三个双引号 print """ 我是一个程序员 我刚开始学习python""" 第三种&#xff1a;\结尾 print "我是…

生成式AI,搜索赛道的又一个黄金十年

文&#xff5c;白 鸽 编&#xff5c;王一粟 随着生成式AI的发展&#xff0c;搜索引擎正在被重构&#xff0c;越来越多玩家开始布局AI搜索赛道。 一方面&#xff0c;传统搜索引擎/浏览器正借助AI技术的重构重新焕发生机&#xff0c;无论是移动端还是PC端&#xff0c;都在抢占…

GHA高质量原创文章是什么?

GHA文章是一种专为提高搜索引擎优化&#xff08;SEO&#xff09;效果而设计的高质量原创内容。GHA代表高质量&#xff0c;这些文章通过精心编写和策略布局&#xff0c;就是为了帮助网站迅速在Google等搜索引擎上获得排名&#xff0c;写一篇能在Google上获得高排名的文章&#x…

postman注入csrf

示例脚本 参数配置位置 必要参数 django项目仅需要设置domain即可&#xff0c;比如www.baidu.com,baidu.com尽量域名精确避免修改到其他域的参数 必须把这个domain添加到 cookies->Manage cookies ->Domains Allowlist 中&#xff0c;否则cookie的注入失败 代码 // 必…

P1516 青蛙的约会(exgcd)

一些前置知识&#xff1a; 1.扩展欧几里得算法&#xff1a; axbygcd(a,b) 方程一个可行的解&#xff08;x1,y1&#xff09;求法&#xff1a; int exgcd(int a,int b,int &x,int &y) {if(!b){x1,y0; return a;}int dexgcd(b,a%b,y,x);y-a/b*x;return d; }2.由axbygcd…

URP简洁的instance的写法

材质还是要开启enable instance&#xff0c;这是上一次的写法 https://dbbh666.blog.csdn.net/article/details/136644181 最近发现更适合我个人的习惯的写法 就是代码控制这个整个过程 C#代码是这样的&#xff0c;获取一个mesh&#xff0c;获取每个mesh的transform&#xff0c…

UE5 摄像机图像采集到材质 映射到 UI 和 物体表面

一.创建SceneCapture2D的组件 二.创建用于 映射的 贴图 三.将RenderTarget贴图放到SceneCapture2D的摄像机上Scene Capture的TextureTarget 四.这个时候的映射贴图&#xff0c;产生的材质可以直接。放到Plane上。 五&#xff0c;但是如果要用于UI,还需要更改SceneCapture2D的摄…

基于SpringBoot的在线答疑系统

你好呀&#xff0c;我是计算机专业毕业生&#xff0c;专注于在线教育平台的开发与实现。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;Java技术 Spring Boot框架 工具&#xff1a;IntelliJ IDEA、Navicat、Maven、Tomcat 系统展示 首页 个人中心…