Netty基础简介

作者:keep_trying_gogo
来源:https://blog.csdn.net/yjp198713/article/details/79309260#bytebuf

  • 一JAVA NIO AIO的不足之处
  • 二Netty介绍
  • 三代码示例
    • 主要代码讲解
  • 四Netty的重要慨念
    • Netty线程机制
    • ByteBuf
    • Channel
    • ChannelPipeline和ChannelHandler
      • 责任链和适配器的应用
      • ChannelInboundHandler类举例
      • ChannelOutboundHandler类举例
  • 五Channel的生命周期
  • 六Netty对IO模型的封装
  • 七Netty对数据信息格式的封装

在讲netty之前我们现总结一下JAVA NIO / JAVA AIO的不足之处。

一、JAVA NIO 、AIO的不足之处

  • 虽然JAVA NIO 和 JAVA AIO框架提供了多路复用IO/异步IO的支持,但是并没有提供上层“信息格式”的良好封装。例如前两者并没有提供针对 ProtocolBuffer、JSON这些信息格式的封装,但是Netty框架提供了这些数据格式封装(基于责任链模式的编码和解码功能)
  • 要编写一个可靠的、易维护的、高性能的(注意它们的排序)NIO/AIO服务器应用。除了框架本身要兼容实现各类操作系统的实现外。更重要的是它应该还要处理很多上层特有服务,例如:客户端的权限、还有上面提到的信息格式封装、简单的数据读取。这些Netty框架都提供了响应的支持。
  • JAVA NIO框架存在一个poll/epoll bug:Selector doesn’t block on
    Selector.select(timeout),不能block意味着CPU的使用率会变成100%(这是底层JNI的问题,上层要处理这个异常实际上也好办)。当然这个bug只有在Linux内核上才能重现。这个问题在JDK 1.7版本中还没有被完全解决:http://bugs.java.com/bugdatabase/view_bug.do?bug_id=2147719。虽然Netty 4.0中也是基于JAVA NIO框架进行封装的(上文中已经给出了Netty中NioServerSocketChannel类的介绍),但是Netty已经将这个bug进行了处理。

二、Netty介绍

Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。但实际上呢,Netty框架并不只是封装了多路复用的IO模型,也包括提供了传统的阻塞式/非阻塞式 同步IO的模型封装。Netty提供了对TCP、UDP和文件传输的支持,作为一个异步NIO框架,Netty的所有IO操作都是异步非阻塞的,通过Future-Listener机制,用户可以方便的主动获取或者通过通知机制获得IO操作结果。作为当前最流行的NIO框架,Netty在互联网领域、大数据分布式计算领域、游戏行业、通信行业等获得了广泛的应用,一些业界著名的开源组件也基于Netty的NIO框架构建。

三、代码示例

客户端在第二篇中已经给出。
服务端:

package demo.com.test.io.netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.bytes.ByteArrayDecoder;
import io.netty.handler.codec.bytes.ByteArrayEncoder;
import io.netty.util.concurrent.DefaultThreadFactory;

import java.net.InetSocketAddress;
import java.nio.channels.spi.SelectorProvider;
import java.util.concurrent.ThreadFactory;

public class NettyServer {

    public static void main(String[] args) throws Exception {
        //这就是主要的服务启动器
        ServerBootstrap serverBootstrap = new ServerBootstrap();

        //=======================下面我们设置线程池
        //BOSS线程池
        EventLoopGroup bossLoopGroup = new NioEventLoopGroup(1);
        //WORK线程池:这样的申明方式,主要是为了向读者说明Netty的线程组是怎样工作的
        ThreadFactory threadFactory = new DefaultThreadFactory("work thread pool");
        //CPU个数
        int processorsNumber = Runtime.getRuntime().availableProcessors();
        EventLoopGroup workLoogGroup = new NioEventLoopGroup(processorsNumber * 2, threadFactory, SelectorProvider.provider());
        //指定Netty的Boss线程和work线程
        serverBootstrap.group(bossLoopGroup , workLoogGroup);
        //如果是以下的申明方式,说明BOSS线程和WORK线程共享一个线程池
        //(实际上一般的情况环境下,这种共享线程池的方式已经够了)
        //serverBootstrap.group(workLoogGroup);

        //========================下面我们设置我们服务的通道类型
        //只能是实现了ServerChannel接口的“服务器”通道类
        serverBootstrap.channel(NioServerSocketChannel.class);
        //当然也可以这样创建(那个SelectorProvider是不是感觉很熟悉?)
        //serverBootstrap.channelFactory(new ChannelFactory() {
        //  @Override
        //  public NioServerSocketChannel newChannel() {
        //      return new NioServerSocketChannel(SelectorProvider.provider());
        //  }
        //});

        //========================设置处理器
        //为了演示,这里我们设置了一组简单的ByteArrayDecoder和ByteArrayEncoder
        //Netty的特色就在这一连串“通道水管”中的“处理器”
        serverBootstrap.childHandler(new ChannelInitializer() {
            /* (non-Javadoc)
             * @see io.netty.channel.ChannelInitializer#initChannel(io.netty.channel.Channel)
             */
            @Override
            protected void initChannel(NioSocketChannel ch) throws Exception {
                ch.pipeline().addLast(new ByteArrayEncoder());
                ch.pipeline().addLast(new TCPServerHandler());
                ch.pipeline().addLast(new ByteArrayDecoder());
            }
        });

        //========================设置netty服务器绑定的ip和端口
        serverBootstrap.option(ChannelOption.SO_BACKLOG, 128);
        serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
        serverBootstrap.bind(new InetSocketAddress("0.0.0.0", 83));
        //还可以监控多个端口
        //serverBootstrap.bind(new InetSocketAddress("0.0.0.0", 84));
    }
}

   
   
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
package demo.com.test.io.netty;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.util.AttributeKey;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

@Sharable
public class TCPServerHandler extends ChannelInboundHandlerAdapter{
    /**
     * 日志
     */
    private static Log LOGGER = LogFactory.getLog(TCPServerHandler.class);

    /**
     * 每一个channel,都有独立的handler、ChannelHandlerContext、ChannelPipeline、Attribute
     * 所以不需要担心多个channel中的这些对象相互影响。
* 这里我们使用content这个key,记录这个handler中已经接收到的客户端信息。 */
private static AttributeKey content = AttributeKey.valueOf("content"); /* (non-Javadoc) * @see io.netty.channel.ChannelInboundHandlerAdapter#channelRead(io.netty.channel.ChannelHandlerContext, java.lang.Object) */ @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { TCPServerHandler.LOGGER.info("channelRead(ChannelHandlerContext ctx, Object msg)"); /* * 我们使用IDE工具模拟长连接中的数据缓慢提交。 * 由read方法负责接收数据,但只是进行数据累加,不进行任何处理 * */ ByteBuf byteBuf = (ByteBuf)msg; try { StringBuffer contextBuffer = new StringBuffer(); while(byteBuf.isReadable()) { contextBuffer.append((char)byteBuf.readByte()); } //加入临时区域 StringBuffer content = ctx.channel().attr(TCPServerHandler.content).get(); if(content == null) { content = new StringBuffer(); ctx.channel().attr(TCPServerHandler.content).set(content); } content.append(contextBuffer); } catch(Exception e) { throw e; } finally { byteBuf.release(); } } /* (non-Javadoc) * @see io.netty.channel.ChannelInboundHandlerAdapter#channelReadComplete(io.netty.channel.ChannelHandlerContext) */ @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { TCPServerHandler.LOGGER.info("super.channelReadComplete(ChannelHandlerContext ctx)"); /* * 由readComplete方法负责检查数据是否接收完了。 * 和之前的文章一样,我们检查整个内容中是否有“over”关键字 * */ StringBuffer content = ctx.channel().attr(TCPServerHandler.content).get(); //如果条件成立说明还没有接收到完整客户端信息 if(content.indexOf("over") == -1) { return; } //当接收到信息后,首先要做的的是清空原来的历史信息 ctx.channel().attr(TCPServerHandler.content).set(new StringBuffer()); //准备向客户端发送响应 ByteBuf byteBuf = ctx.alloc().buffer(1024); byteBuf.writeBytes("您好客户端:我是服务器,这是回发响应信息!".getBytes()); ctx.writeAndFlush(byteBuf); /* * 关闭,正常终止这个通道上下文,就可以关闭通道了 * (如果不关闭,这个通道的回话将一直存在,只要网络是稳定的,服务器就可以随时通过这个回话向客户端发送信息)。 * 关闭通道意味着TCP将正常断开,其中所有的 * handler、ChannelHandlerContext、ChannelPipeline、Attribute等信息都将注销 * */ ctx.close(); } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91

主要代码讲解

//BOSS线程池
EventLoopGroup bossLoopGroup = new NioEventLoopGroup(1);
   
   
   
   
  • 1
  • 2

BOSS线程池实际上就是JAVA NIO框架中selector工作角色(这个后文会详细讲),针对一个本地IP的端口,BOSS线程池中有一条线程工作,工作内容也相对简单,就是发现新的连接;Netty是支持同时监听多个端口的,所以BOSS线程池的大小按照需要监听的服务器端口数量进行设置就行了。

//Work线程池
int processorsNumber = Runtime.getRuntime().availableProcessors();
EventLoopGroup workLoogGroup = new NioEventLoopGroup(processorsNumber * 2, threadFactory, SelectorProvider.provider());
   
   
   
   
  • 1
  • 2
  • 3

这段代码主要是确定Netty中工作线程池的大小,这个大小一般是物理机器/虚拟机器 可用内核的个数 * 2。work线程池中的线程(如果封装的是JAVA NIO,那么具体的线程实现类就是NioEventLoop)都固定负责指派给它的网络连接的事件监听,并根据事件状态,调用不同的ChannelHandler事件方法。而最后一个参数SelectorProvider说明了这个EventLoop所使用的多路复用IO模型为操作系统决定。

serverBootstrap.option(ChannelOption.SO_BACKLOG, 128);
serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
   
   
   
   
  • 1
  • 2

option方法,可以设置这个ServerChannel相应的各种属性(在代码中我们使用的是NioServerSocketChannel);childOption方法用于设置这个ServerChannel收到客户端事件后,所生成的新的Channel的各种属性(代码中,我们生成的是NioSocketChannel)。详细的option参数可以参见ChannelOption类中的注释说明。

四、Netty的重要慨念

Netty线程机制

在前文介绍JAVA对多路复用IO技术的支持中,我们说过,Selector可以是在主线程上面操作,也可以是一个独立的线程进行操作。在Netty中,这里的部分工作就是交给BOSS线程做的。BOSS线程负责发现连接到服务器的新的channel(SocketServerChannel的ACCEPT事件),并且将这个channel经过检查后注册到WORK连接池的某个EventLoop线程中。

而当WORK线程发现操作系统有一个它感兴趣的IO事件时(例如SocketChannel的READ事件)则调用相应的ChannelHandler事件。当某个channel失效后(例如显示调用ctx.close())这个channel将从绑定的EventLoop中被剔除。

在Netty中,如果我们使用的是一个JAVA NIO框架的封装,那么进行这个循环的是NioEventLoop类(实现多路复用的支持时)。参见该类中的processSelectedKeysPlain方法 和 processSelectedKey方法。另外在这个类中Netty解决了之前我们说到的java nio中”Selector.select(timeout) CPU 100%” 的BUG和一个“NullPointerException in Selector.open()”(http://bugs.java.com/view_bug.do?bug_id=6427854)的BUG

processSelectedKeysPlain方法

for (;;) {
    final SelectionKey k = i.next();
    final Object a = k.attachment();
    i.remove();

    if (a instanceof AbstractNioChannel) {
        processSelectedKey(k, (AbstractNioChannel) a);
    } else {
        @SuppressWarnings("unchecked")
        NioTask task = (NioTask) a;
        processSelectedKey(k, task);
    }

    if (!i.hasNext()) {
        break;
    }

    if (needsToSelectAgain) {
        selectAgain();
        selectedKeys = selector.selectedKeys();

        // Create the iterator again to avoid ConcurrentModificationException
        if (selectedKeys.isEmpty()) {
            break;
        } else {
            i = selectedKeys.iterator();
        }
    }
}
   
   
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

processSelectedKey方法:

if (!k.isValid()) {
   // close the channel if the key is not valid anymore
    unsafe.close(unsafe.voidPromise());
    return;
}

try {
    int readyOps = k.readyOps();
    // Also check for readOps of 0 to workaround possible JDK bug which may otherwise lead
    // to a spin loop
    if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
        unsafe.read();
        if (!ch.isOpen()) {
            // Connection already closed - no need to handle write.
            return;
        }
    }
    if ((readyOps & SelectionKey.OP_WRITE) != 0) {
        // Call forceFlush which will also take care of clear the OP_WRITE once there is nothing left to write
        ch.unsafe().forceFlush();
    }
    if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
        // remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking
        // See https://github.com/netty/netty/issues/924
        int ops = k.interestOps();
        ops &= ~SelectionKey.OP_CONNECT;
        k.interestOps(ops);

        unsafe.finishConnect();
    }
} catch (CancelledKeyException ignored) {
    unsafe.close(unsafe.voidPromise());
}
   
   
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

一个Work线程池的线程将按照底层JAVA NIO的Selector的事件状态,决定执行ChannelHandler中的哪一个事件方法(Netty中的事件,包括了channelRegistered、channelUnregistered、channelActive、channelInactive等事件方法)。执行完成后,work线程将一直轮询直到操作系统回复下一个它所管理的channel发生了新的IO事件。

ByteBuf

Netty uses its own buffer API instead of NIO ByteBuffer to represent a sequence of bytes. This approach has significant advantages over using ByteBuffer. Netty’s new buffer type, ChannelBuffer has been designed from the ground up to address the problems of ByteBuffer and to meet the daily needs of network application developers. To list a few cool features:

You can define your own buffer type if necessary.
Transparent zero copy is achieved by a built-in composite buffer type.
A dynamic buffer type is provided out-of-the-box, whose capacity is expanded on demand, just like StringBuffer.
There’s no need to call flip() anymore.
It is often faster than ByteBuffer.
   
   
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

上面的引用来自于JBOSS-Netty官方文档中,对ByteBuf缓存的解释。翻译成中文就是:Netty重写了JAVA NIO框架中的缓存结构,并将这个结构应用在更上层的封装中。

为什么要重写呢?JBOSS-Netty给出的解释是:我写的缓存比JAVA中的ByteBuffer牛。

这里说一说Netty中几个比较特别的ByteBuf实现:

  • io.netty.buffer.EmptyByteBuf:这是一个初始容量和最大容量都为0的缓存区。一般我们用这种缓存区描述“没有任何处理结果”,并将其向下一个handler传递。
  • io.netty.buffer.ReadOnlyByteBuf:这是一个不允许任何“写请求”的只读缓存区。一般是通过Unpooled.unmodifiableBuffer(ByteBuf)方法将某一个正常可读写的缓存区转变而成。如果我们需要在下一个Handler处理的过程中禁止写入任何数据到缓存区,就可以在这个handler中进行“只读缓存区”的转换。
  • io.netty.buffer.UnpooledDirectByteBuf:基本的JAVA NIO框架的ByteBuffer封装。一般我们直接使用这个缓存区实现来处理Handler事件。
  • io.netty.buffer.PooledByteBuf:Netty4.X版本的缓存新特性,主要是为了减少之前unpoolByteBuf在创建和销毁时的GC时间。

Channel

Channel,通道。您可以使用JAVA NIO中的Channel去初次理解它,但实际上它的意义和JAVA NIO中的通道意义还不一样。我们可以理解成:“更抽象、更丰富”。如下如所示:

Netty基础简介_第1张图片

  • Netty中的Channel专门代表网络通信,这个和JAVA NIO框架中的Channel不一样,后者中还有类似FileChannel本地文件IO通道。由于前者专门代表网络通信,所以它是由客户端地址+ 服务器地址 + 网络操作状态构成的,请参见io.netty.channel.Channel接口的定义。
  • 每一个Netty中的Channel,比JAVA NIO中的Channel更抽象。这是为什么呢?在Netty中,不止封装了JAVA NIO的IO模型,还封装了JAVA BIO的阻塞同步IO通信模型。将他们在表现上都抽象成Channel了。这就是为什么Netty中有io.netty.channel.oio.AbstractOioChannel这样的抽象类。
  • 其io.netty.channel.oio.AbstractOioChannel抽象类上的注解也说明得比较清楚:Abstract base class for Channel implementations that use Old-Blocking-IO 您可以这样理解:Netty的Channel更具业务抽象性

ChannelPipeline和ChannelHandler

  • Netty中的每一个Channel,都有一个独立的ChannelPipeline,中文称为“通道水管”。只不过这个水管是双向的里面流淌着数据,数据可以通过这个“水管”流入到服务器,也可以通过这个“水管”从服务器流出。

  • 在ChannelPipeline中,有若干的过滤器。我们称之为“ChannelHandler”(处理器或者过滤器)。同“流入”和“流出”的概念向对应:用于处理/过滤流入数据的ChannelHandler,称之为“ChannelInboundHandler”;用于处理/过滤流出数据的ChannelHandler,称之为“ChannelOutboundHandler”。

Netty基础简介_第2张图片

责任链和适配器的应用

  • 数据在ChannelPipeline中有一个一个的Handler进行处理,并形成一个新的数据状态。这是典型的“责任链”模式。
  • 需要注意,虽然数据管道中的Handler是按照顺序执行的,但不代表某一个Handler会处理任何一种由“上一个handler”发送过来的数据。某些Handler会检查传来的数据是否符合要求,如果不符合自己的处理要求,则不进行处理。
  • 我们可以实现ChannelInboundHandler接口或者ChannelOutboundHandler接口,来实现我们自己业务的“数据流入处理器”或者“数据流出”处理器。
  • 但是这两个接口的事件方法是比较多的,例如ChannelInboundHandler接口一共有11个需要实现的接口方法(包括父级ChannelHandler的,我们在下一节讲解Channel的生命周期时,回专门讲到这些事件的执行顺序和执行状态),一般情况下我们不需要把这些方法全部实现
  • 所以Netty中增加了两个适配器
    “ChannelInboundHandlerAdapter”和“ChannelOutboundHandlerAdapter”来帮助我们去实现我们只需要实现的事件方法。其他的事件方法我们就不需要关心了:

这里写图片描述

在上文给出的服务端示例代码中,书写的业务处理器TCPServerHandler就是继承了ChannelInboundHandlerAdapter适配器。下面,我们将介绍几个常使用的ChannelInboundHandler处理器和ChannelOutboundHandler处理器

ChannelInboundHandler类举例

  • HttpRequestDecoder:实现了Http协议的数据输入格式的解析。这个类将数据编码为HttpMessage对象,并交由下一个ChannelHandler进行处理。
  • ByteArrayDecoder:最基础的数据流输入处理器,将所有的byte转换为ByteBuf对象(一般的实现类是:io.netty.buffer.UnpooledUnsafeDirectByteBuf)。我们进行一般的文本格式信息传输到服务器时,最好使用这个Handler将byte数组转换为ByteBuf对象。
  • DelimiterBasedFrameDecoder:这个数据流输入处理器,会按照外部传入的数据中给定的某个关键字符/关键字符串,重新将数据组装为新的段落并发送给下一个Handler处理器。后文中,我们将使用这个处理器进行TCP半包的问题。
  • 还有很多直接支持标准数据格式解析的处理器,例如支持Google Protocol Buffers
    数据格式解析的ProtobufDecoderProtobufVarint32FrameDecoder处理器。

ChannelOutboundHandler类举例

  • HttpResponseEncoder:这个类和HttpRequestDecoder相对应,是将服务器端HttpReponse对象的描述转换成ByteBuf对象形式,并向外传播。
  • ByteArrayEncoder:这个类和ByteArrayDecoder,是将服务器端的ByteBuf对象转换成byte数组的形式,并向外传播。一般也和ByteArrayDecoder对象成对使用。
  • 还有支持标准的编码成Google Protocol Buffers格式、JBoss Marshalling
    格式、ZIP压缩格式的ProtobufEncoderProtobufVarint32LengthFieldPrependerMarshallingEncoderJZlibEncoder

五、Channel的生命周期

上面讲到了Netty中的重要概念。我们花很大篇幅讲解了Channel、ChannelPipeline、ChannelHandler,以及他们的联系和工作方式。

在说到ChannelInHandler为什么会使用“适配器”模式的时候,特别指出了原因:因为ChannelInHandler接口中的方法加上父级接口中的方法,总共有11个接口事件方法需要实现。而事实上很多时候我们只会关心其中的一个或者两个接口方法。

那么这些方法是什么时候被触发的呢?这就要说到Netty中一个Channel的生命周期了(这里我们考虑的生命周期是指Netty对JAVA NIO技术框架的封装):

Netty基础简介_第3张图片

这里有一个channel事件没有在图中说明,就是exceptionCaught(ChannelHandlerContext, Throwable)事件。只要在调用图中的所有事件方法时,有异常抛出,exceptionCaught方法就会被调用。

另外,不是channelReadComplete(ChannelHandlerContext)方法调用后就一定会调用channelInactive事件方法。channelReadComplete和channelRead是可以反复调用的,只要客户端有数据发送过来。

最后补充一句,这个生命周期的事件方法调用顺序只是针对Netty封装使用JAVA NIO框架时,并且在进行TCP/IP协议监听时的事件方法调用顺序。

六、Netty对IO模型的封装

阻塞和非阻塞:这个概念是针对应用程序而言,是指应用程序中的线程在向操作系统发送IO请求后,是否一直等待操作系统的IO响应。如果是,那么就是阻塞式的;如果不是,那么应用程序一般会以轮询的方式以一定周期询问操作系统,直到某次获得了IO响应为止(轮序间隔应用程序线程可以做一些其他工作)。

同步和异步:IO操作都是由操作系统进行的(这里的IO操作是个广泛概念了:磁盘IO、网络IO都算),不同的操作系统对不同设备的IO操作都有不同的模式。同步和异步这两个概念都指代的操作系统级别,同步IO是指操作系统和设备进行交互时,必须等待一次完整的请求-响应完成,才能进行下一次操作(当然操作系统和设备本身也有很多技术加快这个反应过程,例如“磁盘预读”技术、数据缓存技术);异步IO是指操作系统和设备进行交互时,不必等待本次得到响应,就可以直接进行下一次操作请求。设备处理完某次请求后,会主动给操作系统相应的响应通知

  • 多路复用IO:多路复用IO,从本质上看还是一种同步IO,因为它没有100%消除IO_WAIT,操作系统也没有为它提供“主动通知”机制。但是多路复用IO的处理速度已经相当快了,利用设备执行IO操作的时间,操作系统可以继续执行IO请求。并同样采用周期性轮询的方式,获取一批IO操作请求的执行响应。操作系统支持的多路复用IO技术主要有select、poll、epoll、kqueue。
  • 阻塞式同步IO模型:这个从字面上就很好理解了,应用程序请求IO操作,并一直等待处理结果;操作系统同时也进行IO操作,并等待设备的处理结果;可以看出,应用程序的请求线程和操作系统的内核线程都是等待状态。
  • 非阻塞式同步IO模型:应用程序请求IO,并且不用一直等待返回结果就去做其他事情。隔一定的周期,再去询问操作系统上次IO操作有没有结果,直到某一次询问从操作系统拿到IO结果;操作系统内核线程在进行IO操作时,还是处理一直等待设备返回操作结果的状态。
  • 非阻塞式多路复用IO模型:应用程序请求IO的工作采用非阻塞方式进行;操作系统采用多路复用模式工作。
  • 非阻塞式异步IO模型:应用程序请求IO的工作采用非阻塞方式进行,但是不需要轮询了,因为操作系统异步IO其中一个主要特性就是:可以在有IO响应结果的时候,主动进行通知

以上这些IO工作模型,在JAVA中都能够找到对应的支持:传统的JAVA Socket套接字支持阻塞/非阻塞模式下的同步IO(有的技术资料里面也称为OIO或者BIO);JAVA NIO框架在不同操作系统下支持不同种类的多路复用IO技术(windows下的select模型、Linux下的poll/epoll模型);JAVA AIO框架支持异步IO(windows下的IOCP和Linux使用epoll的模拟AIO)

实际上Netty是对JAVA BIO 、JAVA NIO框架的再次封装。让我们不再纠结于选用哪种底层实现。您可以理解成Netty/MINA 框架是一个面向上层业务实现进行封装的“业务层”框架。而JAVA Socket框架、JAVA NIO框架、JAVA AIO框架更偏向于对下层技术实现的封装,是面向“技术层”的框架。

七、Netty对数据信息格式的封装

“技术层”框架本身只对IO模型技术实现进行了封装,并不关心IO模型中流淌的数据格式;“业务层”框架对数据格式也进行了处理,让我们可以抽出精力关注业务本身。

  • Protobuf数据协议的集成:Netty利用自身的Channelpipeline的设计对Protobuf数据协议进行了无缝结合。
  • JBoss Marshalling数据协议的集成:JBoss Marshalling是一个Java对象的序列化API包,修正了JDK自带的序列化包的很多问题,又保持跟 java.io.Serializable接口的兼容。Netty通过封装这个协议,可以帮助我们在客户端-服务端简便的进行对象系列化和反序列化。
  • HTTP Request / HTTP Response 协议的集成:在Netty中,可以方便的接受和发送Http协议。也就是说,我们可以使用Netty搭建自己的WEB服务器,当然您还可以根据自己的业务要求,方便的设计类似于Struts、SpringMVC这样的WEB框架。

你可能感兴趣的:(java)