17 DelimiterBasedFrameDecoder

分隔符与Base64编解码

1 DelimiterBasedFrameDecoder介绍

上一节我们介绍了LineBasedFrameDecoder,其以换行符\n或者\r\n作为依据,遇到\n或者\r\n都认为是一条完整的消息。

DelimiterBasedFrameDecoder与LineBasedFrameDecoder类似,只不过更加通用,允许我们指定任意特殊字符作为分隔符。我们还可以同时指定多个分隔符,如果在请求中发的确有多个分隔符,将会选择内容最短的一个分隔符作为依据。
例如:

   +--------------+
   | ABC\nDEF\r\n |
   +--------------+

如果我们指定分隔符为\n,那么将会解码出来2个消息

   +-----+-----+
   | ABC | DEF |
   +-----+-----+

如果我们指定\r\n作为分隔符,那么只会解码出来一条消息

   +----------+
   | ABC\nDEF |
   +----------+

DelimiterBasedFrameDecoder提供了多个构造方法,最终调用的都是以下构造方法:

public DelimiterBasedFrameDecoder(
            int maxFrameLength, boolean stripDelimiter, boolean failFast, ByteBuf... delimiters)

其中:

maxLength:
表示一行最大的长度,如果超过这个长度依然没有检测到\n或者\r\n,将会抛出TooLongFrameException

failFast:
与maxLength联合使用,表示超过maxLength后,抛出TooLongFrameException的时机。如果为true,则超出maxLength后立即抛出TooLongFrameException,不继续进行解码;如果为false,则等到完整的消息被解码后,再抛出TooLongFrameException异常。

stripDelimiter:
解码后的消息是否去除分隔符。

delimiters:
分隔符。我们需要先将分割符,写入到ByteBuf中,然后当做参数传入。

需要注意的是,netty并没有提供一个DelimiterBasedFrameDecoder对应的编码器实现(笔者没有找到),因此在发送端需要自行编码,添加分隔符。

2 Base64编解码

对于以特殊字符作为报文分割条件的协议的解码器,如:LineBasedFrameDecoder、DelimiterBasedFrameDecoder。都存在一个典型的问题,如果发送数据当中本身就包含了分隔符,怎么办?如:我们要发送的内容为:

hello1\nhello2\nhello3\n

我们需要把这个内容整体当做一个有效报文来处理,而不是拆分成hello1、hello2、hello3。一些同学可能想到那可以换其他的特殊字符,但是如果内容中又包含你想指定的其他特殊字符怎么办呢?

因此我们通常需要发送的内容进行base64编码,base64中总共只包含了64个字符。

image.png

我们可以指定这64个字符之外的其他字符作为特殊分割字符;而接收端对应的进行base64解码,得到对应的原始的二进制流,然后进行处理。Netty提供了Base64Encoder/Base64Decoder来帮我们处理这个问题。需要注意的是,只需要对内容进行base64编码,分隔符不需要编码。

3 DelimiterBasedFrameDecoder结合Base64编解码案例

Server端:DelimiterBasedFrameDecoderServer

public class DelimiterBasedFrameDecoderServer {
   public static void main(String[] args) throws Exception {
      EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)
      EventLoopGroup workerGroup = new NioEventLoopGroup();
      try {
         ServerBootstrap b = new ServerBootstrap(); // (2)
         b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class) // (3)
               .childHandler(new ChannelInitializer<SocketChannel>() { // (4)
                  @Override
                  public void initChannel(SocketChannel ch) throws Exception {
                     ByteBuf delemiter= Unpooled.buffer();
                     delemiter.writeBytes("&".getBytes());
                    //先使用DelimiterBasedFrameDecoder解码,以&作为分割符
                    ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, true, true,delemiter));
                    //之后使用Base64Decoder对数据进行解码,得到报文的原始的二进制流
                    ch.pipeline().addLast(new Base64Decoder());
                    //对请求报文进行处理
                     ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                        @Override
                        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                           if (msg instanceof ByteBuf) {
                              ByteBuf packet = (ByteBuf) msg;
                              System.out.println(
                                    new Date().toLocaleString() + ":" + packet.toString(Charset.defaultCharset()));
                           }
                        }
                     });
                  }
               });
         // Bind and start to accept incoming connections.
         ChannelFuture f = b.bind(8080).sync(); // (7)
         System.out.println("DelimiterBasedFrameDecoderServer Started on 8080...");
         f.channel().closeFuture().sync();
      } finally {
         workerGroup.shutdownGracefully();
         bossGroup.shutdownGracefully();
      }
   }
}

Server接受到的数据,首先我们要去除分隔符,然后才能进行base64解码。因此首选我们添加了DelimiterBasedFrameDecoder根据&处理粘包办包问题,之后使用Base64Decoder进行解码,最后通过一个自定义ChannelInboundHandler打印请求的数据。
client端:DelimiterBasedFrameDecoderClient

public class DelimiterBasedFrameDecoderClient {
   public static void main(String[] args) throws Exception {
      EventLoopGroup workerGroup = new NioEventLoopGroup();
      try {
         Bootstrap b = new Bootstrap(); // (1)
         b.group(workerGroup); // (2)
         b.channel(NioSocketChannel.class); // (3)
         b.option(ChannelOption.SO_KEEPALIVE, true); // (4)
         b.handler(new ChannelInitializer<SocketChannel>() {
            @Override
            public void initChannel(SocketChannel ch) throws Exception {
               ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                   //在于server建立连接后,即发送请求报文
                  public void channelActive(ChannelHandlerContext ctx) {
                     //先对要发送的原始内容进行base64编码
                     ByteBuf content = Base64.encode(Unpooled.buffer().writeBytes("hello&tianshouzhi&".getBytes
                           ()));
                     //之后添加分隔符
                     ByteBuf req = Unpooled.copiedBuffer(content);
                     req.writeBytes("&".getBytes());
                     ctx.writeAndFlush(req);
                        }
               });
            }
         });
         // Start the client.
         ChannelFuture f = b.connect("127.0.0.1",8080).sync(); // (5)
         // Wait until the connection is closed.
         f.channel().closeFuture().sync();
      } finally {
         workerGroup.shutdownGracefully();
      }
   }
}

在编写client端代码时我们先对原始内容进行base64编码,然后添加分割符之后进行输出。

需要注意,虽然Netty提供了Base64Encoder进行编码,这里并没有直接使用,如果直接使用Base64Encoder,那么会对我们输出的所有内容进行编码,意味着分隔符也会被编码,这显然不符合我们的预期,所以这里直接使用了Netty提供了Base64工具类来处理。

如果一定要使用Base64Encoder,那么代码需要进行相应的修改,自定义的ChannelInboundHandler只输出原始内容,之后通过Base64Encoder进行编码,然后需要额外再定义一个ChannelOutboundHandler添加分隔符,如:

ch.pipeline().addLast(new ChannelOutboundHandlerAdapter() {
   @Override
   public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
      if(msg instanceof ByteBuf){
         ((ByteBuf) msg).writeBytes("&".getBytes());
      }
   }
});
ch.pipeline().addLast(new Base64Encoder());
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
    //在于server建立连接后,即发送请求报文
   public void channelActive(ChannelHandlerContext ctx) {
      ByteBuf req = Unpooled.buffer().writeBytes("hello&tianshouzhi&".getBytes());
      ctx.writeAndFlush(req);
                   }
});

上述两种方案效果是等价的。

当我们先后启动server和client后,会看到server端控制台输出:

DelimiterBasedFrameDecoderServer Started on 8080...
2018-9-8 13:56:32:hello&tianshouzhi&

说明经过base64编码后,我们的请求中可以包含分隔符作为内容。

如果我们将base64编解码相关逻辑去掉,你将会看到的输出是:

DelimiterBasedFrameDecoderServer Started on 8080...
2018-9-8 14:13:31:hello
2018-9-8 14:13:31:tianshouzhi

也就是说,原始内容也被误分割了,解码失败,读者可以自行验证。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,651评论 18 139
  • 一、Java 简介 Java是由Sun Microsystems公司于1995年5月推出的Java面向对象程序设计...
    子非鱼_t_阅读 4,183评论 1 44
  • 最近,有种本书很火叫做《刻意练习》 。这本书是万维钢老师翻译的一本学习方法论。有很多人对这种方法给予了总结...
    默默地走下去阅读 720评论 1 10
  • 1.下载安装包并解压到/home/app目录: tar -zxvf go1.6.linux-amd64.tar.g...
    GilbertW阅读 303评论 0 0
  • 摘要: 原创出处:www.bysocket.com 泥瓦匠BYSocket 希望转载,保留摘要,谢谢! 简单就好,...
    子木聊出海阅读 1,150评论 0 51