1.Netty Demo代码

下面是Netty服务端代码,可以先大致阅读下代码,后面我们将对照着这个代码我们来看看Netty有哪些组件以及他们各自的作用是什么

public class ServerTest {
    /**
     * 服务端口
     */
    private int port=9999;
    /**
     * 开启服务的方法
     */
    public void StartNetty(){
 
        /**创建两个EventLoop的组,EventLoop 这个相当于一个处理线程,
         是Netty接收请求和处理IO请求的线程。不理解的话可以百度NIO图解*/
        /* 
        相关资料:NioEventLoopGroup是一个处理I/O操作的多线程事件循环。
        Netty为不同类型的传输提供了各种EventLoopGroup实现。
        在本例中,我们正在实现一个服务器端应用程序,因此将使用两个NioEventLoopGroup。
        第一个,通常称为“boss”,接受传入的连接。
        第二个,通常称为“worker”,当boss接受连接并注册被接受的连接到worker时,处理被接受连接的流量。
        使用了多少线程以及如何将它们映射到创建的通道取决于EventLoopGroup实现,甚至可以通过构造函数进行配置。
        */
        EventLoopGroup acceptor = new NioEventLoopGroup();
        EventLoopGroup worker = new NioEventLoopGroup();
        try {
            //1、创建启动类
            ServerBootstrap bootstrap = new ServerBootstrap();
            //2、配置启动参数等
            /**设置循环线程组,前者用于处理客户端连接事件,后者用于处理网络IO(server使用两个参数这个)
             *public ServerBootstrap group(EventLoopGroup group)
             *public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup)
             */
            bootstrap.group(acceptor,worker);
            /**设置选项
            * 参数:Socket的标准参数(key,value),可自行百度
            * eg:
            * bootstrap.option(ChannelOption.SO_BACKLOG, 1024);
            *bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
            * */
            bootstrap.option(ChannelOption.SO_BACKLOG, 1024);
 
            //用于构造socketchannel工厂
            bootstrap.channel(NioServerSocketChannel.class);
            /**
             * 传入自定义客户端Handle(服务端在这里搞事情)
             */
            bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                public void initChannel(SocketChannel ch) throws Exception {
                    // 注册handler
                    ch.pipeline().addLast(new SimpleServerHandler());
                }
            });
 
            // 绑定端口,开始接收进来的连接
            ChannelFuture f = bootstrap.bind(port).sync();
 
 
            // 等待服务器 socket 关闭 。
            f.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            acceptor.shutdownGracefully();
            worker.shutdownGracefully();
        }
 
    }
 
 
 
    public static void main(String[] args) {
        new ServerTest().StartNetty();
    }
}

public class SimpleServerHandler extends ChannelInboundHandlerAdapter {
    /**
     * 本方法用于读取客户端发送的信息
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("SimpleServerHandler.channelRead");
        ByteBuf result = (ByteBuf) msg;
        byte[] result1 = new byte[result.readableBytes()];
        // msg中存储的是ByteBuf类型的数据,把数据读取到byte[]中
        result.readBytes(result1);
        String resultStr = new String(result1);
        // 接收并打印客户端的信息
        System.out.println("Client said:" + resultStr);
        // 释放资源,这行很关键
        result.release();
 
        // 向客户端发送消息
        String response = "hello client!";
        // 在当前场景下,发送的数据必须转换成ByteBuf数组
        ByteBuf encoded = ctx.alloc().buffer(4 * response.length());
        encoded.writeBytes(response.getBytes());
        ctx.write(encoded);
        ctx.flush();
    }
 
    /**
     * 本方法用作处理异常
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        // 当出现异常就关闭连接
        cause.printStackTrace();
        ctx.close();
    }
 
    /**
     * 信息获取完毕后操作
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }
 
}

2.Netty核心组件

    1.ServerBootstrap

       netty服务端应用开发的入口,服务端使用ServerBootstrap,客户端使用Bootstrap。对服务端和客户端做配置和启动的类。

    2.Channel

       网络通信的主体,由它负责同对端进行网络通信、注册和数据操作等功能,写数据,读数据操作,对java NIO的channel进行包装,主体就是socket。

    3.EventLoop

       EventLoop 主要是为Channel 处理 I/O 操作,两者配合参与 I/O 操作。实际上是一个单个线程的线程池,对Java NIO中的selector进行优化和封装。EventLoop线程会不断的轮询,看那个Channel准备好read或者write。

    4.EventLoopGroup

       管理EventLoop,实际上是线程池,包含多个子EventLoop。

    5.ChannelHandler

      ChannelHandler 为 Netty 中最核心的组件,它充当了所有处理入站和出站数据的应用程序逻辑的容器。ChannelHandler 主要用来处理各种事件,这里的事件很广泛,比如可以是连接、数据接收、异常、数据转换等。例如下面列举的一些channel的事件,有点类似如filter拦截处理过滤:

  • channelRegistered 当前channel注册到EventLoop
  • channelUnregistered 当前channel从EventLoop取消注册 
  • channelActive 当前channel激活的时候
  • channelInactive 当前channel不活跃的时候,也就是当前channel到了它生命周期末
  • channelRead 当前channel从远端读取到数据
  • channelReadComplete channel read消费完读取的数据的时候被触发

    6.ChannelPipeline

       ChannelPipeline是ChannelHandler的容器,ChannelHandler是多个的,ChannelPipeline维护这ChannelHandler的触发顺序。看下面的图,ChannelPipeline中有多个Outboundhandler和多个inboundhandler,数据首先经过Indoundhandler的处理,处理完成后经过Outboundhandler的处理,然后出去。

                         

netty session共享 netty技术分享_channel

   7.ChannelFuture

       ChannelFuture的作用是用来保存Channel异步操作的结果。Netty是异步的,任何的I/O调用都将立即返回,而不保证这些被请求的I/O操作在调用结束的时候已经完成。你会得到一个返回的ChannelFuture实例,这个实例将给你一些关于I/O操作结果或者状态的信息。

   8.ByteBuf

       Netty提供的字节容器,Java NIO 提供了 ByteBuffer 作为它 的字节容器,但是这个类使用起来过于复杂,而且也有些繁琐。Netty使用ByteBuf来替代ByteBuffer。

   9.编码器和解码器

       网络中传输的是字节码,编码器的作用就是把我们传输的消息变成字节码,解码器的作用就是把字节码变成我们的消息。编码器和解码器一般需要我们自己来编写,当然Netty也提供一些基本的编码器和解码器,例如:

  • DelimiterBasedFrameDecoder 解决TCP的粘包解码器
  • StringDecoder     消息转成String解码器
  • LineBasedFrameDecoder      自动完成标识符分隔解码器
  • FixedLengthFrameDecoder    固定长度解码器,二进制
  • Base64Decoder base64        解码器
  • Base64Encoder base64   编码
  • StringEncoder    消息转成String编码
  • LineBasedFrameDecoder   自动完成标识符分隔编码器   
  • MessageToMessageEncoder 根据 消息对象 编码为消息对象

       我们查看一下这些类的源码,发现它们都实现了channelHandler接口,编码器和解码器也是channelHandler的一种,用来处理数据的,很好理解。

3.Netty线程模型

      通过下面的架构图,我们来从整体上认识一下Netty各个组件的作用和Netty对于线程的使用。看下面的架构图:

netty session共享 netty技术分享_TCP_02

我们对着序号一部分一部分的来看:

  • 序号1,Netty服务端初始化的时候创建了两个NioEventLoopGroup,一个是bossGroup,一个是workerGroup。对应着下面的Demo代码。BossGroup负责接收客户端connect,并生成对应的NioSocketChannel,workerGroup负责channel的读取和写入操作。
EventLoopGroup acceptor = new NioEventLoopGroup();
EventLoopGroup worker = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(acceptor,worker);
  • 序号2,Netty服务端绑定端口,等待客户端连接。对应代码:
ChannelFuture f = bootstrap.bind(port).sync();
  • 序号3,客户端连接服务端,bossGroup生成对应客户端的Channel。
  • 序号4,客户端连接后对应服务端生成的Channel,Channel会注册到NioEventLoop上。多个Channel可以注册到同一个NioEventLoop,前面我们知道NioEventLoop对应Java NIO的selector,selector的作用就是轮询Channel并进行读写操作。
  • 序号5,设置Channel对应的ChannelHandler(读写事件,编码,解码事件等),出现特定事件ChannelHandler做出处理,ChannelPipeline会维护多个ChannelHandler的处理顺序。对应代码
ch.pipeline().addLast(new SimpleServerHandler());

       我们来总结一下,服务端端启动好以后,一个客户端连接了服务端,bossGroup(NioEventLoopGroup)接收到了连接,生成一个channel(NioSocketChannel),这个channel注册到一个NioEventLoop,NioEventLoop监听读写事件,有对应事件发生,channel的多个各种channelHandler(ChannelPipeline来维护处理顺序)对应数据处理,处理完成后返回一个channelFuture。