Netty学习笔记(三) 自定义编码器
编写一个网络应用程序需要实现某种编解码器,编解码器的作用就是讲原始字节数据与自定义的消息对象进行互转。网络中都是以字节码的数据形式来传输数据的,服务器编码数据后发送到客户端,客户端需要对数据进行解码,因为编解码器由两部分组成:
- Decoder(解码器)
- Encoder(编码器)
解码器负责处理“入站”数据,编码器负责处理“出站”数据。编码器和解码器的结构很简单,消息被编码后解码后会自动通过ReferenceCountUtil.release(message)释放。
需要补充说明的是,Netty中有两个方向的数据流
入站(ChannelInboundHandler):从远程主机到用户应用程序则是“入站(inbound)”
出站(ChannelOutboundHandler):从用户应用程序到远程主机则是“出站(outbound)”
今天我们主要学习编码器,也就是Encoder
实现逻辑
完成一个编码器的编写主要是实现一个抽象类MessageToMessageEncoder,其中我们需要重写方法是
/**
* Encode from one message to an other. This method will be called for each written message that can be handled
* by this encoder.
*
* @param ctx the {@link ChannelHandlerContext} which this {@link MessageToMessageEncoder} belongs to
* @param msg the message to encode to an other one
* @param out the {@link List} into which the encoded msg should be added
* needs to do some kind of aggragation
* @throws Exception is thrown if an error accour
*/
protected abstract void encode(ChannelHandlerContext ctx, I msg, List<Object> out) throws Exception;
其中泛型参数I表示我们需要接收的参数类型,如你需要将ByteBuf类型转换为Date类型,那么泛型I就是ByteBuf(事实上当实现ByteBuf编码为其他类型的时候是不需要使用MessageToMessageEncoder,Netty提供了ByteToMessageCodec,其本质也是实现了MessageToMessageEncoder)
代码编写
需求说明
客户端发过来一个数字(ByteBuf),我们将此类型转换为数字,获取当前时间加上此数字的时间后返回客户端,具体逻辑如下:
编码器的编写
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder;
import io.netty.util.CharsetUtil;
import java.time.LocalDateTime;
import java.util.List;
public class TimeEncoder extends MessageToMessageDecoder<ByteBuf> {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
//将ByteBuf转换为String,注意此处,我是Mac OS,数据结尾是\r\n,如果是其他类型的OS,此处可能需要调整
String dataStr = msg.toString(CharsetUtil.UTF_8).replace("\r\n","");
//将String转换为Integer
Integer dataInteger = Integer.valueOf(dataStr);
//获取当前时间N小时后的数据
LocalDateTime now = LocalDateTime.now();
LocalDateTime dataLocalDatetime = now.plusHours(dataInteger);
out.add(dataLocalDatetime);
}
}
服务端处理代码
此处的服务端HandleAdapter和前面两个章节的HandleAdapter有所区别的是:其继承了SimpleChannelInboundHandler<I> 并且传递了一个泛型参数,这里需要说明一下,SimpleChannelInboundHandler是ChannelInboundHandler一个子类,他能够自动帮我们处理一些数据,在ChannelInboundHandler中,我们使用channel方法来接收数据,那么在SimpleChannelInboundHandler中我们使用protected abstract void messageReceived(ChannelHandlerContext ctx, I msg) throws Exception;来接收客户端的参数,可以看到的是,其参数中自动的实现了我们需要处理的泛型I msg,另外看一下SimpleChannelInboundHandler中channelRead方法的实现代码
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
boolean release = true;
try {
//acceptInboundMessage() 检查参数的类型是否和设定的泛型是否匹配
//可以看到匹配的话,会进行强制类型转换并调用messageReceived方法
//否则的话,不执行,也就是说,这里的泛型一定要和编码器转换的结果类型一致,否则将接收不到参数
//当前如果你需要自己转换,那么你也可以和ChannelInBoundHandleAdapter一样,重写channelRead方法
if (acceptInboundMessage(msg)) {
@SuppressWarnings("unchecked")
I imsg = (I) msg;
messageReceived(ctx, imsg);
} else {
release = false;
ctx.fireChannelRead(msg);
}
} finally {
if (autoRelease && release) {
ReferenceCountUtil.release(msg);
}
}
}
那么继续实现我们的HandleAdapter,代码非常简单,这里不再赘述。需要注意的是,我们这里没有做解码器,也就是说入站的时候需要ByteBuf类型的数据,因此使用channel.writeAndFlush(Object)的时候,需要的就是ByteBuf类型的数据类型(当然如果pipeline中添加了StringDecoder解码器,那么你就可以直接使用字符串类型的数据了)
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import java.nio.charset.Charset;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class TimeServerChannelHandleAdapter extends SimpleChannelInboundHandler<LocalDateTime> {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("添加了新的连接信息:id = " + ctx.channel().id());
}
@Override
protected void messageReceived(ChannelHandlerContext ctx, LocalDateTime msg) throws Exception {
// 转换时间格式
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String dateFormat = msg.format(dateTimeFormatter);
System.out.println("接收到参数:" + dateFormat);
ctx.channel().writeAndFlush(Unpooled.copiedBuffer(dateFormat, Charset.defaultCharset()));
}
}
服务端启动代码
服务前启动代码和以前的代码非常类似,只需要在pipeline添加上适配的编码器即可,当然需要注意顺序(这个知识点以后我在仔细的阐述)
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class TimeServer {
public void start() throws Exception {
EventLoopGroup boosGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap
.group(boosGroup, workGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 128)
.childHandler(
new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//设置编码
pipeline.addLast(new TimeEncoder());
//设置服务处理
pipeline.addLast(new TimeServerChannelHandleAdapter());
}
});
ChannelFuture sync = bootstrap.bind(9998).sync();
System.out.println("Netty Server start with 9998 port");
sync.channel().closeFuture().sync();
} finally {
workGroup.shutdownGracefully();
boosGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
TimeServer server = new TimeServer();
server.start();
}
}
效果展示
这里为了不写太多的代码,防止造成知识的不理解,迷惑,这里我们使用telnet命令来测试数据,
启动服务器端
运行TimeServer代码的中的main方法即可
使用Telnet发送数据
telnet的命令格式是
usage: telnet [-l user] [-a] [-s src_addr] host-name [port]
可以看到大部分参数都是可选的,只有主机名称必填
发送数据的效果
继续在telnet中发送一个数据5,我们分别看下服务端的打印的数据和telnet接收到的数据
服务端打印的数据如下:
telnet端打印的接收到的数据
总结
至此,一个简单的编码器就完成,我们总结一下步骤
- 继承MessageToMessageDecoder抽象类,实现decode()方法
- 配置HandleAdapter 实现channelRead或者messageReceived方法
- 配置服务启动类,配置ChannelPipeline,添加编码器和HandleAdapter
- 编写客户端或者使用telnet或者其他手段测试
Netty学习笔记(三) 自定义编码器的更多相关文章
- Netty学习笔记(三)——netty源码剖析
1.Netty启动源码剖析 启动类: public class NettyNioServer { public static void main(String[] args) throws Excep ...
- Netty 学习(三):通信协议和编解码
Netty 学习(三):通信协议和编解码 作者: Grey 原文地址: 博客园:Netty 学习(三):通信协议和编解码 CSDN:Netty 学习(三):通信协议和编解码 无论使用 Netty 还是 ...
- [转载]SharePoint 2013搜索学习笔记之自定义结果源
搜索中心新建好之后在搜索结果页上会默认有所有内容,人员,对话,视频这四个结果分类,每个分类会返回指定范围的搜索结果,这里我再添加了部门日志结果分类,搜索这个分类只会返回部门日志内容类型的搜索结果,要实 ...
- [Firefly引擎][学习笔记三][已完结]所需模块封装
原地址:http://www.9miao.com/question-15-54671.html 学习笔记一传送门学习笔记二传送门 学习笔记三导读: 笔记三主要就是各个模块的封装了,这里贴 ...
- Hadoop学习笔记—5.自定义类型处理手机上网日志
转载自http://www.cnblogs.com/edisonchou/p/4288737.html Hadoop学习笔记—5.自定义类型处理手机上网日志 一.测试数据:手机上网日志 1.1 关于这 ...
- Netty学习笔记(二) 实现服务端和客户端
在Netty学习笔记(一) 实现DISCARD服务中,我们使用Netty和Python实现了简单的丢弃DISCARD服务,这篇,我们使用Netty实现服务端和客户端交互的需求. 前置工作 开发环境 J ...
- Netty 学习笔记(1)通信原理
前言 本文主要从 select 和 epoll 系统调用入手,来打开 Netty 的大门,从认识 Netty 的基础原理 —— I/O 多路复用模型开始. Netty 的通信原理 Netty 底层 ...
- angular学习笔记(三十)-指令(6)-transclude()方法(又称linker()方法)-模拟ng-repeat指令
在angular学习笔记(三十)-指令(4)-transclude文章的末尾提到了,如果在指令中需要反复使用被嵌套的那一坨,需要使用transclude()方法. 在angular学习笔记(三十)-指 ...
- SpringBoot学习笔记:自定义拦截器
SpringBoot学习笔记:自定义拦截器 快速开始 拦截器类似于过滤器,但是拦截器提供更精细的的控制能力,它可以在一个请求过程中的两个节点进行拦截: 在请求发送到Controller之前 在响应发送 ...
随机推荐
- Android端IM应用中的@人功能实现:仿微博、QQ、微信,零入侵、高可扩展
本文由“猫爸iYao”原创分享,感谢作者. 1.引言 最近有个需求:评论@人(没错,就是IM聊天或者微博APP里的@人功能),就像下图这样: ▲ 微信群聊界面里的@人功能 ▲ QQ群聊界面里 ...
- [Swift]LeetCode509. 斐波那契数 | Fibonacci Number
The Fibonacci numbers, commonly denoted F(n) form a sequence, called the Fibonacci sequence, such th ...
- [Swift]LeetCode692. 前K个高频单词 | Top K Frequent Words
Given a non-empty list of words, return the k most frequent elements. Your answer should be sorted b ...
- [Swift]LeetCode910. 最小差值 II | Smallest Range II
Given an array A of integers, for each integer A[i] we need to choose either x = -K or x = K, and ad ...
- [Swift]LeetCode971.翻转二叉树以匹配先序遍历 | Flip Binary Tree To Match Preorder Traversal
Given a binary tree with N nodes, each node has a different value from {1, ..., N}. A node in this b ...
- PHP常用设计模式讲解
开发中适当的使用设计模式,可以让项目有更易扩展,易维护.低耦合,代码简洁等 单例模式 <?php /** * 单例模式:使类在全局范围内只允许创建一个对象,常用于数据库连接等 */ class ...
- hadoop退出安全模式Name node is in safe mode
在使用 hdfs 的时候出现如下错误: 18/01/12 09:04:34 INFO fs.TrashPolicyDefault: Namenode trash configuration: Dele ...
- Calling handler.OnEndpointsUpdate报错原因
过程:部署node01节点时从一台已经部署好的node02把/opt/kubernetes目录直接拷贝了过来,然后修改相应配置文件,启动kubelet是日志报如下图错误: 导致node01无法连接上m ...
- InstallShield Limited Edition Project 打包windows服务解析
最近项目从vs2005 升级到vs2010后,发现新的vs2010 不再带有原来的安装工程项目,导致以前的安装包不可以使用,查找资料后发现微软从vs2010 版本后不再提供自带的安装工程,尝试着利用 ...
- 浅谈Web开发中的定时任务
曾经做过Windows server下的定时任务的业务,最近又做了一些Linux下使用Crontab做的定时任务的业务,觉得有必要进行一次小结,于是有了如下这篇文章. Windows Server下 ...