Java IO : NIO非阻塞模型

缓冲区数据结构

在使用内存映射时,我们创建了单一的缓冲区横跨整个文件或我们感兴趣的文件区域。我们还可以使用更多的缓冲区来读写大小合适的信息块。

这里简要介绍Buffer对象上的基本操作。缓冲区是由具有相同类型的数值构成的数组,Buffer类是一个抽象类,它有众多的具体子类,包括ByteBufferCharBufferDoubleBufferIntBufferLongBufferShortBuffer

注意:StringBuffer类与这些缓冲区没有关系。

在实践中,最常用的将是ByteBufferCharBuffer。如图2-20所示,每个缓冲区都具有:

  • 一个容量,它永远不能改变。
  • 一个读写位置,下一个值将在此进行读写。
  • 一个界限,超过它进行读写是没有意义的。
  • 一个可选的标记,用于重复一个读入或写出操作。

Java IO : NIO非阻塞模型_第1张图片

这些值满足下面的条件:

0<=标记(mark)<=位置(position)<=界限(limit)<=容量(capacity)

使用缓冲区的主要目的是执行“写,然后读入”循环。假设我们有一个缓冲区,在一开始,它的位置为0,界限等于容量。我们不断地调用put将数值添加到这个缓冲区中,当我们耗尽所有的数据或者写出的数据量达到容量大小时,就该切换到读入操作了。

这时调用flip方法将界限设置到当前位置,并把位置复位到0。现在remaining方法返回正数时(它返回的值“界限-位置”),不断地调用get。在我们将缓冲区中所有的值都读入之后,调用clear使缓冲区为下一次写循环做好准备。clear方法将位置复位到0,并将界限复位到容量。

如果你想重读缓冲区,可以使用rewind或mark/reset方法。

要获取缓冲区,可以调用诸如ByteBuffer.allocate或ByteBuffer.wrap这样的静态方法。

然后,可以使用来自某个通道的数据填充缓冲区,或者将缓冲区的内容写出通道中。这是一种飞虫有用的方法,可以替代随机访问文件。


BIO中的阻塞:

Java IO : NIO非阻塞模型_第2张图片

非阻塞式NIO:

Java IO : NIO非阻塞模型_第3张图片
Channel与Buffer:

Java IO : NIO非阻塞模型_第4张图片
Java IO : NIO非阻塞模型_第5张图片
Java IO : NIO非阻塞模型_第6张图片
Java IO : NIO非阻塞模型_第7张图片

Java IO : NIO非阻塞模型_第8张图片
Java IO : NIO非阻塞模型_第9张图片
Java IO : NIO非阻塞模型_第10张图片

多方法实现本地文件拷贝:

package top.onefine.demo;

import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/**
 * 文件复制
 *
 * @author one fine
*/
interface FileCopyRunner { /** * 文件复制 * @param source 源文件 * @param target 目标文件 */ void copeFile(File source, File target); } @Slf4j public class FileCopyDemo { private static final int ROUNDS = 5; private static void benchmark(FileCopyRunner test, File source, File target) { long elapsed = 0L; boolean b = true; for (int i = 0; i < ROUNDS; i++) { long startTime = System.currentTimeMillis(); test.copeFile(source, target); elapsed += System.currentTimeMillis() - startTime; b = target.delete();// 删除拷贝的文件 if (!b) { log.error("文件删除失败."); break; } } if (b) log.info("{}: 平均消耗时间{}.", test, elapsed / ROUNDS); } private static void close(Closeable closeable) { if (closeable != null) { try { closeable.close(); } catch (IOException e) { e.printStackTrace(); } } } public static void main(String[] args) { // 不带缓冲区的流 FileCopyRunner noBufferStreamCopy = new FileCopyRunner() { @Override public void copeFile(File source, File target) { InputStream fin = null; OutputStream fout = null; try { fin = new FileInputStream(source); fout = new FileOutputStream(target); int result; while ((result = fin.read()) != -1) { // 返回读取到的内容, 最后返回-1 fout.write(result); // 写到目标文件中 } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { close(fout); close(fin); } } @Override public String toString() { return "noBufferStreamCopy"; } }; // 带有缓冲区的流 FileCopyRunner bufferedStreamCopy = new FileCopyRunner() { @Override public void copeFile(File source, File target) { InputStream fin = null; OutputStream fout = null; try { fin = new BufferedInputStream(new FileInputStream(source)); fout = new BufferedOutputStream(new FileOutputStream(target)); byte[] buffer = new byte[1024]; int result; while ((result = fin.read(buffer)) != -1) { // 返回读取到的内容, 最后返回-1 fout.write(buffer, 0, result); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { close(fout); close(fin); } } @Override public String toString() { return "bufferedStreamCopy"; } }; // 缓冲区 < - > 通道 FileCopyRunner nioBufferCopy = new FileCopyRunner() { @Override public void copeFile(File source, File target) { FileChannel fin = null; FileChannel fout = null; try { fin = new FileInputStream(source).getChannel(); fout = new FileOutputStream(target).getChannel(); ByteBuffer buffer = ByteBuffer.allocate(1024); while (fin.read(buffer) != -1) { // 使用通道将数据读到缓冲区中, 最后返回-1 // 切换模式: 写模式 -> 读模式 buffer.flip(); while (buffer.hasRemaining()) fout.write(buffer); // 调整模式: 读模式 -> 写模式 buffer.clear(); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { close(fout); close(fin); } } @Override public String toString() { return "nioBufferCopy"; } }; // 通道 < - > 通道 FileCopyRunner nioTransferCopy = new FileCopyRunner() { @Override public void copeFile(File source, File target) { FileChannel fin = null; FileChannel fout = null; try { fin = new FileInputStream(source).getChannel(); fout = new FileOutputStream(target).getChannel(); long transferred = 0L; // do { // transferred += fin.transferTo(0, fin.size(), fout); // } while (transferred == fin.size()); long size = fin.size(); while (transferred != size) transferred += fin.transferTo(0, size, fout); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { close(fout); close(fin); } } @Override public String toString() { return "nioTransferCopy"; } }; // 小文件 File smallFile = new File("C:/Users/Lenovo/Desktop/copyTest/file1.docx"); File smallFileCopy = new File("C:/Users/Lenovo/Desktop/copyTest/file1Copy.docx"); log.info("---Coping small file---"); benchmark(noBufferStreamCopy, smallFile, smallFileCopy); benchmark(bufferedStreamCopy, smallFile, smallFileCopy); benchmark(nioBufferCopy, smallFile, smallFileCopy); benchmark(nioTransferCopy, smallFile, smallFileCopy); // // 中等文件 // File bigFile = new File("C:/Users/Lenovo/Desktop/copyTest/file2.docx"); // File bigFileCopy = new File("C:/Users/Lenovo/Desktop/copyTest/file2Copy.docx"); // // log.info("---Coping big file---"); // // benchmark(noBufferStreamCopy, bigFile, bigFileCopy); // benchmark(bufferedStreamCopy, bigFile, bigFileCopy); // benchmark(nioBufferCopy, bigFile, bigFileCopy); // benchmark(nioTransferCopy, bigFile, bigFileCopy); // // // 大文件 // File hugeFile = new File("C:/Users/Lenovo/Desktop/copyTest/file3.docx"); // File hugeFileCopy = new File("C:/Users/Lenovo/Desktop/copyTest/file3Copy.docx"); // // log.info("---Coping huge file---"); // // benchmark(noBufferStreamCopy, hugeFile, hugeFileCopy); // benchmark(bufferedStreamCopy, hugeFile, hugeFileCopy); // benchmark(nioBufferCopy, hugeFile, hugeFileCopy); // benchmark(nioTransferCopy, hugeFile, hugeFileCopy); } }

在这里插入图片描述
Java IO : NIO非阻塞模型_第11张图片
Java IO : NIO非阻塞模型_第12张图片
Java IO : NIO非阻塞模型_第13张图片
Java IO : NIO非阻塞模型_第14张图片
Java IO : NIO非阻塞模型_第15张图片
基于AIO改造多人聊天室

服务端编写:

package top.onefine.demo.socket.nio;

import lombok.extern.slf4j.Slf4j;

import java.io.Closeable;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Set;

/**
 * @author one fine
*/
@Slf4j public class ChatServer { private static final int DEFAULT_PORT = 8888; private static final String QUIT = "quit"; private static final int BUFFER = 1024; private ServerSocketChannel server; private Selector selector; private final ByteBuffer rBuffer = ByteBuffer.allocate(BUFFER); private final ByteBuffer wBuffer = ByteBuffer.allocate(BUFFER); // private final Charset charset = Charset.forName("UTF-8"); private final Charset charset = StandardCharsets.UTF_8; private final int port; public ChatServer() { // this.port = DEFAULT_PORT; this(DEFAULT_PORT); } public ChatServer(int port) { this.port = port; } private void start() { try { server = ServerSocketChannel.open(); server.configureBlocking(false); // 设置阻塞状态为false即非阻塞状态 server.socket().bind(new InetSocketAddress(port)); selector = Selector.open(); server.register(selector, SelectionKey.OP_ACCEPT); // 注册监听用户连接事件 log.info("启动服务器, 监听端口: {}...", port); while (true) { // 监听 selector.select(); // 阻塞 Set<SelectionKey> selectionKeys = selector.selectedKeys(); // 处理被触发的事件 selectionKeys.forEach(selectionKey -> { try { handles(selectionKey); } catch (IOException e) { e.printStackTrace(); } }); selectionKeys.clear(); // 手动清空集合 } } catch (IOException e) { e.printStackTrace(); } finally { close(selector); } } private void handles(SelectionKey selectionKey) throws IOException { // 触发 accept 事件, 即 和客户端建立连接 if (selectionKey.isAcceptable()) { ServerSocketChannel server = (ServerSocketChannel) selectionKey.channel(); SocketChannel client = server.accept(); client.configureBlocking(false); // 设置为非阻塞 client.register(selector, SelectionKey.OP_READ); // 注册事件 log.info("客户端[{}]已连接.", client.socket().getPort()); } // 触发 read 事件, 即 客户端发送了消息 if (selectionKey.isReadable()) { SocketChannel client = (SocketChannel) selectionKey.channel(); String fwdMsg = receive(client); if (fwdMsg.isEmpty()) { // 客户端异常 selectionKey.cancel(); // 取消监听 selector.wakeup(); // 通知selector } else { // 转发消息 forwardMessage(client, fwdMsg); // 检查用户是否需要退出 if (readyToQuit(fwdMsg)) { selectionKey.cancel(); // 取消监听 log.info("客户端[{}]已断开.", client.socket().getPort()); } } } } private void forwardMessage(SocketChannel client, String fwdMsg) throws IOException { for (SelectionKey selectionKey : selector.keys()) { Channel connectedClient = selectionKey.channel(); if (connectedClient instanceof ServerSocketChannel) continue; if (selectionKey.isValid() && !client.equals(connectedClient)) { wBuffer.clear(); wBuffer.put(charset.encode(client.socket().getPort() + ": " + fwdMsg)); wBuffer.flip(); // write -> read while (wBuffer.hasRemaining()) ((SocketChannel) connectedClient).write(wBuffer); } } } private String receive(SocketChannel client) throws IOException { rBuffer.clear(); // 清空 while (client.read(rBuffer) > 0) ; rBuffer.flip(); return String.valueOf(charset.decode(rBuffer)); } private boolean readyToQuit(String msg) { return QUIT.equalsIgnoreCase(msg); } private void close(Closeable closeable) { if (closeable != null) { try { closeable.close(); } catch (IOException e) { e.printStackTrace(); } } } public static void main(String[] args) { ChatServer chatServer = new ChatServer(7777); chatServer.start(); } }

客户端编写:

package top.onefine.demo.socket.nio;

import java.io.Closeable;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedSelectorException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Set;

/**
 * @author one fine
*/
public class ChatClient { private static final String DEFAULT_SERVER_HOST = "127.0.0.1"; private static final int DEFAULT_SERVER_PORT = 8888; private static final String QUIT = "quit"; private static final int BUFFER = 1024; private String host; private int port; private SocketChannel client; private ByteBuffer rBuffer = ByteBuffer.allocate(BUFFER); private ByteBuffer wBuffer = ByteBuffer.allocate(BUFFER); private Selector selector; // private Charset charset = Charset.forName("UTF-8"); private Charset charset = StandardCharsets.UTF_8; public ChatClient() { this(DEFAULT_SERVER_HOST, DEFAULT_SERVER_PORT); } public ChatClient(String host, int port) { this.host = host; this.port = port; } public boolean readyToQuit(String msg) { return QUIT.equalsIgnoreCase(msg); } private void close(Closeable closeable) { if (closeable != null) { try { closeable.close(); } catch (IOException e) { e.printStackTrace(); } } } public void start() { try { client = SocketChannel.open(); client.configureBlocking(false); // 非阻塞模式 selector = Selector.open(); // 通道上注册事件 client.register(selector, SelectionKey.OP_CONNECT); client.connect(new InetSocketAddress(host, port)); while (true) { selector.select(); // 阻塞 Set<SelectionKey> selectionKeys = selector.selectedKeys(); for (SelectionKey selectionKey : selectionKeys) { handles(selectionKey); } selectionKeys.clear(); } } catch (IOException e) { e.printStackTrace(); } catch (ClosedSelectorException e) { // 用户正常退出, 不需要做任何处理 } finally { close(selector); } } private void handles(SelectionKey selectionKey) throws IOException { // CONNECT 事件 - 连接就绪事件 if (selectionKey.isConnectable()) { SocketChannel client = (SocketChannel) selectionKey.channel(); if (client.isConnectionPending()) { client.finishConnect(); // 处理用户的输入 new Thread(new UserInputHandler(this)).start(); } client.register(selector, SelectionKey.OP_READ); } // READ 事件 - 服务器转发消息 if (selectionKey.isReadable()) { SocketChannel client = (SocketChannel) selectionKey.channel(); String msg = receive(client); if (msg.isEmpty()) { // 服务器异常 close(selector); } else { System.out.println(msg); } } } public void send(String msg) throws IOException { if (msg.isEmpty()) { return; } wBuffer.clear(); wBuffer.put(charset.encode(msg)); wBuffer.flip(); while (wBuffer.hasRemaining()) { client.write(wBuffer); } // 检查用户是否需要退出 if (readyToQuit(msg)) close(selector); } private String receive(SocketChannel client) throws IOException { rBuffer.clear(); while (client.read(rBuffer) > 0); rBuffer.flip(); return String.valueOf(charset.decode(rBuffer)); } }

测试:

package top.onefine.demo.socket.nio;

/**
 * @author one fine
*/
public class Client1 { public static void main(String[] args) { ChatClient client = new ChatClient("127.0.0.1", 7777); client.start(); } }

你可能感兴趣的:(#,Java,nio,socket,java,网络编程,非阻塞)