Day14.高性能RPC设计 学习笔记2

一、通道选择器

nio

通道注册:需要使用Selector管理通道,然后将就绪的通道封装成SelectionKey对象。

  • 设置通道为非阻塞 ServerSocketChannel/SocketChannel#configureBlocking(false)
  • 注册通道ServerSocketChannel/SocketChannel#register(selector,事件类型[,附件信息])

NIO的网络编程的思想是基于异步事件处理,底层通过Selector去管理注册列表,一旦注册列表的相关通道就绪,selector就会将就绪的通道放置在事件处理队列中,用户可以通过Selector#selectedKeys()获取就绪的keys

所有就绪的key只能被处理一次,因此用户必须在处理完事件key后,将该事件在事件处理列表中移除。

事件取消注册通道关闭,key#cancel()

ServerSocketChannel ssc=ServerSocketChannel.open();
ssc.bind(new InetSocketAddress(9999));
//设置通道非阻塞
ssc.configureBlocking(false);

//创建通道选择器
Selector selector=Selector.open();
//注册ACCEPT事件类型 转发
ssc.register(selector,SelectionKey.OP_ACCEPT);

//迭代遍历事件key
while(true){
    //返回需要处理的事件个数,如果没有该方法会阻塞,也有可能直接返回0(当程序调用Selector#wakeup)
    int num = selector.select();
    if(num >0){
        //事件处理
        Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
        while(keys.hasNext()){
            SelectionKey key = keys.next();
            //处理对应的事件key
            if(key.isAcceptable()){
                //处理转发事件
                ServerSocketChannel channel= (ServerSocketChannel) key.channel();
                
                SocketChannel s=channel.accept();//立即返回一个不为null的SocketChannel
                s.configureBlocking(false);//注册读
                s.register(selector,SelectionKey.OP_READ);
            }else if(key.isReadable()){
                //处理读事件
               SocketChannel s= (SocketChannel) key.channel();
                //处理读
                ...
                //注册写
                s.register(selector,SelectionKey.OP_WRITE[,请求参数]);  
            }else if(key.isWritable()){
                //处理写事件
                SocketChannel s= (SocketChannel) key.channel();
                //根据请求参数给出响应
                ...
                s.shutdownOutput();//告知写结束
                s.close()}
            //移除key
            keys.remove();
        }
    }
}

二、NIO单线程版本

public class NIOBootstrapServer {
    public static void main(String[] args) throws IOException {
        //1、创建ServerSocket
        ServerSocketChannel ssc=ServerSocketChannel.open();
        //2、绑定监听端口
        ssc.bind(new InetSocketAddress(9999));
        //3、设置通道非阻塞
        ssc.configureBlocking(false);

        //4、创建通道选择器
        Selector selector= Selector.open(); //nio多是open来注册东西
        //5、注册ACCEPT事件类型 转发
        ssc.register(selector,SelectionKey.OP_ACCEPT);

        //迭代遍历事件key
        while(true){
            //返回需要处理的事件个数,如果没有该方法会阻塞,也有可能直接返回0(当程序调用Selector#wakeup)
            System.out.println("尝试选择待处理的keys...");
            //可以出来keys的数目,如果没有该方法block
            int num = selector.select();

            if(num >0){
                //事件处理
                Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
                while(keys.hasNext()){
                    SelectionKey key = keys.next();
                    //处理对应的事件key
                    if(key.isAcceptable()){
                        System.out.println("处理转发同时注册读...");
                        //处理转发事件
                        ServerSocketChannel channel= (ServerSocketChannel) key.channel();

                        SocketChannel s=channel.accept();//立即返回一个不为null的SocketChannel
                        s.configureBlocking(false);//设置非阻塞
                        //注册读
                        s.register(selector,SelectionKey.OP_READ,new ByteArrayOutputStream());
                    }else if(key.isReadable()){
                        System.out.println("处理读...");
                        //处理读事件
                        SocketChannel s= (SocketChannel) key.channel(); //拿到注册过的SocketChannel
                        //处理读
                        ByteBuffer buffer=ByteBuffer.allocate(1024);
                        ByteArrayOutputStream baos= (ByteArrayOutputStream) key.attachment();

                        //一次尝试读取一个缓冲区
                        int n=s.read(buffer);
                        if(n==-1){
                            System.out.println("服务器收到:"+new String(baos.toByteArray()));
                            //根据请求参数给出响应
                            ByteArrayInputStream bais=new ByteArrayInputStream((new Date().toLocaleString()).getBytes());
                            //注册写
                            s.register(selector, SelectionKey.OP_WRITE,bais);
                        }else{
                            buffer.flip();
                            baos.write(buffer.array(),0,n);
                        }

                    }else if(key.isWritable()){
                        System.out.println("处理写...");
                        //处理写事件
                        SocketChannel s= (SocketChannel) key.channel();
                        ByteArrayInputStream bais = (ByteArrayInputStream)key.attachment();

                        byte[] bytes=new byte[1024];
                        int n = bais.read(bytes);
                        if(n==-1){
                            s.shutdownOutput();//告知写结束
                            s.close(); //关闭通道
                        }else{
                            s.write(ByteBuffer.wrap(bytes,0,n));
                        }
                    }
                    //移除key
                    keys.remove();
                }
            }
        }
    }
}

三、NIO多线程版【了解】

图02

依赖

<dependency>
   <groupId>org.apache.commons</groupId>
   <artifactId>commons-lang3</artifactId>
   <version>3.6</version>
</dependency>

完整实现如下

public class NIOBootstrapServerPool {
    //该线程池主要负责请求的转发
    private static ExecutorService master= Executors.newFixedThreadPool(66);
    //该线程池主要负责请求的响应
    private static ExecutorService worker= Executors.newFixedThreadPool(66);

    //注册转发队列
    private static final AtomicBoolean NEED_REG_DISPATH= new AtomicBoolean(false);
    //注册读队列
    private static final List<ChannelAndAtt> READ_QUEUE= new Vector<ChannelAndAtt>();
    //注册写队列
    private static final CopyOnWriteArrayList<ChannelAndAtt> WRITE_QUEUE= new CopyOnWriteArrayList<ChannelAndAtt>();

    public static void main(String[] args) throws IOException {
        //创建serverSocket
        ServerSocketChannel ssc=ServerSocketChannel.open();
        ssc.bind(new InetSocketAddress(9999));
        //设置通道非阻塞
        ssc.configureBlocking(false);
        //创建通道选择器selector
        Selector selector= Selector.open();
        //注册ACCEPT事件类型 转发
        ssc.register(selector,SelectionKey.OP_ACCEPT);

        //迭代遍历事件key
        while(true){
            //返回需要处理的事件个数,如果没有该方法会阻塞,也有可能直接返回0(当程序调用Selector#wakeup)
            // System.out.println("尝试选择待处理的keys...");
            int num = selector.select(1);
            if(num >0){
                //事件处理
                Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
                while(keys.hasNext()){
                    SelectionKey key = keys.next();
                    //处理对应的事件key
                    if(key.isAcceptable()){ //请求转发
                        key.cancel();//取消转发注册
                        master.submit(new ProcessDispatcher(key,selector));
                    }else if(key.isReadable()){ //读取IO处理
                        key.cancel();//取消读注册
                        worker.submit(new ProcessRead(key,selector));
                    }else if(key.isWritable()){ //响应IO处理
                        key.cancel();//取消写注册
                        worker.submit(new ProcessWrite(key,selector));
                    }
                    //删除当前事件key,删除并不意味着取消注册
                    keys.remove();
                }
            }else {
                if(NEED_REG_DISPATH.get()){//需要重新注册ACCEPT
                    System.out.println("重新注册ACCEPT");
                    ssc.register(selector,SelectionKey.OP_ACCEPT);
                    NEED_REG_DISPATH.set(false);
                }
                while(READ_QUEUE.size()>0){
                    ChannelAndAtt channelAndAtt = READ_QUEUE.remove(0);
                    //注册读
                    System.out.println("注册READ");
                    channelAndAtt.getChannel().register(selector,SelectionKey.OP_READ,channelAndAtt.att);
                }
                while(WRITE_QUEUE.size()>0){
                    ChannelAndAtt channelAndAtt = WRITE_QUEUE.remove(0);
                    //注册写
                    System.out.println("注册写");
                    channelAndAtt.getChannel().register(selector,SelectionKey.OP_WRITE,channelAndAtt.att);
                }
            }
        }
    }
    /**
     * 处理请求写
     */
    public static class ProcessWrite implements Runnable{
        private SelectionKey key;
        private Selector selector;

        public ProcessWrite(SelectionKey key, Selector selector) {
            this.key = key;
            this.selector = selector;
        }
        @Override
        public void run() {
            try {
                SocketChannel s= (SocketChannel) key.channel();
                ByteArrayInputStream bais = (ByteArrayInputStream)key.attachment();

                byte[] bytes=new byte[1024];
                int n = bais.read(bytes);//最多从bais获取一个缓冲区的数据

                if(n==-1){
                    s.shutdownOutput();//告知写结束
                    s.close(); //关闭通道
                }else{
                    //最多写一个缓冲区的数据
                    s.write(ByteBuffer.wrap(bytes,0,n));
                    //恢复写注册
                    WRITE_QUEUE.add(new ChannelAndAtt(s,bais));
                }
                //打破main线程
                selector.wakeup();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    /**
     * 处理请求读
     */
    public static class ProcessRead implements Runnable{
        private SelectionKey key;
        private Selector selector;

        public ProcessRead(SelectionKey key, Selector selector) {
            this.key = key;
            this.selector = selector;
        }
        @Override
        public void run() {
            try {
                //处理读事件
                SocketChannel s= (SocketChannel) key.channel();
                //处理读
                ByteBuffer buffer=ByteBuffer.allocate(1024);
                ByteArrayOutputStream baos= (ByteArrayOutputStream) key.attachment();

                int n=s.read(buffer);
                if(n==-1){
                    //根据请求参数给出响应
                    Object req= SerializationUtils.deserialize(baos.toByteArray());
                    System.out.println("服务器收到:"+req);
                    ByteArrayInputStream bais=new ByteArrayInputStream(SerializationUtils.serialize(new Date()));
                    //注册写
                    WRITE_QUEUE.add(new ChannelAndAtt(s,bais));
                }else{
                    buffer.flip();
                    baos.write(buffer.array(),0,n);
                    //恢复读注册
                    READ_QUEUE.add(new ChannelAndAtt(s,baos));
                }
                //打断mian线程阻塞
                selector.wakeup();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    /**
     * 处理请求转发
     */
    public static class ProcessDispatcher implements Runnable{
        private SelectionKey key;
        private Selector selector;

        public ProcessDispatcher(SelectionKey key, Selector selector) {
            this.key = key;
            this.selector = selector;
        }
        @Override
        public void run() {
            try {
                //获取通道
                ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                SocketChannel s = ssc.accept();
                s.configureBlocking(false);
                //将需要重新注册
                NEED_REG_DISPATH.set(true);
                //注册读
                READ_QUEUE.add(new ChannelAndAtt(s,new ByteArrayOutputStream()));
            } catch (IOException e) {
                e.printStackTrace();
            }
            //打断mian线程阻塞
            selector.wakeup();
        }
    }
    
    public static class ChannelAndAtt{
        private SelectableChannel channel;
        private Object att;

        public ChannelAndAtt(SelectableChannel channel, Object att) {
            this.channel = channel;
            this.att = att;
        }

        public SelectableChannel getChannel() {
            return channel;
        }

        public void setChannel(SelectableChannel channel) {
            this.channel = channel;
        }

        public Object getAtt() {
            return att;
        }

        public void setAtt(Object att) {
            this.att = att;
        }
    }
}
  • 一个小小的分析:
    宏观上来看,首先两个线程池master主负责转发和worker子负责读写;另外三个变量,AtomicBoolean表示当前是否需要注册转发,和另外两个读 与 写队列。
    在main函数中,所有的处理都先是key.cancel( ),为了防止一个事件被多线程同时处理。在子线程中分别操纵那三个变量。
    要重复注册请求转发的时候,还需要读用户请求,所以操作两个变量;select()中增加timeout参数可以自动打破阻塞,可以及时的进行注册。
    在各个变量对应的方法中,先拿通道,再那对应的事件,进行核心业务读缓冲区,或者写数据之类的操作。写完之后shutdownOutput(),关流操作。

四、RPC 设计雏形

图图
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值