java NIO

博主梳理了Java NIO相关源码及流程,介绍了NIO核心的Selector、SelectableChannel、SelectionKey三个对象的职责,详细阐述了Server端使用NIO的流程,还给出NIO服务端示例代码,最后总结了Selector操作及注意事项,强调通过它处理SocketChannel信息。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这两天看了下NIO相关的源码,梳理了下NIO的流程。我觉得NIO里面比较核心的三个对象是Selector,SelectableChannel,SelectionKey。

我说下我个人对三者职责的理解:

1、Selector

Selector是NIO里面SelectableChannel的多路复用器。他负责管理多个channel和key。

2、SelectableChannel

是可以被多路复用的channel,常用的实现类是ServerSocketChannel、SocketChannel等, 是用来管理socket连接的通道。

3、SelectionKey

这是一个用来维护Selector和一个SelectableChannel的关系,每个channel注册到selector里的时候都会产生一个selectionkey,会聚合channel和selector,有点类似EventKey。

 

接下来直接上图,解释下过程:

server端:
1.使用ServerSocketChannel.open()创建ServerSocketChannel对象(简称channel)。
2、使用Selector.open()创建Selector对象(简称selector)。
3、使用channel.regist(selector, SelectionKey)将channel注册给selector。 
4、注册的时候需要指定SelectionKey的operate值,是int类型,有四种类型OP_READ、OP_WRITE、
OP_CONNECT、OP_ACCEPT,分别对应的值是1,4,8,16。这样注册的时候会生成一个SelectionKey对象(简称key)。
SelectionKey相当于是个令牌,它把每个channel与唯一的selector进行关联。因此key中会聚合channel和selector
的引用。
5、注册的时候会把生成的key放到selector的key集合中。selector中有三个集合,key,selected-key,cancle-key,
select-key和cancle-key是key的子集。
6、轮询执行selector.select()方法,阻塞在这里等待连接或操作
7、客户端socket执行connect()
8、ServerSocketChannel会创建SocketChannel对象来创建socket连接跟客户端进行握手连接。
9、socketChannel创建之后会有机制进行唤醒selector
10、selector的select()方法开始执行
11、执行完select()之后,cancelled-key集合被清空,这个期间被注册产生的key会添加到selected-key集合中
12、这个时候可以拿到selected-key集合中的key,进行遍历处理,根据key的类型,可以进行不同的处理逻辑,
通常accept状态的key需要获得到ServerSocketChannel,然后通过ServerSocketChannel.accept()获取SocketChannel,
然后将SocketChannel再注册回selector里面,不过类型设置为Read或者Write。对于Read和Write类型的key,就可以
通过key.channel()获取SocketChannel对象,然后进行SocketChannel.write或者SocketChannel.read进行发送给端上数据
或读取client端发来的内容。
13、当client端往server端消息时,也会唤醒selector,继续进行10,11,12
 

以上属于自己理解和梳理的,有不对之处可以留言。

下面贴下NIO服务端的示例代码,在示例代码中客户端就是启动个socket就可以,所以客户端用不用nio无所谓。

public class Server {
    public static void main(String[] args) throws IOException {
        ServerSocketChannel ssh = ServerSocketChannel.open();
        ssh.bind(new InetSocketAddress(8888));
        ssh.configureBlocking(false);

        Selector selector = Selector.open();
//        Selector selector = SelectorProvider.provider().openSelector();  //这种创建方式也可以
        ssh.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("server started, listening on "+ssh.getLocalAddress());
        int count=0;

        while(true){
            selector.select();
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while(iterator.hasNext()){
                SelectionKey key = iterator.next();
                handler(key);
                iterator.remove();
            }
        }

    }

    private static void handler(SelectionKey key) {
        if(key.isAcceptable()){
            ServerSocketChannel channel = (ServerSocketChannel)key.channel();
            try {
                SocketChannel accept = channel.accept();
                accept.configureBlocking(false);
                accept.register(key.selector(), SelectionKey.OP_READ);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }else if(key.isReadable()){
            SocketChannel sc = (SocketChannel) key.channel();
            ByteBuffer wbb = ByteBuffer.allocate(1024);
            try {
                wbb.clear();
                sc.read(wbb);
                System.out.println(new String(wbb.array(),"gbk"));
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if(sc != null) {
                    try {
                        sc.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

 

总结:

Selector是NIO里面SelectableChannel的多路复用器。

Selector通过自己的open()创建,ServerSocketChannel(简称channel)也是通过自己的open()创建。

当channel.regist(selector,SelectionKey)的时候,一个SelectionKey会被创建,并且将channel和selector会聚合到SelectionKey里面,

同时这个key会被添加到selector的keyset集合里面。

当selector.select()的时候,selector里面的cancelled-keySet集合元素会被清除,cancelled-keySet集合会被清空,cancelled-keySet里面的key里的channel会取消注册。

然后这个key会添加到selector的selected-keyset集合里面,同时selector里面的Cancelkeyset也会被清除。

当key调用cancle()或者key的channel被close()的时候,这个key会被放置到selector的cancelkeyset里面。

通过调用set.remove()或者iterator.remove()可以将一个key从selected-keyset里面直接移除,但并不是移动到cancelkey集合里。

selector.select()是阻塞方法,要被打算阻塞的话,有三种方式:

1、调用selector.wakeup()方法

2、调用selector.close()方法

3、当前阻塞的线程调用thread.interrupt() (这种方式会修改线程状态的同时,调用selector.wakeup()方法)

 

通常来说,一个selector的set集合,在并发多线程的时候,是不安全的。 当set集合的iterator被创建,但是set集合的内容又被修改了的时候会抛出ConcurrentModificationException异常

 

selector.select()方法使用了synchronized(this),就是锁的selector自身,当有操作进行唤醒的时候,select()方法执行的时候依旧是锁的状态,这个时候其他线程触发selector.wakeup()的操作都不会生效,这时候操作的key都会作为准备放到selected-key中的状态,只有当下一次selector的select()方法被调用的时候,准备状态的key会被添加到selected-key集合中,然后就可以处理selected-key中所有的key。

 

一句话: 通过selector拿到所有的selected-key,就相当于拿到了所有可处理的channel,根据key的类型进行不同的处理,最终是要处理SocketChannel里面的信息。

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值