NIO(同步非阻塞IO)

NIO


同步&异步

同步

同一时间,一个服务,只能被一个线程使用

异步

同一时间,一个服务,可以被多个线程使用

阻塞&非阻塞

阻塞

进程给CPU传达一个任务之后,一直等待CPU处理完成,然后才执行后面的操作

非阻塞

进程给CPU传达任务后,继续处理后续的操作,隔断时间再来询问之前的操作是否完成。这样的过程其实也叫轮询。

 

 

BIO

BlockingIO

同步阻塞式IO

举例

File、UDP、TCP

BIO缺点

一对一连接

大量的客户端,需要产生大量的线程,会导致服务器卡顿,甚至崩溃

客户端连接后,如果不发生任何操作依然会占用服务器端的线程

 

 

 

NIO

NewIO/NonBlockingIO

同步式非阻塞IO

重要位置

capacity

容量位

用于标记当前缓冲区的容量/大小

limit

限制位

用于限定position所能达到的最大下标。默认和容量位是重合的

position

操作位

类似于数组中的下标,用于指向要操作的位置。默认是第0位

mark

标记位

用于进行标记,通常是用于避免数据大批量产生错误。注意,标记位默认是不启用的

 

NIO的三大组件

Buffer、Channel、Selector

Buffer - 缓冲区

用于进行数据的存储

底层是基于数组进行存储的,只能存储基本数据类型

针对八种基本类型提供了7个子类,其中没有针对boolean类型的子类

主要掌握ByteBuffer

ByteBufferDemo.java

ByteBuffer buffer = ByteBuffer.allocate(10);

创建大小为10的缓冲区

buffer.put("abc".getBytes());

添加数据

buffer.flip();

翻转缓冲区

需要先将limit挪到position上

然后将position归零

等价于

buffer.limit(buffer.position()); buffer.position(0);

while (buffer.hasRemaining()) { System.out.println(buffer.get()); }

遍历缓冲区的数据

 

Channel - 通道

在NIO中,用于进行数据的传输

Channel可以实现双向传输

Channel默认是阻塞的,可以手动设置非阻塞

针对不同的场景提供了不同的子类:

File:FileChannel

UDP:DatagramChannel

TCP:SocketChannel、ServerSocketChannel

Client.java
package com.wiscom.channel;
​
public class Client {
​
    public static void main(String[] args) throws IOException {
​
        // 开启一个客户端通道
        SocketChannel sc = SocketChannel.open();
​
        // 可以将这个通道手动设置为非阻塞
        sc.configureBlocking(false);
​
        // 发起连接
        sc.connect(new InetSocketAddress("localhost", 8090));
​
        // 因为设置为了非阻塞,所以需要保证连接建立之后才能写出数据
        // 先去判断连接是否建立
        while (!sc.isConnected())
            // 在这个方法中自动进行计数,如果连接多次未连接上
            // 那么认为这个连接无法建立
            // 此时会抛出异常
            sc.finishConnect();
​
        // 写出数据
        sc.write(ByteBuffer.wrap("hello server".getBytes()));
​
        // 关流
        sc.close();
    }
​
}
Server.java
package com.wiscom.channel;
​
public class Server {
​
    public static void main(String[] args) throws IOException {
​
        // 开启服务器端的通道
        ServerSocketChannel ssc = ServerSocketChannel.open();
​
        // 绑定要监听端口
        ssc.bind(new InetSocketAddress(8090));
​
        // 设置为非阻塞
        ssc.configureBlocking(false);
​
        // 接收连接
        SocketChannel sc = ssc.accept();
​
        // 判断是否接收到了连接
        while (sc == null)
            sc = ssc.accept();
​
        // 读取数据
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        sc.read(buffer);
        buffer.flip();
        // 获取字节数组
        byte[] data = buffer.array();
        System.out.println(new String(data, 0, buffer.limit()));
        // while(buffer.hasRemaining())
        // System.out.println(buffer.get());
// 关流
        ssc.close();
    }
​
}
Selector - 多路复用选择器

Selector针对事件来进行选择

利用Selector可以实现一对多的连接效果

Selector是面向通道进行选择,并且要求被选择的通道必须是非阻塞的

选择器可以分为三类

可接受

可写

可读

Client.java
package com.wiscom.selector;
​
public class Client {
​
    public static void main(String[] args) throws IOException {
​
        SocketChannel sc = SocketChannel.open();
        sc.connect(new InetSocketAddress("localhost", 8090));
        sc.write(ByteBuffer.wrap("hello server".getBytes()));
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        sc.read(buffer);
        System.out.println(new String(buffer.array(), 0, buffer.position()));
        sc.close();
    }
​
}
Server.java
package com.wiscom.selector;
​
public class Server {
​
    public static void main(String[] args) throws IOException {
​
        // 开启服务器端的通道
        ServerSocketChannel ssc = ServerSocketChannel.open();
        // 绑定要监听的端口
        ssc.bind(new InetSocketAddress(8090));
        // 设置为非阻塞
        ssc.configureBlocking(false);
        // 开启选择器
        Selector selc = Selector.open();
        // 将通道注册到选择器上
        ssc.register(selc, SelectionKey.OP_ACCEPT);
​
        while (true) {
​
            // 进行选择
            selc.select();
​
            // 获取这次选择出来的事件类型
            Set<SelectionKey> keys = selc.selectedKeys();
            // 获取迭代器遍历
            // 根据不同的事件类型来进行不同的处理
            Iterator<SelectionKey> it = keys.iterator();
            while (it.hasNext()) {
                SelectionKey key = it.next();
                // 可接受
                if (key.isAcceptable()) {
                    // 从这个事件中将通道取出来
                    ServerSocketChannel sscx = (ServerSocketChannel) key.channel();
                    // 接受连接
                    SocketChannel sc = sscx.accept();
                    System.out.println("连接成功~~~");
                    // 注册可读以及可写事件
                    sc.configureBlocking(false);
                    sc.register(selc, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
                }
                // 可读
                if (key.isReadable()) {
                    // 从事件中获取到通道
                    SocketChannel sc = (SocketChannel) key.channel();
                    // 读取数据
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    sc.read(buffer);
                    // 解析数据
                    byte[] data = buffer.array();
                    System.out.println(new String(data, 0, buffer.position()));
                    // 注销掉read事件,目的是为了防止重复读取
                    // 获取这个通道上的所有的事件
                    // sc.register(selc, key.interestOps() -
                    // SelectionKey.OP_READ);
                    sc.register(selc, key.interestOps() ^ SelectionKey.OP_READ);
                }
                // 可写
                if (key.isWritable()) {
                    // 从事件中获取通道
                    SocketChannel sc = (SocketChannel) key.channel();
                    // 写出数据
                    sc.write(ByteBuffer.wrap("hello client".getBytes()));
                    // 注销掉可写事件
                    sc.register(selc, key.interestOps() - SelectionKey.OP_WRITE);
                }
                it.remove();
            }
        }
    }
​
}

 

拓展

NIO框架

Netty / Mina

做通讯业务时使用,否则一般用不到

 

 

posted @ 2020-08-21 14:07  minnersun  阅读(669)  评论(0编辑  收藏  举报