Netty中解码基于分隔符的协议和基于长度的协议
在使用Netty的过程中,你将会遇到需要解码器的基于分隔符和帧长度的协议。本节将解释Netty所提供的用于处理这些场景的实现。
基于分隔符的协议
基于分隔符的(delimited)消息协议使用定义的字符来标记的消息或者消息段(通常被称为帧)的开头或者结尾。由RFC文档正式定义的许多协议(如SMTP、POP3、IMAP以及Telnet名称)都是这样的。此外,当然,私有组织通常也拥有他们自己的专有格式。无论你使用什么样的协议,下面列出的解码器都能帮助你定义可以提取由任意标记(token)序列分隔的帧的自定义解码器。
用于处理基于分隔符的协议和基于长度的协议的解码器
DelimiterBasedFrameDecoder 使用任何由用户提供的分隔符来提取帧的通用解码器。
LineBasedFrameDecoder 提取由行尾符(\n或者\r\n)分隔的帧的解码器。这个解码器比DelimiterBasedFrameDecoder更快。
由行尾符分隔的帧图解析

下面代码清单展示了如何使用LineBasedFrameDecoder来处理上图所示的场景。
public class LineBasedHandlerInitializer extends ChannelInitializer<Channel>{
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new LineBasedFrameDecoder(64 * 1024));// 该LineBasedFrameDecoder将提取的帧转发给下一个ChannelInboundHandler
pipeline.addLast(new FrameHandler());// 添加FrameHandler以接收帧
}
public static final class FrameHandler extends SimpleChannelInboundHandler<ByteBuf> {
@Override
public void channelRead0(ChannelHandlerContext ctx, //传入了单个帧的内容
ByteBuf msg) throws Exception {
// Do something with the data extracted from the frame
}
}
}
如果你正在使用除了行尾符之外的分隔符分隔的帧,那么你可以以类似的方式使用DelimiterBasedFrameDecoder,只需要将特定的分隔符序列指定到其构造函数即可。
这些解码器是实现你自己的基于分隔符的协议的工具。作为示例,我们将使用下面的协议规范:
①传入数据流是一系列的帧,每个帧都由换行符(\n)分隔;
②每个帧都由一系列的元素组成,每个元素都由单个空格字符分隔;
③一个帧的内容代表一个命令,定义为一个命令名称后跟着数目可变的参数。
我们用于这个协议的自定义解码器将定义以下类:
①Cmd—将帧(命令)的内容存储在ByteBuf 中,一个ByteBuf 用于名称,另一个用于参数;
②CmdDecoder—从被重写了的decode()方法中获取一行字符串,并从它的内容构建一个Cmd的实例;
③CmdHandler —从CmdDecoder 获取解码的Cmd 对象,并对它进行一些处理;
④CmdHandlerInitializer为了简便起见,我们将会把前面的这些类定义为专门的ChannelInitializer的嵌套类,其将会把这些ChannelInboundHandler安装到ChannelPipeline中。
这个解码器的关键是扩展LineBasedFrameDecoder,详看如下代码 使用ChannelInitializer 安装解码器:
public class CmdHandlerInitializer extends ChannelInitializer<Channel> {
final byte SPACE = (byte) ' ';
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new CmdDecoder(64 * 1024));// 添加CmdDecoder 以提取Cmd 对象,并将它转发给下一个ChannelInboundHandler
pipeline.addLast(new CmdHandler());// 添加CmdHandler以接收和处理Cmd对象
}
public static final class Cmd {// Cmd POJO
private final ByteBuf name;
private final ByteBuf args;
public Cmd(ByteBuf name, ByteBuf args) {
this.name = name;
this.args = args;
}
public ByteBuf name() {
return name;
}
public ByteBuf args() {
return args;
}
}
public static final class CmdDecoder extends LineBasedFrameDecoder {
public CmdDecoder(int maxLength) {
super(maxLength);
}
@Override
protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
ByteBuf frame = (ByteBuf) super.decode(ctx, buffer);// 从ByteBuf中提取由行尾符序列分隔的帧
if (frame == null) {
return null;// 如果输入中没有帧,则返回null
}
int index = frame.indexOf(frame.readerIndex(), // 查找第一个空格字符的索引。前面是命令名称,接着是参数
frame.writerIndex(), SPACE);
return new Cmd(frame.slice(frame.readerIndex(), index), // 使用包含有命令名称和参数的切片创建新的Cmd对象
frame.slice(index + 1, frame.writerIndex()));
}
}
public static final class CmdHandler extends SimpleChannelInboundHandler<Cmd> {
@Override
public void channelRead0(ChannelHandlerContext ctx, Cmd msg) throws Exception {
// Do something with the command 处理传经ChannelPipeline的Cmd对象
}
}
}
基于长度的协议
基于长度的协议通过将它的长度编码到帧的头部来定义帧,而不是使用特殊的分隔符来标记它的结束。下面列出了Netty提供的用于处理这种类型的协议的两种解码器。
用于基于长度的协议的解码器
FixedLengthFrameDecoder 提取在调用构造函数时指定的定长帧
LengthFieldBasedFrameDecoder 根据编码进帧头部中的长度值提取帧;该字段的偏移量以及长度在构造函数中指定
下图展示了FixedLengthFrameDecoder 的功能,其在构造时已经指定了帧长度为8字节。

你将经常会遇到被编码到消息头部的帧大小不是固定值的协议。为了处理这种变长帧,你可以使用LengthFieldBasedFrameDecoder,它将从头部字段确定帧长,然后从数据流中提取指定的字节数。
下图展示了将变长帧大小编码进头部的消息的示例,其中长度字段在帧中的偏移量为0,并且长度为2字节。

LengthFieldBasedFrameDecoder提供了几个构造函数来支持各种各样的头部配置情况。下面代码清单展示了如何使用其3个构造参数分别为maxFrameLength、lengthFieldOffset和lengthFieldLength的构造函数。在这个场景中,帧的长度被编码到了帧起始的前8个字节中。
使用LengthFieldBasedFrameDecoder 解码器基于长度的协议
public class LengthBasedInitializer extends ChannelInitializer<Channel> {
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new LengthFieldBasedFrameDecoder(64 * 1024, 0, 8));// 使用LengthFieldBasedFrameDecoder解码将帧长度编码到帧起始的前8个字节中的消息
pipeline.addLast(new FrameHandler());// 添加FrameHandler以处理每个帧
}
public static final class FrameHandler extends SimpleChannelInboundHandler<ByteBuf> {
@Override
public void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
// Do something with the frame 处理帧的数据
}
}
}
现在我们知道了Netty提供的,用于支持那些通过指定协议帧的分隔符或者长度(固定的或者可变的)以定义字节流的结构的协议的编解码器。你将会发现这些编解码器的许多用途,因为许多的常见协议都落到了这些分类之一中。
Netty中解码基于分隔符的协议和基于长度的协议的更多相关文章
- Netty入门(十)解码分隔符和基于长度的协议
我们需要区分不同帧的首尾,通常需要在结尾设定特定分隔符或者在首部添加长度字段,分别称为分隔符协议和基于长度的协议,本节讲解 Netty 如何解码这些协议. 一.分隔符协议 Netty 附带的解码器可以 ...
- Netty学习摘记 —— 心跳机制 / 基于分隔符和长度的协议
本文参考 本篇文章是对<Netty In Action>一书第十一章"预置的ChannelHandler和编解码器"的学习摘记,主要内容为通过 SSL/TLS 保护 N ...
- 手把手教你在netty中使用TCP协议请求DNS服务器
目录 简介 DNS传输协议简介 DNS的IP地址 Do53/TCP在netty中的使用 搭建DNS netty client 发送DNS查询消息 DNS查询的消息处理 总结 简介 DNS的全称doma ...
- netty系列之: 在netty中使用 tls 协议请求 DNS 服务器
目录 简介 支持DoT的DNS服务器 搭建支持DoT的netty客户端 TLS的客户端请求 总结 简介 在前面的文章中我们讲过了如何在netty中构造客户端分别使用tcp和udp协议向DNS服务器请求 ...
- netty系列之:在netty中使用protobuf协议
目录 简介 定义protobuf 定义handler 设置ChannelPipeline 构建client和server端并运行 总结 简介 netty中有很多适配不同协议的编码工具,对于流行的goo ...
- 基于大量图片与实例深度解析Netty中的核心组件
本篇文章主要详细分析Netty中的核心组件. 启动器Bootstrap和ServerBootstrap作为Netty构建客户端和服务端的路口,是编写Netty网络程序的第一步.它可以让我们把Netty ...
- netty系列之:在netty中使用native传输协议
目录 简介 native传输协议的依赖 netty本地传输协议的使用 总结 简介 对于IO来说,除了传统的block IO,使用最多的就是NIO了,通常我们在netty程序中最常用到的就是NIO,比如 ...
- Python中的端口协议之基于UDP协议的通信传输
UDP协议: 1.python中基于udp协议的客户端与服务端通信简单过程实现 2.udp协议的一些特点(与tcp协议的比较) 3.利用socketserver模块实现udp传输协议的并 ...
- 快来体验快速通道,netty中epoll传输协议详解
目录 简介 epoll的详细使用 EpollEventLoopGroup EpollEventLoop EpollServerSocketChannel EpollSocketChannel 总结 简 ...
随机推荐
- RHEL6从源码安装python及其他软件包
RHEL6从源码安装python及其他软件包 ## install ssl $ sudo yum install openssl-devel or: $ sudo apt-get install li ...
- LeetCode之“链表”:Intersection of Two Linked Lists
此题扩展:链表有环,如何判断相交? 参考资料: 编程判断两个链表是否相交 面试精选:链表问题集锦 题目链接 题目要求: Write a program to find the node at whic ...
- Linux常用命令(第二版) --权限管理命令
权限管理命令 1.chmod[change the permissions mode of a file] : /bin/chmod 语法: chmod [{ugo}{+-=}{rwx}] [文件或目 ...
- OAF 开发TAB页
TAB页 2013年1月17日 21:31 当查询结果列数比较多的时候,往往一页显示不下,在FORM的情况下,我们往往会用Tab页的方法解决.那么在OAF如何制作TAB页呢?下面的教程将介绍如何制作一 ...
- Linux 系统应用编程——线程基础
传统多任务操作系统中一个可以独立调度的任务(或称之为顺序执行流)是一个进程.每个程序加载到内存后只可以唯一地对应创建一个顺序执行流,即传统意义的进程.每个进程的全部系统资源是私有的,如虚拟地址空间,文 ...
- rails常用命令备忘
rails new xxx 创建一个新rails项目 rails generate scaffold xxx 创建表模型,视图,控制器和迁移的"脚手架" rake db:migra ...
- 如何让DIV中的文字垂直居中
var h = $("div").innerHeight(); $("#text").css("font-size", h); $(&quo ...
- Python3玩转儿 机器学习(5)
numpy 的使用 numpy.array基础 import numpy numpy.__version__ #查询当前numpy的版本 '1.14.0' import numpy as np np. ...
- 关于在vim中的查找和替换
1,查找 在normal模式下按下/即可进入查找模式,输入要查找的字符串并按下回车. Vim会跳转到第一个匹配.按下n查找下一个,按下N查找上一个. Vim查找支持正则表达式,例如/vim$匹配行尾的 ...
- Python新手入门学习常见错误
当初学 Python 时,想要弄懂 Python 的错误信息的含义可能有点复杂.这里列出了常见的的一些让你程序 crash 的运行时错误. 1)忘记在 if , elif , else , for , ...