个人随笔
目录
netty(四)、Netty编解码
2021-01-26 22:24:33

Netty涉及到编解码的组件有Channel、ChannelHandler、ChannelPipe等,先大概了解下这几个组件的作用。

ChannelHandler

ChannelHandler充当了处理入站和出站数据的应用程序逻辑容器。例如,实现ChannelInboundHandler接口(或
ChannelInboundHandlerAdapter),你就可以接收入站事件和数据,这些数据随后会被你的应用程序的业务逻辑处理。当你要给连接的客户端发送响应时,也可以从ChannelInboundHandler冲刷数据。你的业务逻辑通常写在一个或者多个ChannelInboundHandler中。ChannelOutboundHandler原理一样,只不过它是用来处理出站数据的。

ChannelPipeline

ChannelPipeline提供了ChannelHandler链的容器。以客户端应用程序为例,如果事件的运动方向是从客户端到服务端的,那么我们称这些事件为出站的,即客户端发送给服务端的数据会通过pipeline中的一系列ChannelOutboundHandler(ChannelOutboundHandler调用是从tail到head方向逐个调用每个handler的逻辑),并被这些Handler处理,反之则称为入站的,入站只调用pipeline里的ChannelInboundHandler逻辑(ChannelInboundHandler调用是从head到tail方向逐个调用每个handler的逻辑)。

如下图

从客户端的角度触发出站是从客户端的handler的tail到head,刚好去到服务端,然后从服务端的角度来看就是入站从head到tail。

编解码实例

1、字符串编解码

netty(三)、实现一个简单的在线聊天程序中我们如果不添加如下代码:

  1. sc.pipeline().addLast(new StringEncoder());//对 String 对象自动编码,属于出站处理器
  2. sc.pipeline().addLast(new StringDecoder());//把网络字节流自动解码为 String 对象,属于入站处理器

那么直接用

  1. String msg = scanner.nextLine();
  2. System.out.println("用户输入:"+msg);
  3. //通过channel发送到服务器端
  4. cf.channel().writeAndFlush(msg);

将不能发送消息,因为网络传输只能传输字节,但是加上字符串的编解码器就可以了,在发送消息的时候,内容会被StringEncoder编码为字节,在接收消息的时候,字节会被解码器StringDecoder解码为字符串。

为什么直接在pipeline中加上入站出站不会两个都经过呢?

因为我们的出站只会经过ChannelOutboundHandler,入站只会经过ChannelInboundHandler,而StringEncoder实现了ChannelOutboundHandler,StringDecoder实现了ChannelInboundHandler。所以不会两个同时经过。

2、实现自定义Long型编解码器

这里编码器继承MessageToByteEncoder,解码器继承ByteToMessageDecoder即可,代码如下:

编码器:

  1. public class MyLongEncoder extends MessageToByteEncoder<Long>{
  2. @Override
  3. protected void encode(ChannelHandlerContext ctx, Long msg, ByteBuf out) throws Exception {
  4. out.writeLong(msg);
  5. }
  6. }

解码器:

  1. public class MyLongDecoder extends ByteToMessageDecoder{
  2. @Override
  3. protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
  4. if(in.readableBytes()>=4) {
  5. out.add(in.readLong());
  6. }
  7. }
  8. }

在pipeline加上编解码器:

  1. sc.pipeline().addLast(new MyLongEncoder());
  2. sc.pipeline().addLast(new MyLongDecoder());

然后我们发送消息就可以执行如下语句:

  1. ctx.writeAndFlush(1000l);

亲测OK!

3、实现自定义对象编解码器

有时候我们想要发送对象,如

  1. Msg msg = new Msg();
  2. ctx.writeAndFlush(msg);

此时我们可以用netty默认实现的对象编解码器ObjectEncoder和ObjectDecoder也可以自己实现一个,这里是作为学习了解netty的编解码器,所以就自己实现啦。其实原理也很简单,无非就是实现一个编码器把把对象转为字节数组出站,然后实现一个解码器把字节数组转为对象入站罢了。

注:对象序列化一定要实现Serializable接口。

编码器:

  1. public class MyObjectEncoder extends MessageToByteEncoder<Object>{
  2. @Override
  3. protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
  4. System.out.println("对象转字节编码器开始执行");
  5. //将对象转化成字节
  6. ByteArrayOutputStream bops = new ByteArrayOutputStream();
  7. ObjectOutputStream oops = new ObjectOutputStream(bops);
  8. oops.writeObject(msg);
  9. byte[] bytes = bops.toByteArray();
  10. out.writeBytes(bytes);
  11. }
  12. }

解码器:

  1. public class MyObjectDecoder extends ByteToMessageDecoder{
  2. @Override
  3. protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
  4. System.out.println("字节转对象解码器开始执行");
  5. //将字节转成对象
  6. byte[] body = new byte[in.readableBytes()];
  7. in.readBytes(body);
  8. //如果用in.array()获得数组可能会报如下错误:java.lang.UnsupportedOperationException: direct buffer
  9. ByteArrayInputStream bis = new ByteArrayInputStream(body);
  10. ObjectInputStream ois = new ObjectInputStream(bis);
  11. Object obj = ois.readObject();
  12. out.add(obj);
  13. }
  14. }

如果用in.array()获得数组可能会报如下错误:java.lang.UnsupportedOperationException: direct buffer:

  1. hasArray() ——- 如果ByteBuf由一个字节数组支撑,则返回true。通俗的讲:ByteBuf是堆缓冲区模式,则代表其内部存储是由字节数组支撑的。如果还没理解,可参考5.2.2章节
  2. array() ——- 如果ByteBuf是由一个字节数组支撑泽返回数组,否则抛出UnsupportedOperationException异常。也就是,ByteBuf是堆缓冲区模式

在pipeline

  1. ...
  2. protected void initChannel(SocketChannel sc) throws Exception {
  3. sc.pipeline().addLast(new MyObjectEncoder());
  4. sc.pipeline().addLast(new MyObjectDecoder());
  5. sc.pipeline().addLast(new NettyServerHandler());
  6. }
  7. ...

然后就可以直接发对象啦

4、测试对象编解码器

对象

  1. public class Msg implements Serializable{
  2. private static final long serialVersionUID = 7530436885643753365L;
  3. private String username;
  4. private String toUsername;
  5. private String content;
  6. public String getUsername() {
  7. return username;
  8. }
  9. public void setUsername(String username) {
  10. this.username = username;
  11. }
  12. public String getToUsername() {
  13. return toUsername;
  14. }
  15. public void setToUsername(String toUsername) {
  16. this.toUsername = toUsername;
  17. }
  18. public String getContent() {
  19. return content;
  20. }
  21. public void setContent(String content) {
  22. this.content = content;
  23. }
  24. public Msg(String username, String toUsername, String content) {
  25. super();
  26. this.username = username;
  27. this.toUsername = toUsername;
  28. this.content = content;
  29. }
  30. @Override
  31. public String toString() {
  32. return "Msg [username=" + username + ", toUsername=" + toUsername + ", content=" + content + "]";
  33. }
  34. }

发消息

  1. Msg msg = new Msg("客户端1", "服务器", "你是狗");
  2. ctx.writeAndFlush(msg);

接收消息

  1. @Override
  2. public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
  3. System.out.println("接收到客户端的消息:"+msg);
  4. }

测试结果:可以正常收发打印出结果。

好了,这篇文章只是编解码器的入门,以后有机会再继续深入研究下。

 430

啊!这个可能是世界上最丑的留言输入框功能~


当然,也是最丑的留言列表

有疑问发邮件到 : suibibk@qq.com 侵权立删
Copyright : 个人随笔   备案号 : 粤ICP备18099399号-2