带你走进Netty世界

Netty简介

Netty是一个高性能、高可扩展性的异步事件驱动的网络应用程序框架,它极大地简化了TCP和UDP客户端和服务器开发等网络编程。
Netty四个重要内容:

  1. Reactor线程模型:一种高性能的多线程程序设计思路
  2. Netty中自己定义的Channel概念:增强版的通道概念
  3. ChannelPineline职责链设计模式:事件处理机制
  4. 内存管理:增强的ByteBuf缓冲区
    Netty整体结构图
    带你走进Netty世界_第1张图片

Netty线程模型:
为了让NIO处理更好的利用多线程特性,Netty实现了Reactor线程模型。
Reactor模型中有四个核心概念:

  1. Resources资源(请求/任务)
  2. Synchronous Event Demultiplexer同步事件复用器
  3. Dispatcher分配器
  4. Request Handler请求处理器
    带你走进Netty世界_第2张图片

EventLoopGroup初始化过程:
构造函数-》确定线程数量:默认cous*2-》new Executor:构建线程执行器->for->newChild():构建EventLoop-》new EvenrExecutorChooser
带你走进Netty世界_第3张图片
其代码如下:
MultithreadEventExecutorGroup->
this.children[i] = this.newChild((Executor)executor, args);-> NioEventLoopGroup->protected EventLoop newChild(Executor executor, Object… args) throws Exception {
return new NioEventLoop(this, executor, (SelectorProvider)args[0], ((SelectStrategyFactory)args[1]).newSelectStrategy(), (RejectedExecutionHandler)args[2]);
}

// 创建EventLoopGroup accept线程组 NioEventLoop
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
进入会发现其继承了Executor方法
其Executor方法时由SingleThreadEventExecutor实现

EventLoop是包含了nio和Reactor
EventLoop的启动
EventLoop自身实现了Executor接口,当调用executor方法提交任务时,则判断是否启动,未启动则调用内置的executor创建新线程来触发run方法执行。
通过: ChannelFuture f = b.bind(PORT).sync();方法

带你走进Netty世界_第4张图片
Bind绑定端口过程:
bind(端口):AbstactBootstap->创建和初始化Channel->注册到EventLoop的Selector上->提交任务到EventLoop执行。注册完成后再继续绑定->doBind()->channl.bind->pipeline.bind->HeadContext.bind->AbstarctUsafe.bind->NioServerSocketChannel.doBind
带你走进Netty世界_第5张图片
其代码如下:
在AbstactBootStrap类中

    private ChannelFuture doBind(final SocketAddress localAddress) {
        final ChannelFuture regFuture = initAndRegister();// : 创建/初始化ServerSocketChannel对象,并注册到Selector
        final Channel channel = regFuture.channel();
        if (regFuture.cause() != null) {
            return regFuture;
        }
        // : 等注册完成之后,再绑定端口。 防止端口开放了,却不能处理请求
        if (regFuture.isDone()) {
            // At this point we know that the registration was complete and successful.
            ChannelPromise promise = channel.newPromise();
            doBind0(regFuture, channel, localAddress, promise);// : 实际操作绑定端口的代码
            return promise;
        } else {
            // Registration future is almost always fulfilled already, but just in case it's not.
            final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
            regFuture.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    Throwable cause = future.cause();
                    if (cause != null) {
                        // Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an
                        // IllegalStateException once we try to access the EventLoop of the Channel.
                        promise.setFailure(cause);
                    } else {
                        // Registration was successful, so set the correct executor to use.
                        // See https://github.com/netty/netty/issues/2586
                        promise.registered();

                        doBind0(regFuture, channel, localAddress, promise);
                    }
                }
            });
            return promise;
        }
    }
 final ChannelFuture initAndRegister() {
        Channel channel = null;
        try {
            channel = channelFactory.newChannel();
            init(channel);
        } catch (Throwable t) {
            if (channel != null) {
                // channel can be null if newChannel crashed (eg SocketException("too many open files"))
                channel.unsafe().closeForcibly();
                // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
                return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
            }
            // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
            return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);
        }
        // : (一开始初始化的group)MultithreadEventLoopGroup里面选择一个eventLoop进行绑定
        ChannelFuture regFuture = config().group().register(channel);
        if (regFuture.cause() != null) {
            if (channel.isRegistered()) {
                channel.close();
            } else {
                channel.unsafe().closeForcibly();
            }
        }

        // If we are here and the promise is not failed, it's one of the following cases:
        // 1) If we attempted registration from the event loop, the registration has been completed at this point.
        //    i.e. It's safe to attempt bind() or connect() now because the channel has been registered.
        // 2) If we attempted registration from the other thread, the registration request has been successfully
        //    added to the event loop's task queue for later execution.
        //    i.e. It's safe to attempt bind() or connect() now:
        //         because bind() or connect() will be executed *after* the scheduled registration task is executed
        //         because register(), bind(), and connect() are all bound to the same thread.

        return regFuture;
    }

 @Override
    public ChannelFuture register(final ChannelPromise promise) {
        ObjectUtil.checkNotNull(promise, "promise");
        promise.channel().unsafe().register(this, promise);//  调用channel中unsafe对象的注册方法(AbstractUnsafe)
        return promise;
    }
到AbstarctChannel的register方法执行 eventLoop.execute(new Runnable() {
                            public void run() {
                                AbstractUnsafe.this.register0(promise);
                            }
                        });
                        执行SingleEventExecutor类的 execute(方法添加任务对列
 @Override
    public void execute(Runnable task) {
        if (task == null) {
            throw new NullPointerException("task");
        }
        //  判断execute方法的调用者是不是EventLoop同一个线程
        boolean inEventLoop = inEventLoop();
        addTask(task);//  增加到任务队列
        if (!inEventLoop) {// : 不是同一个线程,则调用启动方法
            startThread();
            if (isShutdown()) {
                boolean reject = false;
                try {
                    if (removeTask(task)) {
                        reject = true;
                    }
                } catch (UnsupportedOperationException e) {
                    // The task queue does not support removal so the best thing we can do is to just move on and
                    // hope we will be able to pick-up the task before its completely terminated.
                    // In worst case we will log on termination.
                }
                if (reject) {
                    reject();
                }
            }
        }

        if (!addTaskWakesUp && wakesUpForTask(task)) {
            wakeup(inEventLoop);
        }
    }

Channel概念:
netty中的Channel是一个抽象的概念,可以理解为对JDKNIO Channel的增强和扩展。增加了很多属性和方法,完整信息可以看代码注释,下面罗列几个常见的属性和方法。
带你走进Netty世界_第6张图片

设计模式-责任链模式
责任链模式为请求创建了一个处理对象的链
发起请求和具体处理请求的过程进行解耦:职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无需关心请求的处理细节和请求的传递。
带你走进Netty世界_第7张图片
实现责任链模式
实现责任链模式4个要素:处理器抽象类、具体的处理器实现类、保存处理器信息、处理执行。netty还有用链表形头部 next式
带你走进Netty世界_第8张图片
Netty中的ChannelPipeline责任链
Pipeline管道保存了通道所有处理器信息。创建新Channel时自动创建一个专有的pipeline。入站事件和出栈操作会调用pipeline的处理器:
带你走进Netty世界_第9张图片
入站事件和出站事件
入站事件:通常指i/o线程生成了入站数据。
(通俗理解:从socket底层自己往上冒上来的事件都是入站)比如EventLoop收到selector的op_read事件,入站处理器调用socektChannel.Read(ByteBuffer)接收到数据后,这将导致通道的ChannelPipeline中包含的下一个中的channelRead方法被调用。
出站事件:经常是指i/o线程执行实际的输出操作。
(通俗理解:想主动往socket底层操作的事件都是出站)比如bind方法用意时请求server socket绑定到给定的SocketAddress,这将导致通道的ChannelPipeline中包含的下一个出站处理器中的bind方法被调用。
下面时netty的事件定义:
带你走进Netty世界_第10张图片
read入站事件的处理:
带你走进Netty世界_第11张图片
用户在管道中有一个或多个channelhandler来接收i/o事件(例如读取)和请求i/o操作(例如写入和关闭)
一个典型的服务器哎每个通道的管道中都有以下处理程序,但是根据协议和业务逻辑的复杂性和特征,可能会有所不同:
协议编码器–将二进制数据(例如ByteBuf)转换为java对象。
协议编码器–将java对象转换为二进制数据。
业务逻辑处理程序–执行事件的业务逻辑(例如数据库访问)。

Pipeline中的handler是什么
channelHandler:用于处理i/o事件或拦截i/o操作,并转发到ChannelPipeline中国的下一个处理器。这个顶级接口定义功能很弱,实际使用时会去实现下面两大子接口:
处理入站i/o事件的ChannelinboundHandler处理出站i/o操作的ChannelOutboundHandler
适配器类:为了方便,避免所有handler去实现一遍接口方法,Netty提供了简单的实现类:
ChannelInboundHandlerAdapter处理入站i/o事件
ChannelOutboundHandlerAdapter处理出站i/o事件
ChannelDuolexHandler来支持同时处理入站和出站事件
ChannelHandlerContext:实际存储在pipeline中的对象并非ChannelHandler,而时上下文对象。
将handler,包裹在上下文对象中,通过上下文对象与它所属的ChannelPipeline交互,向上或向下传递事件或者修改pipeline都是通过上下文对象。

维护Pipeline中的Handler

ChannePipeline是线程安全的,ChannelHandler可以在任何时候添加或删除。例如:你可以在即将交换敏感信息时插入加密处理程序,并在交换后删除它。一般操作,初始化的时候增加进去,较少删除。下面是Pipeline管理handler的api
带你走进Netty世界_第12张图片
带你走进Netty世界_第13张图片

注意:

  1. 复用handler
    handler处理过程中线程安全问题
    1、减少内存占用
    2、加快连接建立的速度

  2. 业务耗时操作采取独立的线程运行

  3. 大型数据报的相依、要多次write、让i/o更均匀处理多个连接的响应数据包。

  4. Bytebuf对象复用机制
    用完bytebuf要记得release。上线之前自己简单测试一些
    handler继承SimpleChannelinboundHandler可以简单的规章。

你可能感兴趣的:(java,netty,java,netty,网络)