Netty(三)——Reactor模式(单Reactor)

前言

上一篇Netty的博客,说明了BIO与NIO,这两者最大的差别提现在多路IO复用上。

什么是reactor模式

reactor模式是一种典型的事件驱动模式,同时在之前的BIO实例,详细解读了BIO的阻塞问题。如果想要通过文字理解Reactor模式,确实很困难。如果通过代码在理解了NIO的基础上,再来看Reactor模式就相对容易一些。所以在理解Reactor模式之前最好需要理解NIO的详细实例,理解Channel与Selector的注册关系。如果要在这个阶段弄懂Reactor模式是什么,这里推荐这篇博文:什么是reactor模式

单线程的reactor模式

在reactor模式中有最重要的两个组件,一个是Reactor反应器(这个就是Selector)另一个是Handler处理器,反应器发现某一个事件注册好了之后,就将事件发送给相应的Handler处理器。一张图或许更容易理解:

上面这幅图就比较好理解了,针对客户端请求,服务端首先注册到Selector上,然后根据注册的事件类型将任务分发给不同的Handler,如果是连接就绪则将其分发给连接成功的处理类(Acceptor) 如果是业务的读写请求,则将其分发给业务处理的Handler。

同时还需要知道的是:SelectorKey中有两个重要的方法

public final Object attach(Object ob) {
    return attachmentUpdater.getAndSet(this, ob);
}

public final Object attachment() {
    return attachment;
}

attach方法其实就是将处理器附加到指定的SelectionKey上,而attachment方法即获取与SelectionKey上绑定的处理类。 

服务端的代码:

package com.learn.reactor.singlereactormodel;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.util.Iterator;
import java.util.Set;

/**
 * autor:liman
 * createtime:2019/10/15
 * comment:单Reactor线程模型的服务端实现
 * Reactor——负责监听就绪事件,并对事件进行分发处理
 */
public class Reactor implements Runnable{

    private final Selector selector;
    private final ServerSocketChannel serverSocketChannel;

    public Reactor(int port) throws IOException {
        selector = Selector.open();
        serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().bind(new InetSocketAddress(port));
        serverSocketChannel.configureBlocking(false);

        //将ServerSocketChannel注册到selector上,订阅ACCEPT事件
        SelectionKey keys = serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);

        //绑定连接建立的处理对象
        keys.attach(new ServerAcceptor(serverSocketChannel,selector));
    }

    @Override
    public void run() {
        try{
            while(!Thread.interrupted()){
                selector.select();//就绪事件到达之前阻塞
                Set selected = selector.selectedKeys();
                Iterator iterator = selected.iterator();
                while(iterator.hasNext()){
                    //进行任务的分发
                    dispatch((SelectionKey)iterator.next());
                }
                selected.clear();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    private void dispatch(SelectionKey key) {
        //这里并没有做区分,任务的分发依旧交给了服务器主线程去处理。
        Runnable runThread = (Runnable) key.attachment();
        if(runThread!=null){
            runThread.run();
        }
    }

    public static void main(String[] args) throws IOException {
        new Thread(new Reactor(8888)).start();
    }
}

上述代码最核心的其实就是任务的分发。

 连接建立的处理对象

package com.learn.reactor.singlereactormodel;

import lombok.extern.slf4j.Slf4j;

import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

/**
 * autor:liman
 * createtime:2019/10/15
 * comment:
 */
@Slf4j
public class ServerAcceptor implements Runnable{

    private final Selector selector;

    private final ServerSocketChannel serverSocketChannel;

    public ServerAcceptor(ServerSocketChannel serverSocketChannel, Selector selector) {
        this.selector = selector;
        this.serverSocketChannel = serverSocketChannel;
    }


    @Override
    public void run() {
        SocketChannel socketChannel;
        try{
            socketChannel = serverSocketChannel.accept();
            if(socketChannel!=null){
                log.info("收到来自{}的连接",socketChannel.getRemoteAddress());
                //处理SocketChannel的读写业务
                new ServerHandler(socketChannel,selector);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

真正的业务处理类:

package com.learn.reactor.singlereactormodel;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;

/**
 * autor:liman
 * createtime:2019/10/15
 * comment:
 */
@Slf4j
public class ServerHandler implements Runnable {

    private final SelectionKey selectionKey;
    private final SocketChannel socketChannel;

    private ByteBuffer readBuffer = ByteBuffer.allocate(1024);
    private ByteBuffer writeBuffer = ByteBuffer.allocate(2048);

    private final static int READ = 0;
    private final static int SEND = 1;

    private int status = READ;

    public ServerHandler(SocketChannel socketChannel, Selector selector) throws IOException {

        this.socketChannel = socketChannel;

        this.socketChannel.configureBlocking(false);
        selectionKey = socketChannel.register(selector, 0);
        selectionKey.attach(this);//将读写的处理类绑定为当前的业务类。
        selectionKey.interestOps(SelectionKey.OP_READ);
//        selector.wakeup();
    }


    @Override
    public void run() {
        try {
            switch (status) {
                case READ:
                    read();
                    break;
                case SEND:
                    send();
                    break;
                default:
            }
        } catch (IOException e) { //这里的异常处理是做了汇总,常出的异常就是server端还有未读/写完的客户端消息,客户端就主动断开连接,这种情况下是不会触发返回-1的,这样下面read和write方法里的cancel和close就都无法触发,这样会导致死循环异常(read/write处理失败,事件又未被cancel,因此会不断的被select到,不断的报异常)
            System.err.println("read或send时发生异常!异常信息:" + e.getMessage());
            selectionKey.cancel();
            try {
                socketChannel.close();
            } catch (IOException e2) {
                System.err.println("关闭通道时发生异常!异常信息:" + e2.getMessage());
                e2.printStackTrace();
            }
        }
    }

    private void read() throws IOException {
        if (selectionKey.isValid()) {
            readBuffer.clear();
            int count = socketChannel.read(readBuffer); //read方法结束,意味着本次"读就绪"变为"读完毕",标记着一次就绪事件的结束
            if (count > 0) {

//                try {
//                    Thread.sleep(5000L);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }

                log.info("收到来自客户端的信息,信息为:{}", BufferUtils.readByteBufferInfo(readBuffer));
                status = SEND;
                selectionKey.interestOps(SelectionKey.OP_WRITE); //注册写方法
            } else {
                //读模式下拿到的值是-1,说明客户端已经断开连接,那么将对应的selectKey从selector里清除,否则下次还会select到,因为断开连接意味着读就绪不会变成读完毕,也不cancel,下次select会不停收到该事件
                //所以在这种场景下,(服务器程序)你需要关闭socketChannel并且取消key,最好是退出当前函数。注意,这个时候服务端要是继续使用该socketChannel进行读操作的话,就会抛出“远程主机强迫关闭一个现有的连接”的IO异常。
                selectionKey.cancel();
                socketChannel.close();
                System.out.println("read时-------连接关闭");
            }
        }
    }

    private void send() throws IOException {
        if (selectionKey.isValid()) {
            writeBuffer.clear();
            String returnMessage = String.format("服务端收到来自%s的信息:%s,  200ok;",
                    socketChannel.getRemoteAddress(),
                    new String(readBuffer.array()));
            writeBuffer.put(returnMessage.getBytes());
            log.info("服务端发送消息,消息为:{}", returnMessage);
            writeBuffer.flip();
            int count = socketChannel.write(writeBuffer); //write方法结束,意味着本次写就绪变为写完毕,标记着一次事件的结束
            if (count < 0) {
                //同上,write场景下,取到-1,也意味着客户端断开连接
                selectionKey.cancel();
                socketChannel.close();
                log.info("发送消息时连接关闭");
                System.out.println("send时-------连接关闭");
            }

            //没断开连接,则再次切换到读
            status = READ;
            selectionKey.interestOps(SelectionKey.OP_READ);
        }
    }
}

客户端代码

package com.learn.reactor.singlereactormodel;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

/**
 * autor:liman
 * createtime:2019/10/15
 * comment:
 */
public class NIOClient implements Runnable {

    private Selector selector;

    private SocketChannel socketChannel;

    public NIOClient(String ip, int port) {

        try {
            selector = Selector.open();
            socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(false);
            socketChannel.connect(new InetSocketAddress(ip, port));

            SelectionKey selectionKey = socketChannel.register(selector, SelectionKey.OP_CONNECT);
            selectionKey.attach(new Connector(socketChannel, selector));
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                selector.select(); //就绪事件到达之前,阻塞
                Set selected = selector.selectedKeys(); //拿到本次select获取的就绪事件
                Iterator it = selected.iterator();
                while (it.hasNext()) {
                    //这里进行任务分发
                    dispatch((SelectionKey) (it.next()));
                }
                selected.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    void dispatch(SelectionKey k) {
        Runnable r = (Runnable) (k.attachment()); //这里很关键,拿到每次selectKey里面附带的处理对象,然后调用其run,这个对象在具体的Handler里会进行创建,初始化的附带对象为Connector(看上面构造器)
        //调用之前注册的callback对象
        if (r != null) {
            r.run();
        }
    }

    public static void main(String[] args) {
        new Thread(new NIOClient("127.0.0.1",8888)).start();
        new Thread(new NIOClient("127.0.0.1",8888)).start();
    }
}

 客户端处理连接的线程

package com.learn.reactor.singlereactormodel;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;

/**
 * autor:liman
 * createtime:2019/10/15
 * comment:
 */
@Slf4j
public class Connector implements Runnable{
    private final Selector selector;

    private final SocketChannel socketChannel;

    Connector(SocketChannel socketChannel, Selector selector) {
        this.socketChannel = socketChannel;
        this.selector = selector;
    }

    @Override
    public void run() {
        try {
            if (socketChannel.finishConnect()) { //这里连接完成(与服务端的三次握手完成)
                log.info("已经完成{}的连接",socketChannel.getRemoteAddress());
                new ClientHandler(socketChannel, selector); //连接建立完成后,接下来的动作交给Handler去处理(读写等)
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

客户端真正处理业务的类:

package com.learn.reactor.singlereactormodel;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * autor:liman
 * createtime:2019/10/15
 * comment:
 */
@Slf4j
public class ClientHandler implements Runnable {
    private final SelectionKey selectionKey;
    private final SocketChannel socketChannel;

    private ByteBuffer readBuffer = ByteBuffer.allocate(2048);
    private ByteBuffer sendBuffer = ByteBuffer.allocate(1024);

    private final static int READ = 0;
    private final static int SEND = 1;

    private int status = SEND; //与服务端不同,默认最开始是发送数据

    private AtomicInteger counter = new AtomicInteger();

    ClientHandler(SocketChannel socketChannel, Selector selector) throws IOException, IOException {
        this.socketChannel = socketChannel; //接收客户端连接
        this.socketChannel.configureBlocking(false); //置为非阻塞模式(selector仅允非阻塞模式)
        selectionKey = socketChannel.register(selector, 0); //将该客户端注册到selector,得到一个SelectionKey,以后的select到的就绪动作全都是由该对象进行封装
        selectionKey.attach(this); //附加处理对象,当前是Handler对象,run是对象处理业务的方法
        selectionKey.interestOps(SelectionKey.OP_WRITE); //走到这里,说明之前Connect已完成,那么接下来就是发送数据,因此这里首先将写事件标记为“感兴趣”事件
        selector.wakeup(); //唤起select阻塞
    }

    @Override
    public void run() {
        try {
            switch (status) {
                case SEND:
                    send();
                    break;
                case READ:
                    read();
                    break;
                default:
            }
        } catch (IOException e) { //这里的异常处理是做了汇总,同样的,客户端也面临着正在与服务端进行写/读数据时,突然因为网络等原因,服务端直接断掉连接,这个时候客户端需要关闭自己并退出程序
            System.err.println("send或read时发生异常!异常信息:" + e.getMessage());
            selectionKey.cancel();
            try {
                socketChannel.close();
            } catch (IOException e2) {
                System.err.println("关闭通道时发生异常!异常信息:" + e2.getMessage());
                e2.printStackTrace();
            }
        }
    }

    void send() throws IOException {
        if (selectionKey.isValid()) {
            sendBuffer.clear();
            int count = counter.incrementAndGet();
            if (count <= 10) {
                String sendMessage = "客户端发送的第" + count + "条消息";
//                sendBuffer = BufferUtils.writeByteBufferInfo("客户端发送的第" + count + "条消息");
//                System.out.println("客户端发送的第["+count+"]条消息"+BufferUtils.readByteBufferInfo(sendBuffer));
                sendBuffer.put(sendMessage.getBytes());
                sendBuffer.flip(); //切换到读模式,用于让通道读到buffer里的数据
//                String sendMessage=BufferUtils.readByteBufferInfo(sendBuffer);
//                sendBuffer.reset();
//                log.info("客户端发送的第{}条消息,消息内容为:{}",count,sendMessage);
                socketChannel.write(sendBuffer);
//                String sendMessage = BufferUtils.readByteBufferInfo(sendBuffer);
                log.info("客户端发送的第{}条消息,消息内容为:{}", count, sendMessage);


                //则再次切换到读,用以接收服务端的响应
                status = READ;
                selectionKey.interestOps(SelectionKey.OP_READ);
            } else {
                selectionKey.cancel();
                socketChannel.close();
            }
        }
    }

    private void read() throws IOException {
        if (selectionKey.isValid()) {
            readBuffer.clear();
            socketChannel.read(readBuffer);
            log.info("收到来自客户端的消息,消息内容为:{}", BufferUtils.readByteBufferInfo(readBuffer));
//            System.out.println("收到来自客户端的消息"+BufferUtils.readByteBufferInfo(readBuffer));

            //收到服务端的响应后,再继续往服务端发送数据
            status = SEND;
            selectionKey.interestOps(SelectionKey.OP_WRITE); //注册写事件
        }
    }
}

 运行结果如下:

服务端输出

客户端输出:

但是单线程的reactor模式缺点也很明显,这里处理数据依旧是阻塞的,多线程并没有起到多大的作用。

总结

本篇博客只是针对单线程Reactor模式下的实例总结而已,这里所说的单Reactor指的是处理Selector连接建立事件的Reactor只有一个(或者可以简单理解为真正做事的Selector只有一个),同时后面的读和写其实也是单线程在处理,所以这里统称为单Reactor单线程的Reactor的模型

参考资料:

https://www.cnblogs.com/hama1993/p/10611229.html

发布了129 篇原创文章 · 获赞 37 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/liman65727/article/details/102648601