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 类是一个抽象类, 常用方法和说明如下:
public abstract class Selector implements Closeable {
public static Selector open();//得到一个选择器对象
public int select(long timeout);//监控所有注册的通道,当其中有 IO 操作可以进行时,将对应的 SelectionKey 加入到内部集合中并返回,参数用来设置超时时间
public Set<SelectionKey> selectedKeys();//从内部集合中得到所有的 SelectionKey
}

注意事项

  1. NIO中的 ServerSocketChannel功能类似ServerSocket,SocketChannel功能类似Socket
  2. selector 相关方法说明
    selector.select()//阻塞
    selector.select(1000);//阻塞1000毫秒,在1000毫秒后返回
    selector.wakeup();//唤醒selector
    selector.selectNow();//不阻塞,立马返还

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

在这里插入图片描述

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

代码

服务端

package com.jhj.channel;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
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();
        //绑定端口
        serverSocketChannel.socket().bind(new InetSocketAddress(666));
        //设置为非阻塞
        serverSocketChannel.configureBlocking(false);
        //把serverSocketChannel注册到selector关心事件为OP_ACCEPT
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        //循环等待客户端连接
        while (true){
            //等待1s,如果没有事件发生就返回
            if (selector.select(1000)==0){
                System.out.println("服务器等待1秒,无连接");
                continue;
            }
            //如果返回的大于0 获取到相关的selectionKey集合
            //如果返回大于0,表示获取到关注的事件了
            //返回关注事件的集合
            //通过selectionKeys 反向获取通道
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            //遍历set
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()){
                //获取到selectionKey
                SelectionKey next = iterator.next();
                //根据key对应的通道发生的事件做相应的处理
                if (next.isAcceptable()){
                    //如果是Op_Accept 则是有客户端来连接我
                    //给该客户端生成一个SocketChannel
                    SocketChannel accept = serverSocketChannel.accept();
                    System.out.println("客户端连接成功,生成了一个socketChannel"+accept.hashCode());
                    //将socketChannel设置非阻塞
                    accept.configureBlocking(false);
                    //将当前的socketChannel 注册到selector,关注事件为 Op_Read,同时给socketChannel关联一个Buffer
                    accept.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));

                }


                if (next.isReadable()){
                    //发生Op_Read

                    //通过key反向获取对应的channel
                    SocketChannel channel = (SocketChannel)next.channel();

                    //获取到该channel关联的buffer
                    ByteBuffer buffer = (ByteBuffer)next.attachment();
                    //读
                    channel.read(buffer);
                    System.out.println("客户端发送的数据是"+new String(buffer.array()));
                }

                //手动从集合中移动当前的selectionKey,防止重复操作
                iterator.remove();
            }
        }
    }
}

客户端

package com.jhj.channel;

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 open = SocketChannel.open();
        //设置非阻塞
        open.configureBlocking(false);
        //提供服务ip和端口
        InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 666);
        //连接服务器
        if (!open.connect(inetSocketAddress)){
            while (!open.finishConnect()){
                System.out.println("因为连接需要时间,客户端不会阻塞,可以做其他工作");
            }
        }
        //如果连接成功,发送数据
        String str="hello";
        ByteBuffer wrap = ByteBuffer.wrap(str.getBytes());
        //发送数据将buffer写入channel
        open.write(wrap);

        System.in.read();

    }
}

SelectionKey

  1. SelectionKey,表示 Selector 和网络通道的注册关系, 共四种:
    int OP_ACCEPT:有新的网络连接可以 accept,值为 16
    int OP_CONNECT:代表连接已经建立,值为 8
    int OP_READ:代表读操作,值为 1
    int OP_WRITE:代表写操作,值为 4
    源码中:
    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;
public abstract class SelectionKey {
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

  1. ServerSocketChannel 在服务器端监听新的客户端 Socket 连接
  2. 相关方法如下
public abstract class ServerSocketChannel
extends AbstractSelectableChannel
implements NetworkChannel{
public static ServerSocketChannel open(),得到一个 ServerSocketChannel 通道
public final ServerSocketChannel bind(SocketAddress local),设置服务器端端口号
public final SelectableChannel configureBlocking(boolean block),设置阻塞或非阻塞模式,取值 false 表示采用非阻塞模式
public SocketChannel accept(),接受一个连接,返回代表这个连接的通道对象
public final SelectionKey register(Selector sel, int ops),注册一个选择器并设置监听事件
}

SocketChannel

  1. SocketChannel,网络 IO 通道,具体负责进行读写操作。NIO 把缓冲区的数据写入通道,或者把通道里的数据读到缓冲区。
  2. 相关方法如下
public abstract class SocketChannel
extends AbstractSelectableChannel
implements ByteChannel, ScatteringByteChannel, GatheringByteChannel, NetworkChannel{
public static SocketChannel open();//得到一个 SocketChannel 通道
public final SelectableChannel configureBlocking(boolean block);//设置阻塞或非阻塞模式,取值 false 表示采用非阻塞模式
public boolean connect(SocketAddress remote);//连接服务器
public boolean finishConnect();//如果上面的方法连接失败,接下来就要通过该方法完成连接操作
public int write(ByteBuffer src);//往通道里写数据
public int read(ByteBuffer dst);//从通道里读数据
public final SelectionKey register(Selector sel, int ops, Object att);//注册一个选择器并设置监听事件,最后一个参数可以设置共享数据
public final void close();//关闭通道

作者声明

如有问题,欢迎指正!
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值