什么是编解码处理器?
编码处理器是将消息转换为适合于传输的格式网络字节流;而对应的解码处理器则是将网络字节流转换回应用程序的消息格式。
比如LengthFieldPrepender就是一个编码器,该编码器会给消息加上一个长度域。从继承关系,可以看出编码器实际上是一种出站处理器。
而LengthFieldBasedFrameDecoder就是一个解码处理器,参数配置正确,它就会解掉长度域,得到我们真正想要的数据。从继承关系,可以看出解码处理器实际上是一种入站处理器。
解码处理器介绍:
1、将字节解码为消息ByteToMessageDecoder
将字节解码为消息(或者另一个字节序列)是一项如此常见的任务,以至于Netty 为它提供了一个抽象的基类:ByteToMessageDecoder。由于你不可能知道远程节点是否会一次性地发送一个完整的消息,所以这个类会对入站数据进行缓冲。
decode()方法:
这是你必须实现的唯一抽象方法。decode()方法被调用时将会传入一个包含了传入数据的ByteBuf,以及一个用来添加解码消息的List。对这个方法的调用将会重复进行,直到确定没有新的元素被添加到该List,或者该ByteBuf 中没有更多可读取的字节时为止。然后,如果该List 不为空,那么它的内容将会被传递给ChannelPipeline 中的下一个ChannelInboundHandler。
比如DelimiterBasedFrameDecoder解码处理器,我们发送消息的时候会加上一个分隔符,该解码处理器就会根据分割符把数据切分成多个ByteBuf添加到out中。
然后会循环遍历out,调用ctx.fireChannelRead(msgs.getUnsafe(i)); 传递给下一个ChannelInboundHandlerAdapter 入站处理器。
2、将一种消息类型解码为另一种——MessageToMessageDecoder<I>,I代表源数据的类型。
在两个消息格式之间进行转换(例如,从Integer->String)
public class IntToStringDecoder extends MessageToMessageDecoder<Integer> {
@Override
protected void decode(ChannelHandlerContext ctx, Integer msg, List<Object> out) throws Exception {
out.add(String.valueOf(msg));//转为String 并将它添加到输出的list中
}
}
decode方法:对于每个需要被解码为另一种格式的入站消息来说,该方法都将会被调用。解码消息随后会被传递给ChannelPipeline中的下一个ChannelInboundHandler。
编码处理器介绍:
1、将消息编码为字节:只需要继承MessageToByteEncoder,然后实现encode()方法。
public abstract class MessageToByteEncoder<I>
encode()方法是你需要实现的唯一抽象方法,在该方法里将消息写入out中。该ByteBuf 随后将会被转发给 ChannelPipeline中的下一个 ChannelOutboundHandler。
举个例子:spring转为网络字节数据
public class StringToByteEncoder extends MessageToByteEncoder<String>{
@Override
protected void encode(ChannelHandlerContext ctx, String msg, ByteBuf out) throws Exception {
out.writeBytes(msg.getBytes(CharsetUtil.UTF_8));
}
}
2、将消息编码为消息:只需要继承MessageToMessageEncoder,然后实现encode()方法。
public abstract class MessageToMessageEncoder<I> extends ChannelOutboundHandlerAdapter {
实现该方法可以将一种格式的消息转为另一种,然后添加到out中。随后,会被转发给 ChannelPipeline中的下一个 ChannelOutboundHandler
int 转为 string
public class IntToStringEncoder extends MessageToMessageEncoder<Integer> {
@Override
protected void encode(ChannelHandlerContext ctx, Integer msg, List<Object> out) throws Exception {
out.add(String.valueOf(msg));
}
}
实战:
发送消息(消息类型为User对象)----》将User对象转为Json格式的字节数组----》发送到网络上
接收数据-----》将字节数组转为User对象。
编解码处理器:
JsonDecoder 该类将Bytebuf缓存区里的字节数据转为json字符串,然后将json对象转为User对象,最后放到输出list中,转到下一个
ChannelInboundHandlerAdapter。
public class JsonDecoder extends MessageToMessageDecoder<ByteBuf>{
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception {
String msgStr = in.toString(CharsetUtil.UTF_8);
System.out.println("JsonDecoder:"+msgStr);
//将json字符串转为对象
User user = JSON.parseObject(msgStr,new TypeReference<User>(){});
//将对象放到list中
out.add(user);
}
}
JsonEncoder 编码器将User对象转为Json字符串,然后将字符串转为网络字节数据发送到网络上。
public class JsonEncoder extends MessageToByteEncoder<User> {
@Override
protected void encode(ChannelHandlerContext ctx, User msg, ByteBuf out) throws Exception {
System.out.println("JsonEncoder:"+msg);
String userJson = JSON.toJSONString(msg);
//将消息写入ByteBuf中
out.writeBytes(userJson.getBytes(CharsetUtil.UTF_8));
}
}
服务端启动类:
服务端添加了LengthFieldBasedFrameDecoder和LengthFieldPrepender来解决粘包和半包问题。客户端发送过来的消息首先经过LengthFieldBasedFrameDecoder解掉长度域,然后经由JsonDecoder将ByteBuf里的字节数据转为User对象。最后经由CustomEchoServerInHandler进行处理。
public class CustomEchoServer {
private int port;
private EventLoopGroup bossGroup;
private EventLoopGroup workGroup;
private ServerBootstrap b;
public CustomEchoServer(int port) {
this.port = port;
//第一个线程组是用于接收Client连接的
bossGroup = new NioEventLoopGroup(1);
//第二个线程组是用于消息的读写操作
workGroup = new NioEventLoopGroup(2);
//服务端辅助启动类
b = new ServerBootstrap();
b.group(bossGroup, workGroup)
//需要指定使用NioServerSocketChannel这种类型的通道
.channel(NioServerSocketChannel.class)
//向ChannelPipeline里添加业务处理handler
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(65535,
0,2,0,2));
//给发送出去的消息增加长度字段 2个字节
ch.pipeline().addLast(new LengthFieldPrepender(2));
ch.pipeline().addLast(new JsonDecoder());//自定义的json解码处理器
ch.pipeline().addLast(new JsonEncoder());//自定义的json编码处理器
ch.pipeline().addLast(new CustomEchoServerInHandler());
}
});
}
/**
* 启动
* @throws InterruptedException
*/
public void start() throws InterruptedException {
try {
//绑定到端口,阻塞等待直到完成
b.bind(this.port).sync();
System.out.println("服务器启动成功");
} finally {
}
}
/**
* 资源优雅释放
*/
public void close() {
try {
if (bossGroup != null)
bossGroup.shutdownGracefully().sync();
if (workGroup != null)
workGroup.shutdownGracefully().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException, IOException {
int port = 9999;
CustomEchoServer echoServer = new CustomEchoServer(port);
try {
echoServer.start();
//防止主程序退出
System.in.read();
} finally {
echoServer.close();
}
}
}
服务端处理类:可以看到服务端的处理类直接接受user对象。然后给该对象设置好name和age,在写会客户端。消息会经由JsonEncoder进行编码,将对象转为json字符串,然后将字符串转为网络字节数据。最后经由LengthFieldPrepender加上数据的长度域。
public class CustomEchoServerInHandler extends SimpleChannelInboundHandler<User> {
/**
* 服务端读取到网络数据后的处理
* @param ctx
* @param user
* @throws Exception
*/
@Override
public void channelRead0(ChannelHandlerContext ctx, User user)
throws Exception {
System.out.println("CustomEchoServerInHandler" + user);
user.setName("hello");
user.setAge(18);
ctx.writeAndFlush(user);
}
/**
* exceptionCaught()事件处理方法当出现Throwable对象才会被调用
* 即当Netty由于IO错误或者处理器在处理事件时抛出的异常时
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
cause.printStackTrace();
ctx.close();
}
}
客户端的代码
public class CustomEchoClient {
private final int port;
private final String host;
private Channel channel;
private EventLoopGroup group;
private Bootstrap b;
public CustomEchoClient(String host, int port) {
this.host = host;
this.port = port;
//客户端启动辅助类
b = new Bootstrap();
//构建线程组 处理读写
group = new NioEventLoopGroup();
b.group(group)
//指明使用NIO进行网络通讯
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(65535,
0,2,0,2));
/*给发送出去的消息增加长度字段*/
ch.pipeline().addLast(new LengthFieldPrepender(2));
ch.pipeline().addLast(new JsonDecoder());//自定义的json解码处理器
ch.pipeline().addLast(new JsonEncoder());//自定义的json编码处理器
ch.pipeline().addLast(new CustomEchoClientInHandler());
}
});
}
public void start() {
try {
//连接到远程节点,阻塞等待直到连接完成
ChannelFuture f = b.connect(new InetSocketAddress(host, port)).sync();
//同步获取channel(通道,实际上是对socket的封装支持读取和写入)
channel = f.sync().channel();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 发送消息
* @param user
* @return
*/
public boolean send(User user) {
channel.writeAndFlush(user);
return true;
}
/**
* 关闭释放资源
*/
public void close(){
try {
if (group != null)
group.shutdownGracefully().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException, IOException {
CustomEchoClient client = new CustomEchoClient("127.0.0.1", 9999);
try {
client.start();
for (int i = 0; i < 1; i++){
User user = new User();
user.setId(i);
client.send(user);
}
//防止程序退出
System.in.read();
}finally {
client.close();
}
}
}
public class CustomEchoClientInHandler extends SimpleChannelInboundHandler<User> {
/**
* 客户端读取到数据
* @param ctx
* @param user
* @throws Exception
*/
@Override
public void channelRead0(ChannelHandlerContext ctx, User user)
throws Exception {
System.out.println("CustomEchoClientInHandler" + user);
}
/**
* exceptionCaught()事件处理方法当出现Throwable对象才会被调用
* 即当Netty由于IO错误或者处理器在处理事件时抛出的异常时
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
cause.printStackTrace();
ctx.close();
}
}
启动服务端和客户端并发送消息,可以看到服务端收到了消息,客户端也收到了服务端返回的消息