Java IO : NIO非阻塞模型

缓冲区数据结构

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

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

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

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

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

在这里插入图片描述

这些值满足下面的条件:

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

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

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

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

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

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


BIO中的阻塞:

在这里插入图片描述

非阻塞式NIO:

在这里插入图片描述
Channel与Buffer:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

package top.onefine.demo;

import lombok.extern.slf4j.Slf4j;

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

/**
 * 文件复制
 *
 * @author one fine<br/>
 */
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);

    }
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
基于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<br/>
 */
@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<br/>
 */
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<br/>
 */
public class Client1 {
    
    
    public static void main(String[] args) {
    
    
        ChatClient client = new ChatClient("127.0.0.1", 7777);
        client.start();
    }
}

猜你喜欢

转载自blog.csdn.net/jiduochou963/article/details/108189409