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之前 在响应发送 ...
随机推荐
- vue父子组件及非父子组件通信
1.父组件传递数据给子组件 父组件数据如何传递给子组件呢?可以通过props属性来实现 父组件: <parent> <child :child-msg="msg" ...
- [Swift]LeetCode873. 最长的斐波那契子序列的长度 | Length of Longest Fibonacci Subsequence
A sequence X_1, X_2, ..., X_n is fibonacci-like if: n >= 3 X_i + X_{i+1} = X_{i+2} for all i + 2 ...
- [Swift]LeetCode1005. K 次取反后最大化的数组和 | Maximize Sum Of Array After K Negations
Given an array A of integers, we must modify the array in the following way: we choose an i and repl ...
- 在 ns-3.25中添加 plc(电力线载波) 模块
上一篇安装好了 ns-3.25,这里继续往里添加 plc 模块.整理之前现推荐一个网站,大家可以参考. 英属哥伦比亚大学(University of British Columbia,UBC)提供NS ...
- Java 子类 instanceof 父类
1.结论: 子类 instanceof 父类 == true 父类 instanceof 子类 == false 2.测试代码 @Test public void testInstanceof() { ...
- C++ 断言
assert宏 (基本概念与用法整理) assert宏的深入学习 1.运行时断言 1.1.assert属于运行时断言,可以在运行时判断给定条件是否为真,如果为真则什么也不做,否则打印一跳错误信息,然后 ...
- Android--操作图片Exif信息
前言 在Android系统中,图片文件在内存中以像素点的二维数组加载,存放像素信息,还会在开头加上一些额外的照片拍摄参数信息,这些信息就是Exif.Android2.0之后,媒体库加入了操作图片Exi ...
- Chapter 4 Invitations——12
"I don't know what you mean," I said, my voice guarded. “我不知道你什么意思”我声音谨慎地说道. "It's be ...
- 冗余jar包识别神器 - loose.jar
冗余jar包识别神器 - loose.jar 场景描述 项目迭代久了,会添加各类jar包,事实上很多jar包其实根本没用到.那如何快速识别冗余的jar,以方便从项目中清除掉呢? 比如: 该简单的测试工 ...
- JDBC 连接池的两种方式——dbcp & c3p0
申明:本文对于连接资源关闭采用自定义的 JDBCUtils 工具: package com.test.utils; import java.sql.Connection; import java.sq ...