关于java:理解什么是BIONIOAIO

10次阅读

共计 15405 个字符,预计需要花费 39 分钟才能阅读完成。

I/O 模型

I/O 模型根本阐明

  1. I/O 模型简略了解为:就是应用什么样的通道进行数据的发送和接管,很大水平上决定了程序通信的性能。
  2. Java 反对 3 种网络编程模型:BIO、NIO、AIO。

    Java BIO同步并阻塞(传统阻塞型),服务器实现模式为一个连贯一个线程,即客户端有连贯申请时服务器端就须要启动一个线程进行解决,如果这个连贯不作任何事件会造成不必要的线程开销。

    Java NIO同步非阻塞,服务器实现模式为一个线程解决多个申请(连贯),即客户端发送的连贯申请会被注册到多路复用器上,多路复用器轮询到有 I/O 申请就会进行解决。

    Java AIO异步非阻塞,AIO 引入了异步通道的概念,采纳了 Proactor 模式,简化了程序编写,无效的申请才启动线程,它的特点是先由操作系统实现后才告诉服务端程序启动线程去解决,个别实用于连接数较多且连接时间较长的利用。

  • 同步阻塞:你到饭馆点餐,而后在那等着,还要一边喊:好了没啊!
  • 同步非阻塞:在饭馆点完餐,就去遛狗了。不过溜一会儿,就回饭馆喊一声:好了没啊!
  • 异步阻塞:遛狗的时候,接到饭馆电话,说饭做好了,让您亲自去拿。
  • 异步非阻塞:饭馆打电话说,咱们晓得您的地位,一会给你送过来,安心遛狗就能够了。

BIO、NIO、AIO 应用场景剖析

  • BIO 形式实用于 连接数比拟小且固定 的架构,这种形式对服务器资源要求比拟高,并发局限于利用中,JDK1.4 之前惟一的抉择,程序较为简单容易了解。
  • NIO 形式实用于 连贯数目多且连贯比拟短 的架构,比方聊天服务器,弹幕零碎,服务器间通信等,编程比较复杂,JDK1.4 开始反对。
  • AIO 形式实用于 连贯数目多且连贯比拟长 的架构,比方相册服务器,充沛调用 OS 参加并发操作,变成比较复杂,JDK7 开始反对。

BIO 根本介绍

  • Java BIO 就是传统的 Java IO 编程,其相干的类和接口在 java.io 包下。
  • BIO(Blocking I/O):同步阻塞,服务器实现模式为一个连贯一个线程,即客户端有连贯申请时,服务器就会须要启动一个线程来进行解决。如果这个连贯不作任何事件就会造成不必要的开销,能够通过线程池机制改善。

BIO 编程简要流程

  1. 服务器驱动一个 ServerSocket。
  2. 客户端启动 Socket 对服务器进行通信,默认状况下服务器端须要对每一个客户端建设一个线程进行通信。
  3. 客户端发出请求后,先征询服务器时候否线程响应,如果没有则会期待,或者被回绝。
  4. 如果有响应,客户端线程会期待申请完结后,再继续执行。

BIO 服务端代码案例

public class Server {public static void main(String[] args) throws IOException {
        // 创立线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        // 创立 serverSocket
        ServerSocket serverSocket = new ServerSocket(6666);
        for (; ;) {System.out.println("期待连贯中...");
            // 监听, 期待客户端连贯
            Socket socket = serverSocket.accept();
            System.out.println("连贯到一个客户端");
            executorService.execute(() -> handler(socket));
        }
    }

    // 编写一个 handler 办法, 和客户端通信
    public static void handler(Socket socket) {byte[] bytes = new byte[1024];
        System.out.println("以后线程信息:" + Thread.currentThread().getName());
        try {
            // 通过 socket 获取输出流
            InputStream inputStream = socket.getInputStream();
            // 循环读取客户端发送的数据
            while (inputStream.read(bytes) != -1) {System.out.println(Thread.currentThread().getName()+ ": 发送信息为 :"+ new String(bytes, 0, bytes.length));
            }

        } catch (IOException e) {e.printStackTrace();
        } finally {System.out.println("敞开连贯");
            try {socket.close();
            } catch (IOException e) {e.printStackTrace();
            }
        }
    }
}

运行后果

应用终端命令

telnet 127.0.0.1 6666

BIO 问题剖析

  1. 每个申请都须要创立独立的线程,与对应的客户端进行数据处理。
  2. 当并发数大时,须要 创立大量线程来解决连贯,系统资源占用较大。
  3. 连贯建设后,如果以后线程临时没有数据可读,则以后线程会始终阻塞在 Read 操作上,造成线程资源节约。

NIO 根本介绍

  1. Java NIO 全称 Java non-blocking IO,指的是 JDK 提供的新 API。从 JDK 1.4 开始,Java 提供了一系列改良的输出 / 输入的新个性,被统称为 NIO,即 New IO,是 同步非阻塞 的。
  2. NIO 相干类都放在 java.nio 包下,并对原 java.io 包中很多类进行了改写。
  3. NIO 有 三大外围 局部:Channel(管道)Buffer(缓冲区)Selector(选择器)
  4. NIO 是面向 缓冲区 编程的。数据读取到了一个它略微解决的缓冲区,须要时可在缓冲区中前后挪动,这就减少了处理过程中的灵活性,应用它能够提供非阻塞的高伸缩性网络。
  5. Java NIO 的非阻塞模式,使一个线程从某通道发送申请读取数据,然而它仅能失去目前可用数据,如果目前没有可用数据时,则阐明都不会获取,而不是放弃线程阻塞,所以直到数据变为能够读取之前,该线程能够做其余事件。非阻塞写入同理。

NIO Buffer 的根本应用

public class BufferTest {public static void main(String[] args) {
        // 同理对应的还有:ByteBuffer,IntBuffer,FloatBuffer,CharBuffer,ShortBuffer,DoubleBuffer,LongBuffer
        // 创立一个 Buffer, 大小为 5
        IntBuffer buffer = IntBuffer.allocate(5);
        // 存放数据
        for (int i = 0; i < buffer.capacity(); i++) {buffer.put(i);
        }
        // 切换成读模式. 读写切换
        buffer.flip();
        while (buffer.hasRemaining()) {System.out.println(buffer.get()); // 0 1 2 3 4
        }
    }

}

NIO 和 BIO 比照

  1. BIO 以流的形式解决数据,而 NIO 以块的形式解决数据,块 I/O 的效率比流 I/O 高很多。
  2. BIO 是阻塞的,而 NIO 是非阻塞的。
  3. BIO 基于字节流和字符流进行操作,而 NIO 基于 Channel(通道)和 Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择器)用于监听多个通道事件(比方连贯申请,数据达到等),因而 应用单个线程就能够监听多个客户端通道

NIO 三大外围组件关系

阐明:

  1. 每个 Channel 对应一个 Buffer。
  2. Selector 对应一个线程,一个线程对应多个 Channel。
  3. 该图反馈了有三个 Channel 注册到该 Selector。
  4. 程序切换到那个 Channel 是由 事件 决定的(Event)。
  5. Selector 会依据不同的事件,在各个通道上切换。
  6. Buffer 就是一个内存块,底层是有一个数组。
  7. 数据的读取和写入是通过 Buffer,然而须要 flip() 切换读写模式。而 BIO 是单向的,要么输出流要么输入流。

NIO 三大外围了解

Buffer 的机制及子类

Buffer(缓冲区)根本介绍

缓冲区实质上是一个能够读写数据的内存块,能够了解为是一个 容器对象(含数组),该对象提供了 一组办法,能够更轻松地应用内存块,缓冲区对象内置了一些机制,可能跟踪和记录缓冲区的状态变动状况。

Channel 提供从文件、网络读取数据的渠道,然而读取或者都必须通过 Buffer。

在 Buffer 子类中保护着一个对应类型的数组,用来存放数据:

public abstract class IntBuffer
    extends Buffer
    implements Comparable<IntBuffer>
{

    // These fields are declared here rather than in Heap-X-Buffer in order to
    // reduce the number of virtual method invocations needed to access these
    // values, which is especially costly when coding small buffers.
    //
    final int[] hb;                  // Non-null only for heap buffers
    final int offset;
    boolean isReadOnly;                 // Valid only for heap buffers

    // Creates a new buffer with the given mark, position, limit, capacity,
    // backing array, and array offset
    //
    IntBuffer(int mark, int pos, int lim, int cap,   // package-private
                 int[] hb, int offset)
    {super(mark, pos, lim, cap);
        this.hb = hb;
        this.offset = offset;
    }

    // Creates a new buffer with the given mark, position, limit, and capacity
    //
    IntBuffer(int mark, int pos, int lim, int cap) { // package-private
        this(mark, pos, lim, cap, null, 0);
    }
Buffer 罕用子类 形容
ByteBuffer 存储字节数据到缓冲区
ShortBuffer 存储字符串数据到缓冲区
CharBuffer 存储字符数据到缓冲区
IntBuffer 存储整数数据据到缓冲区
LongBuffer 存储长整型数据到缓冲区
DoubleBuffer 存储浮点型数据到缓冲区
FloatBuffer 存储浮点型数据到缓冲区

Buffer 中定义了四个属性来提供所其蕴含的数据元素。

// Invariants: mark <= position <= limit <= capacity
private int mark = -1;
private int position = 0;
private int limit;
private int capacity;
属性 形容
capacity 容量,即能够包容的最大数据量;在缓冲区被创立时候就被指定,无奈批改
limit 示意缓冲区的以后起点,不能对缓冲区超过极限的地位进行读写操作,但极限是能够批改的
position 以后地位,下一个要被读或者写的索引,每次读写缓冲区数据都会扭转该值,为下次读写做筹备
Mark 标记以后 position 地位,当 reset 后回到标记地位。

Channel 的根本介绍

NIO 的通道相似于流,但有如下区别:

  1. 通道是双向的能够进行读写,而流是单向的只能读,或者写。
  2. 通道能够实现异步读写数据。
  3. 通道能够从缓冲区读取数据,也能够写入数据到缓冲区。

罕用的 Channel 有:FileChannel、DatagramChannel、SocketChannel、SocketServerChannel。

FileChannel 类

FileChannel 次要用来对本地文件进行 IO 操作,常见的办法有:

  1. public int read(ByteBuffer dst):从通道中读取数据到缓冲区中。
  2. public int write(ByteBuffer src):把缓冲区中的数据写入到通道中。
  3. public long transferFrom(ReadableByteChannel src,long position,long count):从指标通道中复制数据到以后通道。
  4. public long transferTo(long position,long count,WriteableByteChannel target):把数据从以后通道复制给指标通道。

应用 FileChannel 写入文本文件

public class NIOFileChannel {public static void main(String[] args) throws IOException {
        String str = "Hello,Java 菜鸟程序员";
        // 创立一个输入流
        FileOutputStream fileOutputStream = new FileOutputStream("hello.txt");
        // 获取通道
        FileChannel channel = fileOutputStream.getChannel();
        // 创立缓冲区
        ByteBuffer byteBuffer = ByteBuffer.allocate(100);
        // 写入 byteBuffer
        byteBuffer.put(str.getBytes());
        // 切换模式
        byteBuffer.flip();
        // 写入通道
        channel.write(byteBuffer);
        // 敞开
        channel.close();
        fileOutputStream.close();}
}

应用 FileChannel 读取文本文件

public class NIOFileChannel {public static void main(String[] args) throws IOException {FileInputStream fileInputStream = new FileInputStream("hello.txt");
      FileChannel channel = fileInputStream.getChannel();
      ByteBuffer byteBuffer = ByteBuffer.allocate(100);
      channel.read(byteBuffer);
      System.out.println(new String(byteBuffer.array(), 0, byteBuffer.limit())); //Hello,Java 菜鸟程序员
      channel.close();
      fileInputStream.close();}
}

应用 FileChannel 复制文件

public class NIOFileChannel03 {public static void main(String[] args) throws IOException {FileInputStream fileInputStream = new FileInputStream("hello.txt");
        FileOutputStream fileOutputStream = new FileOutputStream("world.txt");
        FileChannel inChannel = fileInputStream.getChannel();
        FileChannel outChannel = fileOutputStream.getChannel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(1);
        while (inChannel.read(byteBuffer) != -1) {byteBuffer.flip();
            outChannel.write(byteBuffer);
            // 清空重置
            byteBuffer.clear();}
        fileOutputStream.close();
        fileInputStream.close();}
}

应用 transferFrom 复制文件

public class NIOFileChannel04 {public static void main(String[] args) throws IOException {FileInputStream fileInputStream = new FileInputStream("hello.txt");
        FileOutputStream fileOutputStream = new FileOutputStream("world.txt");
        FileChannel inChannel = fileInputStream.getChannel();
        FileChannel outChannel = fileOutputStream.getChannel();
        // 从哪拷贝, 从几开始到几完结 对应的还有 transferTo()办法.
        outChannel.transferFrom(inChannel, 0, inChannel.size());
        outChannel.close();
        inChannel.close();
        fileOutputStream.close();
        fileInputStream.close();}
}

Channel 和 Buffer 的注意事项

  1. ByteBuffer 反对 类型化 的 put 和 get,put 放入什么数据类型,get 就应该应用相应的数据类型来取出,否则可能会产生 ByteUnderflowException 异样。
  2. 能够将一个一般的 Buffer 转换为只读的 Buffer:asReadOnlyBuffer()办法
  3. NIO 提供了 MapperByteBuffer,能够让文件间接在 内存(堆外内存)中进行批改,而如何同步到文件由 NIO 来实现。
  4. NIO 还反对通过多个 Buffer(即 Buffer 数组)实现读写操作,即Scattering(扩散)和 Gathering(汇集)

    • Scattering(扩散):在向缓冲区写入数据时,能够应用 Buffer 数组顺次写入,一个 Buffer 数组写满后,持续写入下一个 Buffer 数组。
    • Gathering(汇集):从缓冲区读取数据时,能够顺次读取,读完一个 Buffer 再按程序读取下一个。

Selector 的根本介绍

  1. Java 的 NIO 应用了非阻塞的 I/O 形式。能够用一个线程解决若干个客户端连贯,就会应用到 Selector(选择器)。
  2. Selector 可能检测到多个注册通道上是否有事件产生(多个 Channel 以事件的模式注册到同一个 selector),如果有事件产生,便获取事件而后针对每个事件进行相应的解决。
  3. 只有在连贯真正有读写事件产生时,才会进行读写,缩小了零碎开销,并且不用为每个连贯都创立一个线程,不必保护多个线程。
  4. 防止了多线程之间上下文切换导致的开销。

Selector 特点

Netty 的 I/O 线程 NioEventLoop 聚合了 Selector(选择器 / 多路复用器),能够并发解决成千盈百个客户端连贯。

当线程从某客户端 Socket 通道进行读写时,若没有数据可用,该线程能够进行其余工作。

线程通常将非阻塞 I/O 的闲暇工夫用于其余通道上执行 I/O 操作,所以独自的线程能够治理多个输入输出通道。

因为读写操作都是非阻塞的,就能够充沛进步 I/O 线程的运行效率,防止因为频繁 I/O 阻塞导致的线程挂起。

一个 I/O 线程能够并发解决 N 个客户端连贯和读写操作,这从根本上解决了传统同步阻塞 I/O 一连贯一线程模型,架构性能、弹性伸缩能力和可靠性都失去极大地晋升。

Selector 罕用办法

public abstract class Selector implement Closeable{public static Selector open(); // 失去一个选择器对象

    public int select(long timeout); // 监控所有注册的通道,当其中的 IO 操作能够进行时,将对应的 selectionkey 退出外部汇合并返回,参数设置超时工夫

    public Set<SelectionKey> selectionKeys(); // 从外部汇合中失去所有的 SelectionKey}

Selector 相干办法阐明

  • selector.select():// 若未监听到注册管道中有事件,则继续阻塞
  • selector.select(1000):// 阻塞 1000 毫秒,1000 毫秒后返回
  • selector.wakeup():// 唤醒 selector
  • selector.selectNow():// 不阻塞,立刻返回

NIO 非阻塞网络编程过程剖析

  1. 当客户端连贯时,会通过 SeverSocketChannel 失去对应的 SocketChannel。
  2. Selector 进行监听,调用 select()办法,返回注册该 Selector 的所有通道中有事件产生的通道个数。
  3. 将 socketChannel 注册到 Selector 上,public final SelectionKey register(Selector sel, int ops),一个 selector 上能够注册多个 SocketChannel。
  4. 注册后返回一个 SelectionKey,会和该 Selector 关联 (以 汇合 的模式)。
  5. 进一步失去各个 SelectionKey,有事件产生。
  6. 再通过 SelectionKey 反向获取 SocketChannel,应用 channnel()办法。
  7. 能够通过失去的 channel,实现业务解决。

SelectionKey 中定义了四个操作标记位:OP_READ示意通道中产生读事件;OP_WRITE—示意通道中产生写事件;OP_CONNECT—示意建设连贯;OP_ACCEPT—申请新连贯。

NIO 非阻塞网络编程代码示例

public class Server {public static void main(String[] args) throws IOException {
        // 创立 serverSocketChannel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        // 绑定端口
        serverSocketChannel.socket().bind(new InetSocketAddress(6666));
        // 设置为非阻塞
        serverSocketChannel.configureBlocking(false);
        // 失去 Selector 对象
        try (Selector selector = Selector.open()) {
            // 把 ServerSocketChannel 注册到 selector,事件为 OP_ACCEPT
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            // 如果返回的 >0,示意曾经获取到关注的事件
            while (selector.select() > 0) {Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()) {
                    // 取得到一个事件
                    SelectionKey next = iterator.next();
                    // 如果是 OP_ACCEPT,示意有新的客户端连贯
                    if (next.isAcceptable()) {
                        // 给该客户端生成一个 SocketChannel
                        SocketChannel accept = serverSocketChannel.accept();
                        accept.configureBlocking(false);
                        // 将以后的 socketChannel 注册到 selector,关注事件为读事件,同时给 socket Channel 关联一个 buffer
                        accept.register(selector, SelectionKey.OP_READ,ByteBuffer.allocate(1024));
                        System.out.println("获取到一个客户端连贯");
                    // 如果是读事件
                    } else if (next.isReadable()) {
                        // 通过 key 反向获取到对应的 channel
                        SocketChannel channel = (SocketChannel) next.channel();
                        // 获取到该 channel 关联的 buffer
                        ByteBuffer buffer = (ByteBuffer) next.attachment();
                        while (channel.read(buffer) != -1) {buffer.flip();
                            System.out.println(new String(buffer.array(), 0, buffer.limit()));
                            buffer.clear();}
                    }
                    iterator.remove();}
            }
        }
    }

}
public class Client {public static void main(String[] args) throws IOException {
        // 失去一个网络通道
        SocketChannel socketChannel = SocketChannel.open();
        // 设置为非阻塞
        socketChannel.configureBlocking(false);
        // 提供服务器端的 IP 和端口
        InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);
        // 连贯服务器
        if (!socketChannel.connect(inetSocketAddress)) {while (!socketChannel.finishConnect()) {System.out.println("连贯须要工夫, 客户端不会阻塞... 先去吃个宵夜");
            }
        }
        // 连贯胜利, 发送数据
        String str = "hello,Java 菜鸟程序员";
        ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes());
        socketChannel.write(byteBuffer);
        socketChannel.close();
        System.out.println("客户端退出");
    }

}

运行后果

SelectionKey 的相干办法

办法 形容
public abstract Selector selector(); 失去与之关联的 Selector 对象
public abstract SelectableChannel channel(); 失去与之关联的通道
public final Object attachment() 失去与之关联的共享数据
public abstract SelectionKey interestOps(int ops); 设置或扭转监听的事件类型
public final boolean isReadable(); 通道是否可读
public final boolean isWritable(); 通道是否可写
public final boolean isAcceptable(); 是否能够建设连贯 ACCEPT

NIO 实现群聊零碎

  1. 实现服务器端与客户端的数据简略通信(非阻塞)实现多人群聊。
  2. 服务器端:能够检测用户上线,离线,并实现音讯转发性能。
  3. 客户端:通过 Channel 能够无阻塞发送数据给其余所有用户,同时能够接管其余用户发送的音讯(由服务器转发失去)。
public class GroupChatClient {

    private static final String HOST = "127.0.0.1";
    private static final int PORT = 6667;
    private Selector selector;
    private SocketChannel socketChannel;
    private String username;

    public GroupChatClient() {
        try {selector = Selector.open();
            // 连贯服务器
            socketChannel = SocketChannel.open(new InetSocketAddress(HOST, PORT));
            // 设置非阻塞
            socketChannel.configureBlocking(false);
            // 注册
            socketChannel.register(selector, SelectionKey.OP_READ);
            username = socketChannel.getLocalAddress().toString().substring(1);
            System.out.println("客户端:" + username + ", 准备就绪...");
        } catch (IOException e) {e.printStackTrace();
        }
    }

    /**
     * 向服务器发送数据
     *
     * @param info
     */
    public void sendInfo(String info) {
        info = username + "说:" + info;
        try {socketChannel.write(ByteBuffer.wrap(info.getBytes()));
        } catch (IOException e) {e.printStackTrace();
        }
    }

    /**
     * 读取服务端回复的音讯
     */
    public void readInfo() {
        try {
            // 有可用通道
            if (selector.select() > 0) {Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {SelectionKey key = iterator.next();
                    if (key.isReadable()) {
                        // 失去相干的通道
                        SocketChannel sc = (SocketChannel) key.channel();
                        // 失去一个 buffer
                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        // 读取
                        sc.read(buffer);
                        // 把读取到的缓冲区数据转成字符串
                        String msg = new String(buffer.array());
                        System.out.println(msg.trim());
                    }
                    iterator.remove(); // 删除以后的 selectionKey,避免反复操作}
            }
        } catch (IOException e) {e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        // 启动客户端
        GroupChatClient chatClient = new GroupChatClient();
        // 启动一个线程, 每隔 3 秒,读取从服务器端发送的数据
        new Thread(() -> {while (true) {chatClient.readInfo();
                try {TimeUnit.MILLISECONDS.sleep(500);
                } catch (InterruptedException e) {e.printStackTrace();
                }
            }
        }).start();

        // 发送数据给服务器
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNextLine()) {chatClient.sendInfo(scanner.nextLine());
        }

    }

}
public class GroupChatServer {

    // 定义属性
    private Selector selector;
    private ServerSocketChannel listenChannel;
    private static final int PORT = 6667;


    public GroupChatServer() {
        try {
            // 取得选择器
            selector = Selector.open();
            //listenChannel
            listenChannel = ServerSocketChannel.open();
            // 绑定端口
            listenChannel.socket().bind(new InetSocketAddress(PORT));
            // 设置非阻塞模式
            listenChannel.configureBlocking(false);
            // 将该 listenChannel 注册到 Selector
            listenChannel.register(selector, SelectionKey.OP_ACCEPT);
        } catch (IOException e) {e.printStackTrace();
        }
    }


    public static void main(String[] args) {
        // 创立一个服务器对象
        GroupChatServer groupChatServer = new GroupChatServer();
        // 监听
        groupChatServer.listen();}

    /**
     * 监听
     */
    public void listen() {
        try {
            // 如果返回的 >0,示意曾经获取到关注的事件
            while (selector.select() > 0) {Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                // 判断是否有事件
                while (iterator.hasNext()) {
                    // 取得事件
                    SelectionKey key = iterator.next();
                    // 如果是 OP_ACCEPT,示意有新的客户端连贯
                    if (key.isAcceptable()) {SocketChannel socketChannel = listenChannel.accept();
                        // 设置为非阻塞
                        socketChannel.configureBlocking(false);
                        // 注册到 Selector
                        socketChannel.register(selector, SelectionKey.OP_READ);
                        System.out.println("获取到一个客户端连贯 :" + socketChannel.getRemoteAddress() + "上线!");
                    } else if (key.isReadable()) {
                        // 如果是读事件, 就读取数据
                        readData(key);
                    }
                    iterator.remove();}
            }
        } catch (IOException e) {e.printStackTrace();
        } finally {}}

    /**
     * 读取客户端音讯
     */
    private void readData(SelectionKey key) {
        SocketChannel channel = null;
        try {
            // 失去 channel
            channel = (SocketChannel) key.channel();
            // 创立 buffer
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            if (channel.read(buffer) != -1) {String msg = new String(buffer.array());
                System.out.println(msg);
                // 转发音讯给其它客户端(排除本人)
                sendInfoOtherClients(msg, channel);
            }
        } catch (Exception e) {
            try {System.out.println(channel.getRemoteAddress() + "下线了!");
                // 敞开通道
                key.cancel();
                channel.close();} catch (IOException ioException) {ioException.printStackTrace();
            }
        } finally {}}

    /**
     * 转发音讯给其它客户端(排除本人)
     */

    private void sendInfoOtherClients(String msg, SocketChannel self) throws IOException {
        // 服务器转发音讯
        System.out.println("服务器转发音讯中...");
        // 遍历所有注册到 selector 的 socketChannel 并排除本身
        for (SelectionKey key : selector.keys()) {
            // 反向获取通道
            Channel targetChannel = key.channel();
            // 排除本身
            if (targetChannel instanceof SocketChannel && targetChannel != self) {
                // 转型
                SocketChannel dest = (SocketChannel) targetChannel;
                // 将 msg 存储到 buffer 中
                ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
                // 将 buffer 中的数据写入通道
                dest.write(buffer);
            }
        }
    }

}

AIO 根本介绍

JDK 7 引入了 Asynchronous I/O,即 AIO。在进行 I/O 编程中,通常用到两种模式:Reactor 和 Proactor。Java 的 NIO 就是 Reactor,当有事件触发时,服务器端失去告诉,进行相应的解决。

AIO 叫做 异步非阻塞 的 I/O,引入了异步通道的概念,采纳了 Proactor 模式,简化了程序编写,无效的申请才会启动线程,特点就是先由操作系统实现后才告诉服务端程序启动线程去解决,个别用于连接数较多且连贯时长较长的利用。

Reactor 与 Proactor

  • 两种 IO 多路复用计划:Reactor and Proactor。
  • Reactor 模式是基于同步 I/O 的,而 Proactor 模式是和异步 I/O 相干的。

因为 AIO 目前利用并不宽泛,所以本文只是讲述 AIO 根本介绍。

正文完
 0