NIO的Reactor模型

《同步非阻塞IO》里面已经用“餐馆服务员”的例子解释了NIO的同步非阻塞模型,如果讲的不够明白。。。
那我也没什么办法啊,毕竟我也没给别人讲过,不知道痛点在哪里
这次会通过一些demo来解释一下Reactor模型,内容的话,基本照搬Doug Lea的Scalable IO in Java
个人觉得这个Reactor模型的发展史,基本上是一个餐馆压榨劳动力的发展史,所以这次还是会基于餐馆模型来讲

传统IO

public static void main(String[] args) throws IOException{
        ServerSocket serverSocket = new ServerSocket(9999);
        while (true){
            // 给一桌客人服务完之后站在门口傻等着
            Socket socket = serverSocket.accept();
            // 在桌子前傻等着客人点菜
            InputStream in = socket.getInputStream();
            byte[] b = new byte[in.available()];
            int index = in.read(b);
            StringBuffer sb = new StringBuffer();
            while (index != -1) {
                sb.append(new String(b));
            }

            OutputStream out = socket.getOutputStream();
            out.write("server speaking".getBytes());
            socket.close();
        }
}

即使有其它客人需要服务,服务员也不会去,而是在傻等着。那如果我是老板,让自己的员工的工作这么不饱和的,于是就有了NIO...

NIO


    public static void main(String[] args) throws IOException {
        Selector selector = Selector.open();
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().bind(new InetSocketAddress(9999));
        serverSocketChannel.configureBlocking(false);
        // 最开始店里没人,肯定只关心有新客人来的情况
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        while (true){
            // 这时候没啥事,在这等人叫
            selector.select();
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> it = keys.iterator();
            while (it.hasNext()) {
                it.remove();
                SelectionKey key = it.next();
                if (key.isAcceptable()) {
                    // accept
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    socketChannel.configureBlocking(false);
                    socketChannel.register(selector, SelectionKey.OP_READ);
                }
                if (key.isReadable()) {
                    // read
                    String msg = readFromChannel((SocketChannel) key.channel());
                    System.out.println(msg);
                }
            }
        }
    }

  这种情况就好多了,服务员永远处于忙碌的状态,只有当没有任何客人需要被服务的时候才能闲下来。
  现在服务员的工作已经很饱和了,但是作为老板我会想,服务员做的事情有多又杂,招个人得要啥都会,是不是不太好招人啊,于是我打算给服务员分配一下工作内容,比如站在门口接客的那个叫接待,在里面点餐擦桌子端菜送水的叫跑堂...

单线程Reactor模型

  1. 反应器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);
    }

    public static void main(String[] args){
        try {
            Reactor reactor = new Reactor(9999);
            reactor.run();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        try {
             // 告诉服务员你现在叫“接待”,有客人来了就把他们带接进来
            SelectionKey sk = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            while (true) {
                selector.select();
                Set<SelectionKey> keys = selector.selectedKeys();
                Iterator<SelectionKey> it = keys.iterator();
                SelectionKey key = null;
                while (it.hasNext()) {
                    it.remove();
                    key = it.next();
                    dispatch(key);
                }
                keys.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void dispatch(SelectionKey k) throws IOException {
        if (!k.isValid()){
            return;
        }
        if (k.isAcceptable()) {
            // 是外面来客人了,所以我现在是"接待"
            Acceptor acceptor = (Acceptor) k.attachment();
            acceptor.handler();
        }
        if (k.isReadable()) {
            // 客人要点菜了,所以现在我是"点餐员"
            ReadEventHandler handler = (ReadEventHandler) k.attachment();
            handler.handler();
        }
    }

  1. Acceptor(接待员)
public class Acceptor extends NioEventHandler {

    public Acceptor(Selector selector, SelectionKey key) {
        super(selector, key);
    }

    @Override
    public void handler() throws IOException {
        // 把客人接到座位上
        ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
        SocketChannel socketChannel = serverSocketChannel.accept();
        socketChannel.configureBlocking(false);
        // 然后告诉客人,如果要点菜,就呼叫"点餐员"
        SelectionKey key = socketChannel.register(selector, SelectionKey.OP_READ);
        key.attach(new ReadEventHandler(selector, key));
    }
}
  1. Handler(点餐员、传菜员什么的。。)
public class ReadEventHandler extends NioEventHandler {

    public ReadEventHandler(Selector selector, SelectionKey key) {
        super(selector, key);
    }

        @Override
    public void handler() throws IOException {
        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
        // 点菜
        String msg = readFromChannel(socketChannel);
        // 你也可以在这里招呼厨房开始做菜
        // TODO mainServer
        // 然后告诉客人菜做好了就会上
        // socketChannel.register(selector, SelectionKey.OP_WRITE);
    }

  什嘛?少了个NioEventHandler类?别急,后面会有的
  现在分工已经很明确了,我们店里虽然只有一个服务员,但是接待员、点菜员、传菜员这些,只要你想要的,都可以有!是不是很有B格
  最关键的是,我可以按不同的角色去招人,这样就很方便,比如接待员得是漂亮小姐姐,而传菜员走路要快而稳...

Reactor多线程模型

  1. 上面缺的NioEventHandler
@Slf4j
public abstract class NioEventHandler implements Runnable {
    protected final Selector selector;
    protected final SelectionKey selectionKey;

    public NioEventHandler(Selector selector, SelectionKey key){
        this.selector = selector;
        this.selectionKey = key;
    }

    protected abstract void handler() throws IOException;

    @Override
    public void run() {
        try {
            handler();
        } catch (IOException e) {
            log.error("handler field with io Exception ", e);
        }
    }
}
  1. 稍微改一下Reactor的dispatch
    private static void dispatch(SelectionKey k) throws IOException {
        if (!k.isValid()){
            return;
        }
        if (k.isAcceptable()) {
            // 是外面来客人了,所以我现在是"接待"
            Acceptor acceptor = (Acceptor) k.attachment();
            acceptor.handler();
        }
        if (k.isReadable()) {
            // 客人要点菜了,所以叫一个点餐员来服务
            ReadEventHandler handler = (ReadEventHandler) k.attachment();
            new Thread(handler).start();
        }
    }

  店里的客人变多了,一个服务员有点忙不过来了,于是我招了一个漂亮的小姐姐专门做接待,之前的服务员继续让他干苦力,然后再招几个苦力,完美!

Reactor主从多线程模型

对Reactor的启动方法做点小小的改动

    public static void main(String[] args){
        try {
            // 启动3个reactor,分别监听9999,10000,10001端口
            for (int i = 0; i < 3; i ++) {
                Reactor reactor = new Reactor(9999 + i);
                new Thread(reactor).start();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

  我一直尝试把这个模型和餐馆模型匹配起来。我觉得应该可以这么看。因为有我这么机智的老板,店里客人越来越多,扩招服务员已经不能满足顾客需求了,于是我选择开分店...其实这不是一个好的比喻,但是理解起来会容易一些
  关于主从多线程模型,在Doug Lea的Scalable IO in Java中,是通过增加Selector线程池来实现的,这是个很优雅的方式。我为什么直接多线程启动了几个Acceptor呢。因为这样更贴近原文中“Multiple Reactor Threads”或者说“主从多线程模型”这个标题(嗯,其实主要是因为代码改起来简单)

我在Reactor模型中开到的吸引

  NIO提供了多路复用的可能,让线程从IO等待的牢笼中解脱出来,Reactor模式“don‘t call us, we‘ll call you”的思想毫无疑问是相较于Socket编程的一次巨大的变革。但是Reactor模型的演进所最吸引我的是将一次完整的IO链路拆分成了不同的事件,拆分到更细的粒度总是更方便分治,就好像服务员的角色被细分成不同的工种之后,就更容易招到更专业的服务员。

Reactor模型其实更优雅

  • 相比于我上面那一堆直接new一个Thread的操作,使用线程池明显更加优雅,也更加方便进行资源管理(只是我一个萌新并不会写线程池)
  • 服务端来讲,除了accept和read事件,当然还有write事件,还有核心业务处理逻辑,这些其实都可以抽象成Handler来进行处理
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容