Netty学习笔记_10(Netty核心模块组件)

15 篇文章 0 订阅

1、BootStrap,ServerBootStrap

  1. 一个Netty应用通常由一个BootStrap开始,主要作用是配置整个Netty程序,串联各个组件,Netty中的BootStrap类是客户端程序的启动引导类,ServerBootStrap是服务端启动引导类
  2. 常见方法:

    public ServerBootstrap group  (EventLoopGroup parentGroup,

    EventLoopGroup childGroup)

    作用于服务器端,用来设置两个EventLoop

    public B group(EventLoopGroup group)

    作用于客户端,用来设置一个EventLoopGroup

    public B channel(Class<? extends C> channelClass)

    用来设置一个服务端的通道实现

    public <T> B option(ChannelOption<T> option, T value)

    用来给ServerChannel添加配置

    Public <T> ServerBootStrap childOption  (ChannelOption<T> childOption, T value)

    用来给接收到的通道添加配置

    public ServerBootstrap childHandler (ChannelHandler childHandler)

    用来设置业务处理类(自定义handler)

    childHandler主要针对于服务器端为客户端划分;

    public B handler(ChannelHandler handler)

    Handler则在服务器端本身发挥作用

    public ChannelFuture bind(int inetPort)

    用于服务器,设置占用的端口号

    public ChannelFuture connect (String inetHost,int inetPort)

    该方法用于客户端,用来连接服务器

 2、Future,ChannelFuture

  1. Netty中所有操作都是异步的,不能立即得知消息是否被正确处理,但可以过一会等它执行完成或直接注册一个监听器,具体实现通过Future和ChannelFuture,它们可以注册一个监听,当操作执行成功或失败时,监听会自动触发注册的监听事件
  2. 常用方法:

    Channel channel()

    返回当前正在进行IO操作的通道

    ChannelFuture sync()

    等待异步操作执行完毕

 3、Channel

  1. Channel是Netty网络通信组件,能够用于执行网络IO操作
  2. 通过Channel可获得当前网络连接的通道状态
  3. 通过Channel可获得网络连接配置参数
  4. Channel提供异步的网络IO操作(建立连接,读写,绑定端口),异步调用意味着任何IO调用都将立即返回,但不保证在调用结束时所请求的IO操作已完成
  5. 调用立即返回一个ChannelFuture实例,通过注册监听器,可以在IO操作成功、失败或取消时回调通知调用方
  6. 支持关联IO操作与对应的处理程序
  7. 不同协议、不同的阻塞类型的连接是不同的,Channel类型与之对应,常用的Channel类型有:

    NioSocketChannel

    异步的客户端TCP socket连接

    NioServerSocketChannel

    异步的服务器TCP socket连接

    NioDatagramChannel

    异步的UDP连接

    NioSctpChannel

    异步的客户端Sctp连接

    NioSctpServerChannel

    异步的服务端Sctp连接

 4、Selector

  1. Netty基于Selector对象实现IO多路复用,通过Selector一个线程可以监听多个连接的Channel事件
  2. 当向一个Selector中注册Channel后,Selector内部的机制就可以自动不断地查询(select)这些Channel中是否有就绪的IO事件(可读、可写、完成网络连接等),这样程序就可以简单地使用一个线程高效地管理多个Channel

5、ChannelHandler及其实现类

  1. ChannelHandler是一个接口,处理IO事件 / 拦截IO操作,并将其转发到其ChannelPipeline(业务处理链)中的下一个处理程序
  2. ChannelHandler本身并没有提供很多方法,因为这个接口有许多方法需要实现,方便使用的时候,可以继承它的子类
  3. ChannelHandler及其实现类:

                                                                     接           口

    ChannelInboundHandler用于处理Channel(通道)入站IO事件;
    ChannelOutboundHandler用于处理Channel(通道)出站IO事件;
                                                            适             配              器
    ChannelOutboundHandlerAdapter用于处理出站IO操作;
    ChanneInboundHandlerAdapter用于处理入站IO操作;
    ChannelDuplexHandler用于处理入站和出站事件。

【注】以客户端应用程序为例:如果事件运动方向客户端服务器,我们称之为“出站”,即客户端发送的数据会通过pipeline中的一系列ChannelOutboundHandler,并被这些Handler处理,反之称为“入站”。

        4、自定义handler类继承ChannelInboundHandlerAdapter,通过重写相应方法实现业务逻辑:

public class ChannelInboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelInboundHandler {

 

               public void channelRegistered(ChannelHandlerContext ctx) throws Exception {

                             ctx.fireChannelRegistered();

               }

                public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {

                              ctx.fireChannelUnregistered();

                }

               //通道就绪事件

                public void channelActive(ChannelHandlerContext ctx) throws Exception {

                              ctx.fireChannelActive();

                }

               public void channelInactive(ChannelHandlerContext ctx) throws Exception {

                              ctx.fireChannelInactive();

                }

                  //通道读取数据事件

                public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

                              ctx.fireChannelRead(msg);

                }

              //读取数据完毕

               public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {

                              ctx.fireChannelReadComplete();

                }

                public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {

                               ctx.fireUserEventTriggered(evt);

               }

               public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {

                              ctx.fireChannelWritabilityChanged();

               }

               //异常捕获事件

               public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)   throws Exception {

                             ctx.fireExceptionCaught(cause);

                }

}

6、Pipeline和ChannelPipeline

  1. ChannelPipeline是一个handler的集合,负责处理和拦截inbound / outbound的事件或操作。ChannelPipeline是ChannelHandler实例对象的链表
  2. ChannelPipeline实现了一种高级形式的拦截过滤器模式,用户可以完全控制事件的处理方式,以及Channel中各个ChannelHandler的交互模式
  3. 一个Channel包含了一个ChannelPipeline,而一个ChannelPipeline中又维护了一个由ChannelHandlerContext组成的双向链表,且每个ChannelHandlerContext中由关联了一个ChannelHandler;
  4. 入站事件和出站事件在一个双向链表中,入站事件会从链表head往后传递到最后一个入站的handler,出站事件会从链表tail往前传递到第一个出站的handler,两者互不干扰。

    ...

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {

                      ChannelPipeline pipeline = ch.pipeline();

                     pipeline.addLast("MyHttpServerCC",new HttpServerCodec());
                    //增加一个自定义的处理器
                    pipeline.addLast("MyHttpServerHandler",new TestHttpServerHandler());

    断点        System.out.println("ok");
    }

    ...

    这段代码中显然有两个handler对象HttpServerCodec()和TestHttpServerHandler(),通过debug来观察ChannelPipeline构成的双向链表:

    显然,pipeline 对象中包含了一个head和tail,从head中进入,可以看到链表第一个元素为我们编写的初始化文件TestServerInitializer,它本质上也是一个自定义的handler;

    而next(下一个)即为HttpServerCodec

    继续next,发现下一个handler为TestHttpServerHandler

    继续next,得到链表尾元素,即默认的DefultChannelPipeline$TailContext,其中不再包含handler;

    反向从tail进入,可以发现末尾元素没有handler,且next属性为null,prev指向它的前一个handler,即TestHttpServerHandler;

    TestHttpServerHandlerprev指向前一个handler,即HttpServerCodec

    HttpServerCodec的前一个handler则是链表中第一个元素,即TestServerInitializer。

    【注】在TestHttpServerHandler中传入的ctx,本质上是DefaultChannelHandlerContext类型的数据

  5. 常用方法:

    ChannelPipeline addFirst(String name, ChannelHandler handler);

    把一个业务处理类(handler)添加到链表中第一个位置

    ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler);

    把一个业务处理类(handler)添加到链表中最后一个位置

 7、ChannelHandlerContext

  1. 保存Channel相关的所有上下文信息,同时关联一个ChannelHandler对象;
  2. ChannelHandlerContext中包含一个具体的ChannelHandler,同时ChannelHandlerContext中绑定了对应的pipeline和Channel的信息,方便对ChannelHandler进行调用。

    TestHttpServerHandler

    ......

    @Override

    protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {

            System.out.println(ctx.getClass());

            if (msg instanceof HttpRequest){

    断点        System.out.println("ctx 类型为:"+ctx.getClass());

                System.out.println("pipeline hashcode = "+ctx.pipeline()

    .hashCode() + "TestHttpServerHandler hash = "

    +this.hashCode());

                System.out.println("msg 类型为:"+msg.getClass());

                System.out.println("客户端地址为:"+ctx.channel()

    .remoteAddress());

                HttpRequest httpRequest = (HttpRequest) msg;

                URI uri = new URI(httpRequest.uri());

                if ("/favicon.ico".equals(uri.getPath())){

                    System.out.println("请求了 favicon.ico,不做响应");

                    return;

                }

    ......

    }

    ......

    对以上代码进行debug,分析ctx的数据类型和包含内容

    1. 首先可以得出ctx的数据类型为DefaultChannelHandlerContext,ctx中包含的handler为TestHttpServerHandler
    2. 由于已经证明TestHttpServerHandler为最后一个执行的handler,故其位于双向链表表尾,next值为null。Prev指向前一个DefaultChannelHandlerContext,其包含的handler为HttpServerCodeC
    3. Ctx还关联了pipeline信息,通过pipeline还可以找到对应的channel
  3. 常用方法:

    ChannelFuture close()

    关闭通道

    ChannelOutboundInvoker flush()

    刷新

    ChannelFuture writeAndFlush(Object msg)

    将数据写入ChannelPipeline中当前ChannelHandler的下一个ChannelHandler开始处理(出站)

8、ChannelOption

  1. Netty在创建Channel实例后,一般需要通过ChannelOption参数来配置channel的相关属性
  2. ChannelOption参数如下:

    ChannelOption.SO_BACKLOG

    对应TCP/IP协议listen函数中的backlog参数,用来初始化服务器可连接队列大小。服务端处理客户端连接请求是顺序处理的,所以同一时间只能处理一个客户端连接,多个客户端来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理,backlog参数指定了队列的大小。

    ChannelOption.SO_KEEPALIVE

    一直保持连接活动状态

9、EventLoopGroup和其实现类NioEventLoopGroup

  1. EventLoopGroup本质上是一个接口(interface),继承了EventExecutorGroup,通过继承关系分析,发现EventLoopGroup的实现子类是MultithreadEventLoopGroup下的NioEventLoopGroup
  2. EventLoopGroup是一组EventLoop的抽象,Netty为了更好的利用多核CPU资源,一般会有多个EventLoop同时工作,每个EventLoop维护了一个selector实例
  3. EventLoopGroup提供next接口,可以从组里按照一定规则获取其中一个EventLoop来处理任务。在Netty服务器端编程中,我们一般都需要提供两个EventLoopGroup,例如BossEventLoopGroup和WorkerEventLoopGroup。
  4. 通常一个服务端口(ServerSocketChannel)对应一个Selector和一个EventLoop线程。BossEventLoopGroup负责接收客户端连接并将SocketChannel交给WorkerEventLoopGroup进行IO处理
    • BossEventLoopGroup通常是一个单线程的EventLoop,EventLoop维护了一个注册了ServerSocketChannel的Selector实例。BossEventLoopGroup不断轮询Selector将连接事件分离出来。
    • 通常是OP_ACCEPT事件,然后将接收的SocketChannel交给WorkerEventLoopGroup。
    • WorkerEventLoopGroup会由next选择其中一个EventLoop将这个SocketChannel注册到其维护的Selector并对其后续的IO事件进行处理

    ......

            EventLoopGroup bossGroup = new NioEventLoopGroup(1);

            EventLoopGroup workerGroup = new NioEventLoopGroup();

    ......

    对上述代码段进行debug,容易得到bossGroup 的children中只包含了一个EventLoop,而 workerGroup 的children中包含了8个EventLoop,这是由于我们设置了bossGroup 为单线程,而workerGroup 默认为CPU核数*2的多线程。

    运行服务器端代码后,多开客户端,并输出当前线程及channel

    发现:前8个客户端连接的线程为[nioEventLoopGroup-3-1~3-8],其对应的channel也各不相同,第9个客户端连接的线程虽然为nioEventLoopGroup-3-1,但其对应的channel和第1个客户端也不相同。

    这表明了WorkerEventLoopGroup会通过next方法选择其中的一个EventLoop,而每个EventLoop对应一个Selector不断轮询其队列中的IO请求并分配channel进行处理

  5. 常用方法:

    Public NioEventLoopGroup();     构造方法

    Public Future<?>shutdownGracefully();       断开连接,关闭线程

10、Unpolled类

  1. Unpolled类是Netty提供的专门用于操作缓冲区(即Netty的数据容器)的工具类
  2. 常用方法:通过给定的数据和字符编码返回一个ByteBuf对象:Public static ByteBuf copierBuffer(CharSequence string, Charset charset)
  3. Unpolled获取Netty数据容器ByteBuf的基本使用:

    创建一个对象,该对象包含一个数组 array,是一个byte[10]

    在Netty的buffer中,读取buffer中的数据不需要通过flip()方法进行状态切换,其底层维护了readerIndex 和 writerIndex

    通过readerIndexwriterIndex 和 capacity,将buffer分割为三个区域

    0——>readerIndex:                                                                                            | 已经读取过的区域

    readerIndex——>writerIndex:                                                                     | 可读但还未被读取的区域

    writerIndex——>capacity:                                                                             | 可写的区域

  4. 每调用一次byteBuf.readByte()读取数据,byteBuf的readerIndex便减少1;调用byteBuf.getByte()则不会引起readerIndex的变化
  5. public abstract CharSequence getCharSequence(int index, int length, Charset charset) 的作用是按照某一个范围进行数据的读取,index表示起始位置,length表示读取长度,charset表示字符编码格式

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值