当前位置: 首页 > 后端技术 > Java

netty系列:使用POJO代替buf

时间:2023-04-01 16:38:46 Java

简介我们在上一篇文章中提到,对于NioSocketChannel来说,它接收的不是最基本的字符串消息,而只是接收ByteBuf和FileRegion。但是ByteBuf是以二进制形式处理的,对于程序员来说太不直观了,处理起来也比较麻烦。是否可以直接处理java简单对象?本文将探讨这个问题。例如,对于解码和编码,我们需要将字符串直接写入通道。在上一篇文章中,我们知道这是不可能的,会报如下错误:DefaultChannelPromise@57f5c075(failure:java.lang.UnsupportedOperationException:unsupportedmessagetype:String(expected:ByteBuf,FileRegion))表示ChannelPromiseonly接受ByteBuf和FileRegion,那怎么办呢?由于ChannelPromise只接受ByteBuf和FileRegion,所以我们需要将String对象转换为ByteBuf。也就是说写入String前先将String转成ByteBuf,读数据时再将ByteBuf转成String。我们知道可以在ChannelPipeline中添加多个handler,并且可以控制这些handler的顺序。然后我们的想法就出来了。在ChannelPipeline中添加一个encode,用于将数据编码到ByteBuf中进行数据写入,然后在写入数据时添加一个decode,将数据解码成对应的。目的。encode和decode是不是很熟悉?对了,这就是对象的序列化。对象序列化netty中的对象序列化是直接将传输的对象和ByteBuf相互转换。当然,我们可以自己实现这个转换对象。但是netty为我们提供了两个方便的转换类:ObjectEncoder和ObjectDecoder。先看ObjectEncoder,它的作用是将对象转成ByteBuf。这个类很简单,我们来分析一下:publicclassObjectEncoderextendsMessageToByteEncoder{privatestaticfinalbyte[]LENGTH_PLACEHOLDER=newbyte[4];@Overrideprotectedvoidencode(ChannelHandlerContextctx,Serializablemsg,ByteBufout)throwsException{intstartIdx=out.writerIndex();ByteBufOutputStreambout=newByteBufOutputStream(out);ObjectOutputStreamoout=null;尝试{bout.write(LENGTH_PLACEHOLDER);oout=newCompactObjectOutputStream(bout);oout.writeObject(msg).flush();}finally{if(oout!=null){oout.close();}else{bout.close();}}intendIdx=out.writerIndex();out.setInt(startIdx,endIdx-startIdx-4);}}ObjectEncoder继承MessageToByteEncoder,MessageToByteEncoder继承ChannelOutboundHandlerAdapter。为什么出境?这是因为我们要转换写入的对象,所以是outbound。首先,使用ByteBufOutputStream封装outByteBuf。在bout中,首先写一个LENGTH_PLACEHOLDER字段来表示流中Byte的长度。然后用一个CompactObjectOutputStream来封装bout,最后就可以用CompactObjectOutputStream来写对象了。相应的,netty也有一个ObjectDecoder对象,用来将ByteBuf转换成对应的对象。ObjectDecoder继承自LengthFieldBasedFrameDecoder。其实就是一个ByteToMessageDecoder和一个ChannelInboundHandlerAdapter,用来处理数据读取。我们来看一下ObjectDecoder中最重要的decode方法:如果(框架==空){返回空;}ObjectInputStreamois=newCompactObjectInputStream(newByteBufInputStream(frame,true),classResolver);尝试{返回ois.readObject();}最后{ois.close();从上面的代码可以看出,输入的ByteBuf经过ByteBufInputStream的转换,最后转换为CompactObjectInputStream,可以直接读取对象。使用编解码器和解码器上面两种编解码器,需要直接添加到客户端和服务端的ChannelPipeline中。对于服务端,核心代码如下://定义bossGroup和workerGroupEventLoopGroupbossGroup=newNioEventLoopGroup(1);EventLoopGroupworkerGroup=newNioEventLoopGroup();尝试{ServerBootstrapb=newServerBootstrap();b.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class).handler(newLoggingHandler(LogLevel.INFO)).childHandler(newChannelInitializer(){@OverridepublicvoidinitChannel(SocketChannelch)抛出异常{ChannelPipelinep=ch.pipeline();p.addLast(//添加编码器和解码器newObjectEncoder(),newObjectDecoder(ClassResolvers.cacheDisabled(null)),newPojoServerHandler());}});//绑定端口并准备接受连接b.bind(PORT).sync().channel().closeFuture().sync();同样,对于客户端,我们的核心代码如下:EventLoopGroupgroup=newNioEventLoopGroup();试试{Bootstrapb=newBootstrap();b.group(group).channel(NioSocketChannel.class).handler(newChannelInitializer(){@OverridepublicvoidinitChannel(SocketChannelch)throwsException{ChannelPipelinep=ch.pipeline();p.addLast(//添加编码器和解码器newObjectEncoder(),newObjectDecoder(ClassResolvers.cacheDisabled(null)),newPojoClientHandler());}});//建立连接b.connect(HOST,PORT).sync().channel().closeFuture().sync();可以看到上面的逻辑是在ChannelPipeline中添加了ObjectEncoder和ObjectDecoder。最后,你可以调用:ctx.write("Comeon!");直接写在客户端和浏览器字符串对象上。总结有了ObjectEncoder和ObjectDecoder,我们就不再局限于ByteBuf,程序的灵活性得到了很大的提高。本文示例可参考:learn-netty4本文已收录于http://www.flydean.com/08-netty-pojo-buf/最通俗的解读,最深刻的干货,最简洁的教程,很多你不知道的小技巧等你来发现!欢迎关注我的公众号:《程序那些事儿》,懂技术,更懂你!

最新推荐
猜你喜欢