selector选择器

基本介绍

  1. java的NIO,用非阻塞的IO方式,可以用一个线程,处理多个客户端连接,就会使用到selector(选择器)
  2. selector能够检测多个注册的通道上是否有事件的发生(注意: 多个Channel以事件的方式可以注册到同一个selector), 如果有事件发生,便获取事件然后针对每个事件进行相应的处理. 这样就可以只用一个单线程去管理多个通道,也就是管理多个连接和请求
  3. 只有在连接/通道 真正有读写事件发生时, 才会进行读写, 就大大地减少了系统开销, 并且不必为每一个连接都创建线程,不用去维护多个线程
  4. 避免了多线程之间的上下文切换导致的开销

特点再说明:

  1. Netty的IO线程NioEventLoop聚合了Selector(选择器)也叫多路复用器, 可以同时并发处理成百上千个客户端连接
  2. 当线程从某客户端Socket通道进行数据读写时, 若没有线程可用时, 该线程可以进行其他任务
  3. 线程通常将非阻塞IO的空闲时间用于在其他通道上执行IO操作,所以单独的线程可以管理多个输入和输出
  4. 由于读写都是非阻塞的,这就可以充分提升IO线程的运行效率,避免由于频繁I/O阻塞导致的线程挂起
  5. 一个I/O线程可以并发处理N个客户端连接和读写操作,这从根本上解决了传统阻塞I/O一连接一线程的模型,架构性能,弹性伸缩能力和可靠性都得到了极大的提升

Selector类相关方法

selector类是一个抽象类,常用方法说明如下:

  1. public static Selector open() // 得到一个选择器对象
  2. public abstract int select(long timeout); // 监控所有注册的通道,当其中有IO操作可以进行时,将对应的selectorKey加入内部集合并返回,参数用来设置超时时间
  3. public abstract Set selectedKeys(); // 从内部集合中得到所有的SelectorKey
    在这里插入图片描述

注意事项

  1. NIO中的ServerSocketChannel功能类似 ServerSocket, SocketChannel功能类似Socket
  2. selector相关方法说明
    selector.select() // 阻塞获取直到下一次事件出现
    selector.select(1000);// 阻塞1000毫秒,若期间无事件则1000毫秒后返回
    selector.wakeup(); // 唤醒selector
    selector.selectNow()// 不阻塞,立马返回

NIO非阻塞网络编程原理分析图

NIO非阻塞网络编程相关的(Selector, SelectionKey,ServerSocketChannel,和SocketChannel)关系梳理图
在这里插入图片描述
对上图说明

  1. 当客户端连接时,会通过serverSocketChannel得到SocketChannel
  2. selector进行监听select()方法,返回有事件发生的通道的个数
  3. 将socketChannel注册到selector上,
    public final SelectionKey register(Selector sel, int ops)
    一个selector可以注册多个socketChannel
  4. 注册之后会返回一个selectionKey,会和该selector关联(集合)
  5. 进而得到各个 SelectionKey (有事件发生的)
  6. 在通过SelectionKey 反向获取socketChannel
  7. 可以通过获取的channel完成对应的业务处理

代码示例

  1. 编写一个NIO入门案例,实现服务端和客户端之间的简单数据通讯(非阻塞)
  2. 目的: 理解NIO非阻塞网络编程机制

server服务端代码

package com.ding.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Set;

public class NioServer {
    public static void main(String[] args) throws IOException {
        // 创建serverSocketChannel -> serverSocket
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        // 得到一个selector对象
        Selector selector = Selector.open();
        // 绑定一个端口6666,在服务器端监听
        serverSocketChannel.socket().bind(new InetSocketAddress(6666));
        // 设置为非阻塞
        serverSocketChannel.configureBlocking(false);
        // 将 serverSocketChannel注册到selector,关心事件为op_Accept
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        // 循环等待客户端连接
        while (true) {
            // 这里等待一秒,若无事件发生就返回
            if (selector.select(1000) == 0) {
                // 等待一秒若无事件发生则跳出循环
                System.out.println("服务器等待了一秒, 无连接");
                continue;
            }
            // 如果有返回代表有事件发生 --> 获取相关的selectionKeys集合
            // 1.如果返回大于0,表示已经有关注的事件发生
            // 2.selector.selectedKeys() 获取关注事件的集合
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            // 便利集合,使用迭代器
            for (SelectionKey selectionKey : selectionKeys) {
                // 获取到当前的selectionKey
                // 查看这个key对应的通道发生的事件做不同的处理
                if (selectionKey.isAcceptable()) {// OP_ACCEPT
                    // 有新的客户端连接 -- > 给该客户端生成socketChannel
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    System.out.println("客户端连接成功 生成一个 socketChannel =="+ socketChannel.hashCode());
                    // 将socketChannel 设置为非阻塞
                    socketChannel.configureBlocking(false);
                    // 将当前的socketChannel注册到selector上面, 关注事件为读OP_READ,并且关联一个buffer
                    socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                }
                if (selectionKey.isReadable()) {
                    // 发生OP_READ 可读的
                    // 通过key反向获取对应的channel
                    SocketChannel channel = (SocketChannel)selectionKey.channel();
                    // 获取到该channel关联的buffer
                    ByteBuffer byteBuffer = (ByteBuffer) selectionKey.attachment();
                    // 开始读取数据 --> 把channel数据读入buffer
                    channel.read(byteBuffer);
                    System.out.println("from 客户端=" + new String(byteBuffer.array()));
                }
                // 从集合中移出当前的selectionKey,防止重复
                selectionKeys.remove(selectionKey);
            }
        }
    }
}

客户端代码

package com.ding.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class NioClient {
    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 world";
            // 将字节数组包装到缓冲区中。
            ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes());
            // 发送数据,将buffer数据写入cahnnel
            socketChannel.write(byteBuffer);
            System.in.read();

        }
    }
}

SelectionKey

相关方法
在这里插入图片描述

public abstract Selector selector(); // 得到与之关联的selector对象
public abstract SelectableChannel channel(); // 得到与之关联的通道channel
public final Object attachment() // 得到与之关联的共享数据, 例如Buffer
public abstract SelectionKey interestOps(int ops); // 设置或改变监听事件
public final boolean isAcceptable() // 是否可以accpet
public final boolean isReadable() // 是否可以读
public final boolean isWritable() // 是否可以写

ServerSocketChannel

ServerSocketChannel在服务器端监听客户端Socket连接
相关方法如下
在这里插入图片描述

  1. public static ServerSocketChannel open() // 得到一个ServerSocketChannel通道
  2. public final ServerSocketChannel bind(SocketAddress local) // 设置服务器端端口号
  3. public final SelectableChannel configureBlocking(boolean block) // 设置阻塞或非阻塞模式,取值false表示采用非阻塞模式
  4. public abstract SocketChannel accept() // 接受一个连接,返回代表这个连接的通道对象,为这个连接构建对应通道
  5. public final SelectionKey register(Selector sel, int ops) // 注册一个选择器并设置监听事件
  6. SelectionKey.OP_ACCEPT; // 建立连接
    SelectionKey.OP_READ; // 读取客户端传来数据
    SelectionKey.OP_CONNECT; // 连接建立成功
    SelectionKey.OP_WRITE; // 给客户端写数据

SocketChannel

  1. SocketChannel, 网络通道,具体负责进行读写操作. NIO把缓冲区的数据写入通道,或者把通道中的数据读到缓冲区
  2. 相关方法如下

public static SocketChannel open(); //得到一个SocketChannel通道
public final SelectableChannel configureBlocking(boolean block); //设置阻塞或非阻塞模式, 取值false表示采用非阻塞模式
public abstract boolean connect(SocketAddress remote); // 连接服务器
public abstract boolean finishConnect(); // 如果上面的方法连接失败,接下来就要通过该方法完成连接操作
public abstract int write(ByteBuffer src); //往通道里面写数据
public abstract int read(ByteBuffer dst); //从通道里读数据
public final SelectionKey register(Selector sel, int ops,Object att);//注册一个选择器,并设置监听事件,最后一个参数可以设置共享数据
public final void close(); //关闭通道

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值