【Netty】手写Netty

1. 目的

我们知道Netty优点很多以及netty原理的底层机制,但是是否真正理解呢?如果真的理解了那么自然回答出一下几个问题:

  • 1.你能看的懂netty源码吗
  • 2.你能讲出netty源码的架构吗
  • 3.netty中的NioEventLoop对应一个线程,并绑定了一个selector,为何主Reactor中的NioEventLoop可以负责接收请求,而从Reactor中的NioEventLoop可以负责消息的读取呢?同一个client发起的连接请求及后续的消息可以被2个不同的NioEventLoop处理?

其实netty源码的核心就是问题3,搞清楚了问题3,netty源码就很简单了,看完本篇文章,就会为你揭晓答案。

2 手写netty架构的nio代码

本篇基于《java NIO编程示例以及流程详解》改造,在该篇文章中,我们的服务端代码负责接收请求,由于采用nio架构,让我们能非阻塞的处理消息连接和消息处理。

并且是单线程的,对应【Netty源码分析摘录】(二)Netty源码分析系列之Reactor线程模型中”2.1 Reactor单线程模型“。

我的思路是将这个例子先扩展为多线程的,保证先能理解多个selector之间的协同关系,后面的主从架构就更容易理解了。

2.1 单线程的nio架构

NIO原生语法结构图,这样对比,可以更好的理解netty的架构:
在这里插入图片描述
我们看下单线程的nio架构特点:

  • 只有一个selector,服务端通道和客户端通道都注册到同一个selector上

  • 一旦某个channel和selector绑定后,通过SelectionKey,可以获得对应的channel

    在这里插入图片描述

2.2 改写为多线程架构

我们重写一个服务器端代码ServerSocketChannels2 ,继承了ServerSocketChannels,仅仅覆写了doAccept():



import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

public class ServerSocketChannels2 extends ServerSocketChannels implements Runnable {
    public ServerSocketChannels2(int port ){
        super(port);
    }

    /**
     * 覆写doAccept方法,保证新启动一个线程去负责处理连接的请求
     * @param key
     * @throws IOException
     */
    @Override
    public void doAccept(SelectionKey key) throws IOException {
        ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
        SocketChannel sc = (SocketChannel) ssc.accept();

        new Thread(new SockertChannel(sc),"subclient")
                .start();
    }

    public static void main(String[] args) {
        int port = 8010;
        ServerSocketChannels2 server = new ServerSocketChannels2(port);
        // ServerSocketChannels2 server = new ServerSocketChannels2(port);
        new Thread(server,"timeserver-002").start();
    }


}


在覆写的doAccept()方法中,我们没有在当前的线程中处理连接事件,而是新起了一个线程去负责处理连接事件:new Thread(server,"timeserver-002").start()这里对应Netty从workerGroup中取出一个NioEventLoop,并由该NioEventLoop处理后续的消息。

ServerSocketChannels 和ServerSocketChannels2 对应BossGroup中的一个NioEventLoop,并处理客户端的连接事件。

SockertChannel:

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

/**
 * 当客户端发起连接后,负责生成客户端channel
 */
public class SockertChannel extends ServerSocketChannels  {
    private SocketChannel sc;

    public SockertChannel(SocketChannel sc) {

        this.sc = sc;
        try {
            //新创建的selector
            selector = Selector.open();
            //设置客户端链路为非阻塞模式
            this.sc.configureBlocking(false);
            this.sc.register(selector, SelectionKey.OP_READ);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

SockertChannel继承了ServerSocketChannels是为了复用run(),处理接收的消息。在构造函数中,新建了一个selector,并且将其注册到新的selector上,并设置关心的消息类型为OP_READ

至此,我们基本上模拟了netty的实现原理,实现了一个多线程的nio架构,我们来看下执行结果:

服务端:
在这里插入图片描述
打印的是子线程,说明子线程处理了客户端发给服务端的消息。

3. 原理分析

多线程结构示意图:
在这里插入图片描述

我们在ServerSocketChannels2中创建了一个服务端通道,ServerSocketChannel,并将其注册到selector A上,当某个客户端C1发起连接后,利用ServerSocketChannel生成一个客户端通道SocketChannel SC1,并将SC1注册到新的selector A1上。

依此类推,当某个客户端C2发起连接后,利用ServerSocketChannel生成一个客户端通道SocketChannel SC2,并将SC2注册到新的selector A2上。

由上得知以下结论:SC1和SC2虽然是由ServerSocketChannel创建的,但是将其各自绑定到 selector A1和selector A2上后,算上selector A,总共有3个selector,但3者没有任何关系,各自由的消息由各自的selector 负责通知。

基本上Netty也是这样实现的,只不过不是每次新创建一个线程,而是有个workerGroup,对应一个线程池,线程池内已经预准备好了线程,每次有连接后,每个新产生的客户端通道SocketChannel 分配到某个线程池内的线程,该线程对应具体的NioEventLoop,一个NioEventLoop可以对应多个SocketChannel ,而一个SocketChannel 只会对应一个NioEventLoop,不会变化。

NioEventLoop内部有个selector,也就是说存在多个selector,只要在各自的selector上设置相应的关心事件的类型,那么就会出现分组的概念,各组负责各自组内成员的事件通知,不会跨组通知。

主从结构示意图,与多线程的区别是可以有多个线程负责Accept:
在这里插入图片描述

4. 总结

通过本篇文章,你可以了解多个selector之间是如何协同工作的,据此,知道了netty底层实现,这样,你就可以轻松的去阅读netty源码了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值