Netty学习笔记_14(Netty编解码器和handler调用机制)

15 篇文章 0 订阅

1、Netty的主要组件有:Channel、EventLoop、ChannelFuture、ChannelHandler、ChannelPipeline等。

  • ChannelHandler充当了处理入站和出站数据的应用程序逻辑的容器。例如:实现ChannelInboundHandler接口(或ChannelInboundHandlerAdapter),可以接收入站事件和数据,这些数据将被业务逻辑处理;当给客户端回送响应时,也可以通过ChannelInboundHandler冲刷数据。业务逻辑通常写在一个或多个ChannelInboundHandler中。
  • ChannelOutboundHandler与之类似,只不过是用来处理出站数据的。
  • ChannelPipeline提供了ChannelHandler链的容器(pipeline.addLast()可以将一系列的handler以链表的形式添加),以客户端应用程序为例,如果事件运动方向为客户端->服务器,称之为“出站”,即客户端发送给服务器的数据通过pipeline中的一系列ChannelOutboundHandler,并被这些handler处理。反之则称为“入站”。

【注】“出站”“入站”不是绝对概念,而是相对的。若以客户端为观察主体,则客户端->服务器称为“出站服务器->客户端称为“入站;若以服务器为观测主体,则与之相反。简而言之,如果把网络链路看作一条公路,客户端和服务器分别为公路上的收费站,进入收费站的行为统称为“入站”,离开收费站的行为统称为“出站”。


2、编解码器

  1. Netty发送或接收一个消息时,会发生一次数据的转换:入站消息会被解码,二进制->其他格式 ||出站消息会被编码,其他格式->二进制
  2. Netty提供了一系列编码解码器,都实现了ChannelInboundHandler  / ChannelOutboundHandler接口。以入站消息为例,对于每个从channel中读取的信息,重写并调用了channelRead方法,随后,它将调用解码器提供的decode()方法进行解码,并将解码后的字节发送给ChannelPipeline中的下一个ChannelInboundHandler。

【注】消息入站后,会经过ChannelPipeline中的一系列ChannelHandler处理,这些handler中有Netty已经实现的,也有我们重新实现的自定义handler,但它们都需要实现ChannelInboundHandler接口;即消息入站后所经过的handler链是由一系列ChannelInboundHandler组成的,其中第一个经过的handler就是解码器Decoder;消息出站与入站类似,但消息出站需要经过一系列ChannelOutboundHandler的实现类,最后一个经过的handler是编码器Encoder。

2-1、解码器——ByteToMessageDecoder

  1.  由于不知道远程节点是否会发送一个完整的信息,TCP可能出现粘包和拆包的问题。ByteToMessageDecoder的作用就是对入站的数据进行缓冲,直至数据准备好被处理。
  2. ByteToMessageDecoder实例分析

    public class ToIntgerDecoder extends ByteToMessageDecoder{
            @Override
            protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception{
                    if (in.readableBytes() >= 4) {
                             out.add(in.readint());
                    }
            }
    }

    在此实例中,假设通过Socket发送了8字节数据,每次入站从ByteBuf中读取个4字节,将其解码为一个int,并加入一个List中。当没有更多的元素可以被添加到该List中时,代表此次发送的数据已发送完成,List中的所有内容会被发送给下一个ChannelInboundHandler。Int在被添加到List中时,会被自动装箱为Intger,调用readInt()方法前必须验证所输入的ByteBuf是否有足够的数据。

     

 2-2、Netty的handler链调用机制

2-2-1、实例要求 

  • 使用自定义的编码解码器
  • 客户端可以发送一个Long类型的数据给服务器;服务器也可以发送一个Long类型的数据给客户端

2-2-2、实现

  • 客户端和服务器主程序负责绑定端口,进行连接;
  • 客户端和服务器都实现一个自定义的初始化类Initializer,在初始化类中将编解码器和自定义的handler链式添加pipeline中;
  • 自定义编解码器需要实现ByteToMessageDecoder和MessageToByteEncoder。

自定义编码器——myLongToByteEncoder.java

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;

public class myLongToByteEncoder extends MessageToByteEncoder<Long> {
    //编码的方法
    @Override
    protected void encode(ChannelHandlerContext ctx, Long msg, ByteBuf out) throws Exception {
        System.out.println("Encoder被调用");
        System.out.println("msg = "+msg);
        out.writeLong(msg);
    }
}

自定义解码器——myByteToLongDecoder.java

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;

import java.util.List;

public class myByteToLongDecoder extends ByteToMessageDecoder {
    /**
     *
     * decode会根据接收的数据,被调用多次,直到确定没有新的元素添加到list,或者时ByteBuf没有更多的可读字节为止
     * 如果list out不为空,就会将list的内容传递给下一个channelInboundHandler处理,该方法也会被调用多次
     *
     * @param ctx 上下文对象
     * @param in 入站的ByteBuf
     * @param out  List集合,将解码后的数据传给下一个handler处理
     * @throws Exception
     */
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        // long——>8个字节,需要判断有8个字节,才能读取一个long
        if (in.readableBytes()>=8){
            out.add(in.readLong());
        }
        System.out.println("Decoder被调用");
    }
}

服务器端——myServer.java

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public class myServer {
    public static void main(String[] args) throws InterruptedException {

        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try{
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup,workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new mySeverInitializer());   //自己定义的初始化类


            ChannelFuture channelFuture = serverBootstrap.bind(7001).sync();
            channelFuture.channel().closeFuture().sync();
        }finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

客户端——myClient.java

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;

public class myClient {
    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup group = new NioEventLoopGroup();

        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new myClientInitializer()); //自定义一个初始化对象
            ChannelFuture channelFuture = bootstrap.connect("localhost", 7001).sync();

            channelFuture.channel().closeFuture().sync();
        }finally {
            group.shutdownGracefully();
        }
    }
}

服务器端初始化类——mySeverInitializer.java

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;

public class mySeverInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();

        //入站的handler进行解码 myByteToLongDecoder
        pipeline.addLast(new myByteToLongDecoder());

        pipeline.addLast(new myLongToByteEncoder());

        //自定义handler处理业务逻辑
        pipeline.addLast(new myServerHandler());
    }
}

客户端初始化类——myClientInitializer.java

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;

public class myClientInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();

        //加入一个出站的handler,对数据进行编码
        pipeline.addLast(new myLongToByteEncoder());
        //这是一个入站的解码器
        pipeline.addLast(new myByteToLongDecoder());
        //加入一个自定义handler,处理业务
        pipeline.addLast(new myClientHandler());
    }
}

服务器自定义handler——myServerHandler.java

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

public class myServerHandler extends SimpleChannelInboundHandler<Long> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Long msg) throws Exception {
        System.out.println("从客户端"+ctx.channel().remoteAddress() + " 读取到Long "+ msg);

        //给客户端回送一个long
        ctx.writeAndFlush(987654L);

    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

客户端自定义handler——myClientHandler.java

package com.SF.Netty.InBoundAndOutBound;

import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;

public class myClientHandler extends SimpleChannelInboundHandler<Long> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Long msg) throws Exception {
        System.out.println("服务器的ip = "+ctx.channel().remoteAddress());
        System.out.println("收到服务器数据 = "+msg);
    }
    //重写channelActive,发送数据

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("myClientHandler 发送数据");

        ctx.writeAndFlush(12345678L); //发送一个long
        //该处理器的前一个handler 是 myLongToByteEncoder
        //myLongToByteEncoder 的父类是 MessageToByteEncoder
        //父类 MessageToByteEncoder
        /**
         * public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
         *         ByteBuf buf = null;
         *         try {
         *             if (acceptOutboundMessage(msg)) {
         *             //判断当前 msg是否是该被处理的类型,如果是就处理,否则跳过encode()
         *                 @SuppressWarnings("unchecked")
         *                 I cast = (I) msg;
         *                 buf = allocateBuffer(ctx, cast, preferDirect);
         *                 try {
         *                     encode(ctx, cast, buf);
         *                 } finally {
         *                     ReferenceCountUtil.release(cast);
         *                 }
         *
         *                 if (buf.isReadable()) {
         *                     ctx.write(buf, promise);
         *                 } else {
         *                     buf.release();
         *                     ctx.write(Unpooled.EMPTY_BUFFER, promise);
         *                 }
         *                 buf = null;
         *             } else {
         *                 ctx.write(msg, promise);
         *             }
         */
        //因此我们在编写encoder时,要注意传入的数据类型和处理的数据类型一致
//        ctx.writeAndFlush(Unpooled.copiedBuffer("abcdabcdabcdabcd", CharsetUtil.UTF_8));
    }
}

2-2-3、结论

  1. 不论是解码器handler还是编码器handler,接收的消息类型必须与待处理的消息类型一致,否则该handler不会执行;
  2. 在解码器进行数据解码时,需要判断缓冲区ByteBuf的数据是否足够,否则收到的结果可能和期望结果不一致。

2-3、Netty其他编解码器

 2-3-1、解码器-ReplayingDecoder

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ReplayingDecoder;

import java.util.List;
public class myByteToLongDecoder2 extends ReplayingDecoder<Void> {
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {

        System.out.println("Decoder2被调用");
        //在 ReplayingDecoder 不需要判断数据是否足够读取,内部会判断处理
        out.add(in.readLong());
    }
}

声明: public abstract class ReplayingDecoder<S> extends ByteToMessageDecoder

解释:ReplayingDecoder扩展了ByteToMessageDecoder类,使用这个类,我们不必调用readableBytes()方法,参数S指定了用户状态管理的类型,其中,Void代表不需要状态管理。

局限性:不是所有的ByteBuf操作都支持,如果调用了一个不被支持的方法,将会抛出UnsupportedOperationExceptionReplayingDecoder在网络缓慢且消息格式复杂时,消息会被拆成多个碎片,导致速度变慢。

2-3-2、其他解码器

  1. LineBasedFrameDecoder:它使用行尾控制字符(\n或\r\n)作为分割符来解析数据;
  2. DelimiterBasedFrameDecoder:使用自定义的特殊字符作为分隔符;
  3. HttpObjectDecoder:一个HTTP数据的解码器;
  4. LengthFieldBasedFrameDecoder:通过指定长度来标识整包信息,这样就可以自动的处理粘包和半包信息

3、log4j整合到Netty

3-1、log4j概述

log4j是Apache提供的一个功能强大的日志组件,通过log4j,我们可以指定日志的输出格式、输出位置和日志的级别等内容,使用log4j.properties(键=值)作为配置文件时,主要使用方法为:

  1. 配置根Logger,负责处理日志记录的大部分操作,语法为:log4j.rootLogger = [ level ] , appenderName, appenderName, …。其中,level 是日志记录的优先级,建议使用ERROR、WARN、INFO、DEBUG四个优先级;appenderName就是指定日志信息输出到哪个地方。可同时指定多个输出目的地。
  2. 配置日志信息输出目的地 Appender,负责控制日志记录操作的输出。语法为:log4j.appender.appenderName = fully.qualified.name.of.appender.class,其中appenderName与第一步起得appenderName一致,log4j提供的appenderorg.apache.log4j.ConsoleAppender(控制台);..FileAppender(文件);..DailyRollingFileAppender(每天产生一个日志文件);..RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件);..WriterAppender(将日志信息以流格式发送到任意指定的地方)等;
  3. 配置日志信息的格式(布局)Layout,负责格式化Appender的输出。语法为:log4j.appender.appenderName.layout  =  fully.qualified.name.of.layout.classlog4j提供的layoutHTMLLayout(以HTML表格形式布局);PatternLayout(可以灵活地指定布局模式);SimpleLayout(包含日志信息的级别和信息字符串);TTCCLayout(包含日志产生的时间、线程、类别等等信息)

3-2、配置log4j

在pom.xml中配置log4j的依赖

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.25</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.25</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-simple</artifactId>
    <version>1.7.25</version>
</dependency>
<!--日志end-->

配置log4j.properties

log4j.rootLogger=DEBUG, STDOUT
log4j.appender.STDOUT = org.apache.log4j.ConsoleAppender
log4j.appender.STDOUT.layout = org.apache.log4j.PatternLayout
log4j.appender.STDOUT.layout.ConversionPattern = [%p] %C{1} - %m%n
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值