Netty 源码阅读(1) - 准备 Reactor IO 模型,附Demo源码

预备知识

Java NIO

传统的BIO模型在高并发情况下,每个连接都会新启动一个线程阻塞等待数据,而线程做为重要的资源,这是极为浪费的。

Java NIO是基于多路复用实现的IO模型,一个线程可以监听多个连接,大大节约了资源。具体NIO的Channal,Buffer,Selector这里就不做详细介绍了。

各种IO模型如下图所示

image

Reactor介绍

单线程使用Java NIO

当使用Java NIO时,会将Channel的相关事件(READ, WRITE, CONNECT, ACCEPT)到Selector,如果我们将所有的Channel的事件都注册到一个Selector,势必会影响整体性能,无法发挥多线程的优势。

Reactor模型使用Java NIO

Reactor模式就是充分发挥多线程的优势,将各个事件的监听解耦,各司其职。

image

例子

纸上得来终觉浅,下面是我自己写的一个Reactor模型的Demo,帮助大家学习和巩固,几乎都有注释,应该比较好理解,大家可以对照着上面的图进行学习。

这个Demo主要实现的功能是Base64加密,客户端发送数据到服务端,服务端接受数据之后,做Base64加密,并返回给客户端。

关于粘包拆包,因为客户端和服务端都有buffer,所以如何判定是否是一次完整的消息呢?

客户端和服务端发送消息之前,会先写入一个dataLength,一个4个字节,然后再把数据跟在后面。

下面说一下Server端的几个组件:

  • Accepter: 监听ACCEPT事件,并将连接分发给Reader,目前只有1个线程。

  • Reader: 只负责读取数据,从Channel中将数据读出,并放至callQueue中,目前是设置的2个线程。

  • Handler: 请求的处理器,主要是负责做真实的服务操作,目前是设置的4个线程。

  • Responder: 其实还应该有一个Responder组件,专门用于返回数据,我这边没有实现,直接是在Handler中就把数据返回了。

Accepter

做路由的分发


    private static class Accepter implements Runnable, Closeable {

        // 监听的端口

        private final int port;

        // reader数组

        private final Reader[] readers;

        private ServerSocketChannel serverSocketChannel;

        private volatile boolean running = true;

        private Selector selector;

        // 选择Reader采用轮训的方式

        private int readerIndex = 0;

        public Accepter(int port, BlockingQueue<Call> callQueue) {

            this.port = port;

            System.out.println("start Accepter..");

            this.readers = new Reader[DEFAULT_READER_SIZE];

            for (int i = 0; i < this.readers.length; i++) {

                this.readers[i] = new Reader(callQueue);

                new Thread(this.readers[i]).start();

            }

        }

        @Override

        public void run() {

            // 绑定端口,并把serverSocketChannel的ACCEPT事件注册到Accepter的Select

            try {

                serverSocketChannel = ServerSocketChannel.open();

                serverSocketChannel.bind(new InetSocketAddress(port));

                serverSocketChannel.configureBlocking(false);

                selector = Selector.open();

                serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            } catch (IOException e) {

                e.printStackTrace();

                throw new RuntimeException("Failed to open server socket channel");

            }

            while (running) {

                int acceptedCount;

                try {

                    acceptedCount = selector.select(100);

                } catch (IOException ignored) {

                    ignored.printStackTrace();

                    continue;

                }

                if (acceptedCount == 0) {

                    try {

                        Thread.sleep(100);

                    } catch (InterruptedException ignored) {

                        System.out.println("interrupt when sleep!");

                    }

                    continue;

                }

                Iterator<SelectionKey> iter = selector.selectedKeys().iterator();

                while (iter.hasNext()) {

                    SelectionKey key = iter.next();

                    // 重要,一定记得要remove处理了的SelectionKey

                    iter.remove();

                    if (!key.isAcceptable()) {

                        System.out.println("WARNING: get selection key isn't a " +

                                          "Acceptable key!");

                        continue;

                    }

                    try {

                        // 做accept事件

                        doAccept(key);

                    } catch (IOException e) {

                        e.printStackTrace();

                    }

                }

            }

            // close

            try {

                if (selector != null) {

                    selector.close();

                }

                if (serverSocketChannel != null) {

                    serverSocketChannel.close();

                }

            } catch (IOException ignored) {

                ignored.printStackTrace();

            }

        }

        private void doAccept(SelectionKey key) throws IOException {

            // 轮训选择一个Reader

            Reader reader = this.readers[readerIndex++ % DEFAULT_READER_SIZE];

            SocketChannel socketChannel = serverSocketChannel.accept();

            InetSocketAddress remoteAddress = (InetSocketAddress) socketChannel.getRemoteAddress();

            System.out.println(String.format("accept a connect from %s:%s",

                                            remoteAddress.getHostName(), remoteAddress.getPort()));

            // 将此Channel交给被选中的Reader

            reader.addChannel(socketChannel);

        }

        @Override

        public void close() {

            Arrays.stream(readers).forEach(Reader::close);

            running = false;

        }

    }

Reader

读取数据


    private static class Reader implements Runnable, Closeable {

        private final BlockingQueue<Call> callQueue;

        private Selector selector;

        private volatile boolean running = true;

        public Reader(BlockingQueue<Call> callQueue) {

            System.out.println("start Reader..");

            this.callQueue = callQueue;

        }

        @Override

        public void run() {

            try {

                this.selector = Selector.open();

            } catch (IOException e) {

                System.out.println("Failed to open select in Reader");

                throw new RuntimeException("Failed to open select in Reader");

            }

            while (running) {

                int acceptedCount;

                try {

                    acceptedCount = selector.select(100);

                } catch (IOException ignored) {

                    ignored.printStackTrace();

                    continue;

                }

                if (acceptedCount == 0) {

                    try {

                        Thread.sleep(100);

                    } catch (InterruptedException ignored) {

                        System.out.println("interrupt when sleep!");

                    }

                    continue;

                }

                Iterator<SelectionKey> iter = selector.selectedKeys().iterator();

                while (iter.hasNext()) {

                    SelectionKey key = iter.next();

                    iter.remove();

                    if (!key.isReadable()) {

                        continue;

                    }

                    doRead(key);

                }

            }

            try {

                if (selector != null) {

                    selector.close();

                }

            } catch (IOException ignored) {

                ignored.printStackTrace();

            }

        }

        private void addChannel(SocketChannel channel) {

            try {

                System.out.println("add reading channels..");

                // 因为有可能Select会在轮询中block,所以wakeUp是有必要的

                selector.wakeup();

                channel.configureBlocking(false);

                channel.register(selector, SelectionKey.OP_READ);

            } catch (IOException ignored) {

                ignored.printStackTrace();

            }

        }

        private void doRead(SelectionKey key) {

            SocketChannel channel = (SocketChannel) key.channel();

            byte[] dataBytes;

            try {

                // 从Channel中读取数据,并将Call推到阻塞队列中

                ByteBuffer dataLengthBuf = ByteBuffer.allocate(4);

                channel.read(dataLengthBuf);

                dataLengthBuf.flip();

                int dataLength = dataLengthBuf.getInt();

                ByteBuffer dataBuf = ByteBuffer.allocate(dataLength);

                channel.read(dataBuf);

                dataBuf.flip();

                dataBytes = dataBuf.array();

                System.out.println("accept a msg, length = " + dataLength +

                                  ", content = " + new String(dataBytes));

            } catch (IOException ignored) {

                ignored.printStackTrace();

                return;

            }

            callQueue.offer(new Call(channel, dataBytes));

        }

        @Override

        public void close() {

            running = false;

        }

    }

Handler

处理请求


    private static class Handler implements Runnable, Closeable {

        private final BlockingQueue<Call> callQueue;

        private volatile boolean running = true;

        public Handler(BlockingQueue<Call> callQueue) {

            System.out.println("start Handler..");

            this.callQueue = callQueue;

        }

        @Override

        public void run() {

            while (running) {

                try {

                    Call call = callQueue.poll(100, TimeUnit.MILLISECONDS);

                    if (call == null) {

                        continue;

                    }

                    // Handler处理Call,对传过来的数据做Base64加密,其实在这里实现不同的方法

                    // 一个简单的rpc调用就可以实现了

                    byte[] encode = Base64.getEncoder().encode(call.dataBytes);

                    ByteBuffer resBuf = ByteBuffer.allocate(encode.length + 4);

                    resBuf.putInt(encode.length);

                    resBuf.put(encode);

                    System.out.println("response a msg, length = " + encode.length +

                                      ", content = " + new String(encode));

                    resBuf.flip();

                    call.channel.write(resBuf);

                } catch (InterruptedException | IOException ignored) {

                    ignored.printStackTrace();

                }

            }

        }

        @Override

        public void close() {

            running = false;

        }

    }

Call

一次请求的封装


    static final class Call {

        final SocketChannel channel;

        final byte[] dataBytes;

        public Call(SocketChannel channel, byte[] dataBytes) {

            this.channel = channel;

            this.dataBytes = dataBytes;

        }

    }

Client

Client直接就用BIO简单实现了一下


public class Client {

    private Socket socket;

    public Client(String ip, int port) {

        try {

            this.socket = new Socket(ip, port);

        } catch (IOException e) {

            throw new RuntimeException(e);

        }

    }

    public String base64(String originStr) {

        try {

            DataOutputStream dos = new DataOutputStream(socket.getOutputStream());

            byte[] msg = originStr.getBytes();

            dos.writeInt(msg.length);

            dos.write(msg);

            dos.flush();

        } catch (IOException e) {

            throw new RuntimeException("Failed to send request to server!", e);

        }

        try {

            DataInputStream dis = new DataInputStream(socket.getInputStream());

            int msgLength = dis.readInt();

            byte[] msg = new byte[msgLength];

            dis.read(msg);

            return new String(msg);

        } catch (IOException e) {

            throw new RuntimeException("Failed to receive request from server!", e);

        }

    }

}

主程序


    public static void main(String[] args) {

        Server server = new Server(8080);

        server.start();

        try {

            Thread.sleep(1000);

        } catch (InterruptedException e) {

            throw new RuntimeException(e);

        }

        Scanner sc = new Scanner(System.in);

        String line;

        while ((line = sc.nextLine()) != null) {

            if (line.equals("stop")) {

                server.close();

                break;

            }

            Client client = new Client("localhost", 8080);

            String base64 = client.base64(line);

            System.out.println("result : " + base64);

        }

    }

结果展示


start Base64 Server...

start Accepter..

start Reader..

start Reader..

start Handler..

start Handler..

start Handler..

start Handler..

1

accept a connect from localhost:50476

add reading channels..

accept a msg, length = 1, content = 1

response a msg, length = 4, content = MQ==

result : MQ==

2

accept a connect from localhost:50481

add reading channels..

accept a msg, length = 1, content = 2

response a msg, length = 4, content = Mg==

result : Mg==

stop

Process finished with exit code 0

源码

https://github.com/hansiming/reactor-demo/

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容