Java NIO - Selector

Java NIO - Selector

Selector能够检测多个注册的通道上是否有事件发生(多个channel以事件的方式可以注册到同一个selector)。如果有事件发生,便获取事件,然后对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个通道,也就是管理多个连接和请求

Selector类
Selector类是一个抽象类,说明如下:

public abstract class Selector implements Closeable

一些方法:

1.public static Selector open() - 得到一个选择器对象
2.public abstract int select(long timeout) - 监控所有注册的通道,当其中有IO操作可以进行时,将对应的SelectionKey加入到内部集合中并返回,参数用来设置超时时间

其方法说明如下:

Selects a set of keys whose corresponding channels are ready for I/O operations.
This method performs a blocking selection operation. It returns only after at least one channel is selected, this selector’s wakeup method is invoked, the current thread is interrupted, or the given timeout period expires, whichever comes first.
This method does not offer real-time guarantees: It schedules the timeout as if by invoking the Object.wait(long) method.
Params:
timeout – If positive, block for up to timeout milliseconds, more or less, while waiting for a channel to become ready; if zero, block indefinitely; must not be negative
Returns:
The number of keys, possibly zero, whose ready-operation sets were updated
Throws:
IOException – If an I/O error occurs
ClosedSelectorException – If this selector is closed
IllegalArgumentException – If the value of the timeout argument is negative

该方法执行一个blocking selection操作,直至至少一个channel被selected

public abstract int selectNow()则是非阻塞的

总结下:

  • selector.select() - 阻塞
  • selector.select(1000) - //阻塞1000毫秒,在1000毫秒后返回
  • selector.wakeup() - 唤醒selector
  • selector.selectNow() - 不阻塞,立马返还

3.public abstract Set<SelectionKey> selectedKeys() - 从内部集合中得到所有的SelectionKey

如何理解呢?
Selector是与Thread关联的,Selector调用select方法,这个方法就会返回一个集合(一个SelectionKey的集合),通过SelectionKey知道那个事件发生了,通过SelectionKey取到对应的channel来操作

NIO中的ServerSocketChannel功能类似于ServerSocketSocketChannel功能类似于Socket

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

  • 当客户端连接时,会通过ServerSocketChannel得到对应的SocketChannel
  • 将得到的SocketChannel注册到Selector上,一个Selector可以注册多个SocketChannel
  • 注册后返回一个SelectionKey,会和该Selector关联
  • Selector进行监听(select方法),会返回有事件发生的通道的个数
  • 进一步得到各个SelectionKey(有事件发生的)
  • 再通过SelectionKey反向获取注册的SocketChannel
  • 可以通过的channel,完成业务处理

SelectionKey

SelectionKey表示Selector和网络通道的注册关系,共四种:

public static final int OP_READ = 1 << 0; //代表读操作
public static final int OP_WRITE = 1 << 2; //代表写操作
public static final int OP_CONNECT = 1 << 3; //代表连接已经建立
public static final int OP_ACCEPT = 1 << 4; //有新的网络连接可以accept

一些方法:

  • public abstract Selector selector() - 得到与之关联的Selector对象
  • public abstract SelectableChannel channel() - 得到与之关联的通道
  • public final Object attachment() - 得到与之关联的共享数据
  • public abstract SelectionKey interestOps(int ops) - 设置或改变监听事件
  • public final boolean isAcceptable() - 是否可以accept
  • 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) - 注册一个选择器并设置监听事件

SocketChannel

SocketChannel,网络IO通道,具体负责进行读写操作。NIO把缓冲区的数据写入通道,或者把通道里的数据读到缓冲区

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

NIO非阻塞网络编程入门

按照上面的流程,实现一个服务端,一个客户端,如下:
服务端:

public class NIOServer {
    public static void main(String[] args) throws Exception {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //得到一个Selector对象
        Selector selector = Selector.open();
        //绑定一个端口,在服务端监听
        serverSocketChannel.socket().bind(new InetSocketAddress(6666));
        //设置为非阻塞监听
        serverSocketChannel.configureBlocking(false);
        //把serverSocketChannel注册到selector,关系事件为OP_ACCEPT
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        //循环等待客户端连接
        while (true) {
            //等待1s,如果没有事件发生
            if (selector.select(1000) == 0) {//没有事件发生
                System.out.println("服务器等待了1s,无连接");
                continue;
            }
            //如果返回>0,就获取到相关的SelectionKey集合
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            //通过selectionKeys 反向获取通道
            //遍历
            Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
            while (keyIterator.hasNext()) {
                //获取到selectionKey
                SelectionKey key = keyIterator.next();
                //根据key对应的通道做相应的处理
                if (key.isAcceptable()) {//如果是OP_ACCEPT
                    //给该客户端生成一个SocketChannel
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    //将socketChannel设置为非阻塞
                    socketChannel.configureBlocking(false);
                    System.out.println("客户端连接成功 生成了一个 socketChannel " + socketChannel.hashCode());
                    //将socketChannel注册到selector,关注事件为SelectionKey.OP_READ,同时给socketChannel关联一个Buffer
                    socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                }
                if (key.isReadable()) {//发生了OP_READ事件
                    //通过key 反向获取对应的Channel
                    SocketChannel channel = (SocketChannel)key.channel();
                    //获取到该channel关联的buffer
                    ByteBuffer buffer = (ByteBuffer)key.attachment();
                    channel.read(buffer);
                    System.out.println("from 客户端 " + new String(buffer.array()));
                }

                //手动从集合中移除当前的selectionKey,放置重复操作
                keyIterator.remove();

            }
        }
    }
}

客户端

public class NIOClient {
    public static void main(String[] args) throws Exception{
        //得到一个网络通道
        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, I am client";
        //Wraps a byte array into a buffer.
        ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes());
        //发送数据,将buffer数据写入channel
        socketChannel.write(byteBuffer);
        //代码停在这儿
        System.in.read();
    }
}

客户端向服务端发送数据:
连接打印

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Java NIO 中的 Selector 可以用于多路复用 I/O,它可以同时监控多个 Channel 的 IO 状态,如读写就绪等,从而让你的程序可以同时处理多个网络连接。 使用 Selector 的基本流程如下: 1. 创建 Selector 对象:使用 `Selector.open()` 方法。 2. 创建并配置 Channel:每个 Channel 都必须注册到 Selector 上。 3. 向 Selector 注册感兴趣的事件:使用 `SelectionKey` 对象将 Channel 和感兴趣的事件绑定。 4. 通过 `select()` 方法监控 Channel:该方法会阻塞,直到至少有一个 Channel 处于就绪状态。 5. 处理就绪的 Channel:通过 `selectedKeys()` 方法获取所有就绪的 Channel,然后遍历每一个 Key,并根据 Key 的事件状态进行相应的处理。 6. 关闭 Selector:使用 `close()` 方法关闭 Selector。 以上就是 Selector 的基本使用方法。希望这些信息能帮助你理解和使用 Java NIO 中的 Selector。 ### 回答2: Java NIO(New Input/Output)提供了一种非阻塞I/O的能力,其中的selector是一种重要的组件。它允许程序通过一个单线程来监听多个通道上的事件并做出相应的处理。 使用Selector主要包括以下步骤: 1. 创建Selector实例: Selector selector = Selector.open(); 2. 创建Channel并设置为非阻塞模式: 在使用Selector之前,需要确保Channel处于非阻塞模式,例如SocketChannel或ServerSocketChannel: SocketChannel socketChannel = SocketChannel.open(); socketChannel.configureBlocking(false); 3. 将Channel注册到Selector上: 通过SelectionKey来表示Channel的注册状态,包括感兴趣的操作集合及其附加的数据。可以使用以下方法将Channel注册到Selector上: SelectionKey key = socketChannel.register(selector, SelectionKey.OP_READ); 4. 进行事件监听: 使用Selector的select()方法进行事件监听,它会阻塞,直到有一个或多个事件发生: int readyChannels = selector.select(); if (readyChannels == 0) { continue; } 5. 获取已就绪的事件集合: 通过调用selector.selectedKeys()方法获取已经就绪的事件集合: Set<SelectionKey> selectedKeys = selector.selectedKeys(); 6. 遍历已就绪的事件集合并处理: 遍历selectedKeys集合,处理每一个就绪的事件: Iterator<SelectionKey> keyIterator = selectedKeys.iterator(); while (keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); if (key.isReadable()) { // 可读事件处理逻辑 } if (key.isWritable()) { // 可写事件处理逻辑 } keyIterator.remove(); // 处理完毕后需要手动移除该事件,避免重复处理 } 7. 关闭Selector: 使用完Selector后需要及时关闭: selector.close(); 使用Selector可以实现多个通道的事件监听和处理,极大地提高了应用程序的性能和资源利用率。需要注意的是,在使用Selector时,一个线程可以管理多个Channel,但要谨慎处理每个Channel上的事件,以避免阻塞整个Selector处理线程。 ### 回答3: Java NIO(New I/O)是一种非阻塞I/O操作的Java API。它提供了一组用于高效处理I/O操作的类和接口。其中,SelectorNIO的核心组件之一,用于实现非阻塞I/O。 Selector是一个类似于调度员的对象,它可以同时监视多个通道的I/O事件。使用Selector可以实现单线程同时管理多个通道的I/O操作,提高了系统的效率。 使用Selector的主要步骤如下: 1. 创建一个Selector对象:通过调用Selector.open()方法创建一个Selector对象。 2. 将通道注册到Selector上:将需要监视的通道注册到Selector上,例如SocketChannel、ServerSocketChannel等。通过调用通道的register()方法完成注册。 3. 设置通道的非阻塞模式:通过调用通道的configureBlocking(false)方法将通道设置为非阻塞模式。 4. 选择通道:通过调用Selector的select()方法选择通道,并返回已准备就绪的通道的数量。 5. 处理选择的通道:通过调用Selector的selectedKeys()方法获取选择的通道集合,可以通过遍历通道集合进行相应的读写操作。 6. 取消选择的通道:通过调用SelectionKey的cancel()方法取消选择的通道的注册。 示例代码如下: ```java Selector selector = Selector.open(); SocketChannel socketChannel = SocketChannel.open(); socketChannel.configureBlocking(false); socketChannel.connect(new InetSocketAddress("example.com", 80)); socketChannel.register(selector, SelectionKey.OP_CONNECT); while (true) { int readyChannels = selector.select(); if (readyChannels == 0) { continue; } Set<SelectionKey> selectedKeys = selector.selectedKeys(); Iterator<SelectionKey> keyIterator = selectedKeys.iterator(); while (keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); if (key.isConnectable()) { // 处理连接就绪的通道 SocketChannel channel = (SocketChannel) key.channel(); if (channel.isConnectionPending()) { channel.finishConnect(); } channel.configureBlocking(false); channel.register(selector, SelectionKey.OP_READ); } else if (key.isReadable()) { // 处理读就绪的通道 SocketChannel channel = (SocketChannel) key.channel(); ByteBuffer buffer = ByteBuffer.allocate(1024); channel.read(buffer); buffer.flip(); // 处理读取到的数据 } keyIterator.remove(); } } ``` 以上是一个简单的Selector的使用示例,通过这些步骤,可以实现对多个通道的非阻塞I/O操作的监视和处理。需要注意的是,Selector是基于事件驱动的,可以实现高效的I/O操作。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值