NIO详细介绍(Selector,Channel)

1.Selector(多路复用)

原先的bio中,一个客户端连接,就为它分配一个线程。这样的问题,当用户激增时候,线程会增加很多,增加服务器开销。

所以后来使用了线程池进行管理线程,但是有个弊端,如果线程池有100个线程,这个时候第101个就会等待。传统的bio(Server/Client)如下图:

有这个弊端,Nio就用selector解决。

NIO中非阻塞I/O 采用了基于Reactor模式的工作方式,I/O 调用不会被阻塞,相反是注册感兴趣的特定I/O 事件,如可读数据到
达,新的套接字连接等等,在发生特定事件时,系统再通知我们。NIO中实现非阻塞I/O的核心对象就是Selector,Selector 就是
注册各种I/O 事件地方,而且当那些事件发生时,就是这个对象告诉我们所发生的事件,如下图所示:

从图中可以看出,当有读或写等任何注册的事件发生时,可以从Selector 中获得相应的SelectionKey,同时从 SelectionKey中可
以找到发生的事件和该事件所发生的具体的SelectableChannel,以获得客户端发送过来的数据。
 

使用NIO中非阻塞I/O 编写服务器处理程序,大体上可以分为下面三个步骤:
1. 向Selector 对象注册感兴趣的事件。
2. 从Selector 中获取感兴趣的事件。
3. 根据不同的事件进行相应的处理。
/* * 注册事件 */

private Selector getSelector() throws IOException {

// 创建 Selector 对象

Selector sel = Selector.open();
// 创建可选择通道,并配置为非阻塞模式

ServerSocketChannel server = ServerSocketChannel.open();

server.configureBlocking(false);
// 绑定通道到指定端口

ServerSocket socket = server.socket();

InetSocketAddress address = new InetSocketAddress(port);

socket.bind(address);
// 向 Selector 中注册感兴趣的事件

server.register(sel, SelectionKey.OP_ACCEPT); return sel;
}

创建了ServerSocketChannel对象,并调用 configureBlocking()方法,配置为非阻塞模式,接下来的三行代码把该通道绑定到指定端口,最后向Selector 中注册事件,此处指定的是参数是OP_ACCEPT,即指定我们想要监听accept 事件,也就是新的连接发 生时所产生的事件,对于ServerSocketChannel 通道来说,我们唯一可以指定的参数就是OP_ACCEPT。

当Selector 中获取感兴趣的事件,即开始监听,进入内部循环:

public void listen(){
    System.out.println("listen on " + this.port + ".");
    try {
        //轮询主线程

        while (true){
            //大堂经理再叫号
            selector.select();
            //每次都拿到所有的号子
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> iter = keys.iterator();
            //不断地迭代,就叫轮询
            //同步体现在这里,因为每次只能拿一个key,每次只能处理一种状态
            while (iter.hasNext()){
                SelectionKey key = iter.next();
                iter.remove();
                //每一个key代表一种状态
                //没一个号对应一个业务
                //数据就绪、数据可读、数据可写 等等等等
                process(key);
            }
            
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

在非阻塞I/O 中,内部循环模式基本都是遵循这种方式。首先调用select()方法,该方法会阻塞,直到至少有一个事件发生,然后
再使用selectedKeys()方法获取发生事件的SelectionKey,再使用迭代器进行循环。

最后根据不同事件进行不同处理:

private void process(SelectionKey key) throws IOException {
    //针对于每一种状态给一个反应
    if(key.isAcceptable()){
        ServerSocketChannel server = (ServerSocketChannel)key.channel();
        //这个方法体现非阻塞,不管你数据有没有准备好
        //你给我一个状态和反馈
        SocketChannel channel = server.accept();
        //一定一定要记得设置为非阻塞
        channel.configureBlocking(false);
        //当数据准备就绪的时候,将状态改为可读
        key = channel.register(selector,SelectionKey.OP_READ);
    }
    else if(key.isReadable()){
        //key.channel 从多路复用器中拿到客户端的引用
        SocketChannel channel = (SocketChannel)key.channel();
        int len = channel.read(buffer);
        if(len > 0){
            buffer.flip();
            String content = new String(buffer.array(),0,len);
            key = channel.register(selector,SelectionKey.OP_WRITE);
            //在key上携带一个附件,一会再写出去
            key.attach(content);
            System.out.println("读取内容:" + content);
        }
    }
    else if(key.isWritable()){
        SocketChannel channel = (SocketChannel)key.channel();

        String content = (String)key.attachment();
        channel.write(ByteBuffer.wrap(("输出:" + content).getBytes()));

        channel.close();
    }
}

2.Channel

通道是一个对象。我们用来读取和输出对象。里面的数据我们不是用bio中的字节流处理,而是用buffer缓冲区。是将数据从通
道读入缓冲区,再从缓冲区获取这个字节。

在NIO 中,提供了多种通道对象,而所有的通道对象都实现了 Channel 接口。它们之间的继承关系如下图所示:

 

 

 

 

 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值