netty实现多协议,多数据结构的最终解决方案

前言

前边的几篇文章一直在介绍各种通信协议和动态编解码,但是解决的都不是很完美,但是通过前几篇文章,相信还是有提升的,这里给出一个较完善的方案:如果你的项目中是多客户端,多服务端,多数据格式,多对象,如果你没有更好的办法来融合他们,达到最佳的传输效率,可以参考一下本文。

实现场景:
java项目用netty实现一个客户端,一个服务端,客户端和服务端通信的数据是发送实体类对象,同时这个服务端还要被c语言项目的客户端连接,通信的数据用的是json字符串方式。

在这里插入图片描述

1、netty之间的通信传输数据,为了能够更好的编解码,快速识别对象的类型(比如有user、dept、dict等等实体类),我们采用ObjectEncoder和ObjectDecoder编解码器,这套编解码器是netty提供的,直接拿来用;
2、 netty服务端与c++的客户端通信,需求原因,只能采用json字符串的方式,也就是将对象转成json字符串来进行传输。

在实现之前,我们先来实际的选择一下编解码器。

编解码器的选择

对象传输的编解码器

首先,netty之间的对象传输,我们选择了Object编解码器,原因是它能帮我们将字节流直接解析成对应数据类型,比如:user对象,而不需要我们去解析字节流,然后通过数据里的内容来判断属于什么pojo对象,具体这套编解码器怎么用,请参考这篇文章:
netty实现pojo对象的编解码(ObjectDecoder、ObjectEncoder详解)

字符串的编解码器

为了防止拆包、粘包,首先我们来选择一套编解码器来解决,这里我们采用的是字符串分隔符的方式来解决,那么解码器我们可以选择netty提供的DelimiterBasedFrameDecoder解码器,编码器的话netty没有提供,那就自定义一个编码器即可。
自定义编码器:DelimiterBasedFrameEncoder

public class DelimiterBasedFrameEncoder extends MessageToByteEncoder<String> {


    private String delimiter;

    private Charset charset;


    public DelimiterBasedFrameEncoder(String delimiter,Charset charset) {
        this.delimiter = delimiter;
        this.charset = charset;
    }

    @Override
    protected void encode(ChannelHandlerContext ctx, String msg, ByteBuf out) {
        out.writeBytes(ByteBufUtil.encodeString(ctx.alloc(), CharBuffer.wrap(msg+delimiter), charset));
    }
}

这里自定义的编码器,直接融合了StringEecoder的功能,这样咱们在编码的时候就可以少走一个出栈handler处理器了。

拆包、粘包的编码器选择完之后,继续选择字符串的编解码器,这里我们直接用netty提供的StringDecoderStringEecoder,因为上边将StringEecoder的功能融合进自定义的DelimiterBasedFrameEncoder编码器中了,就省略了一个,稍微看一下源码,很简单的:
在这里插入图片描述
在这里插入图片描述
好啦,编解码器我们准备好啦,下边就来实战,注意啦!!!可不是你想的那么简单的。。。

实战融合

为了减少篇幅,这里我都截图或贴上关键代码。

netty客户端

这个很简单,没什么可说的,把两个Object编解码器写上即可
在这里插入图片描述

netty服务端

编解码器详情

重点在这里,一定要将各个编解码器排好,否则无法达到预想效果,至于编解码器的执行顺序和条件问题,不明白的,请参考:netty实现多协议,多编解码器
现在要用到这么多的编解码器:
在这里插入图片描述

编解码器详情:

  • ObjectEncoder

    在这里插入图片描述

    通过泛型可知,只要是实现了Serializable接口的消息都优先走这个编码器,对消息编码之后,通过继承了MessageToByteEncoder类可知,会继续将消息转换成bytebuf形式,然后继续传递。

  • ObjectDecoder

    在这里插入图片描述
    在这里插入图片描述

    通过上边的两个图可知,ObjectDecoder解码器会将bytebuf消息转换成Object,这里有一个技巧:
    比如这个:ByteToMessageDecoder,只看前部分ByteToMessage;
    Byte:代表此handler接收的消息类型为bytbuf;
    Message:代表经过处理之后,消息被处理成的类型为Object,当然这个message具体是啥类型,是根据你指定的泛型决定的,,比如ByteToMessage<User>,就代表message类型为User,如果不指定泛型就是Object类型。

  • DelimiterBasedFrameDecoder

    在这里插入图片描述

    优先接收bytebuf信息,转换成Object消息

  • DelimiterBasedFrameEncoder

    在这里插入图片描述

    优先接收String消息,转换成bytebuf类型

  • StringDecoder

    在这里插入图片描述

    优先接收ByteBuf消息,转换成Object消息

  • LightsHandler

    在这里插入图片描述

    这是最终用的消息处理器,优先接收实现Serializable接口的消息

  • RsuHandler

    在这里插入图片描述

    这是最终用的另一个消息处理器,优先接收String类型的消息

编解码器排序

编解码器以及逻辑处理hander下边我直接排序好,然后针对性一个个讲。
在这里插入图片描述

为什么这么排,下边一一讲解,这个顺序是试验了很多次才弄好的,照这个做,绝对没错!!!

c++发送信息,netty接收信息

在这里插入图片描述

细心的朋友可能会发现ObjectDecoder变成了MyObjectDecoder,原因是ObjectDecoder解码器排在最上边,消息只要进来就会被它拦截,但是进来的是Sting字符串消息,肯定解析会失败或者报错,但是失败之后ObjectDecoder并不会将消息继续往下传递给其它能解析它的解码器,又由于是netty提供的解码器,无法修改源码,所以只能自己写一个类继承这个类,然后重写解码方法了。
MyObjectDecoder

/**
 * @author: zhouwenjie
 * @description:
 * @create: 2022-07-21 11:19
 **/
public class MyObjectDecoder extends ObjectDecoder {
    
    public MyObjectDecoder(ClassResolver classResolver) {
        super(classResolver);
    }

    @Override
    protected Object decode(ChannelHandlerContext ctx, ByteBuf in) {
        try {
            Object o = super.decode(ctx, in);
            if (o == null) {
                in.resetReaderIndex();
                ByteBuf byteBuf = in.retainedDuplicate();
                in.skipBytes(in.readableBytes());
                return byteBuf;
            }else {
                return o;
            }
        } catch (Exception e) {
            in.resetReaderIndex();
            ByteBuf byteBuf = in.retainedDuplicate();
            in.skipBytes(in.readableBytes());
            return byteBuf;
        }
    }
}

这段代码就是让消息继续传递下去,不明白的可以看本专栏其他文章,里边有介绍到

netty服务端发消息,c++ socker客户端接收消息

在这里插入图片描述
这里可以看到字符串消息从RsuHandler发送出来,经过DelimiterBasedFrameEncoder编码,然后直接发出去了,为什么不继续往上走将消息传递给ObjectEncoder编码器呢?因为ObjectEncoder只拦截实现了Serializable接口的消息,然而,经过DelimiterBasedFrameEncoder编码的字符串消息变成了ByteBuf类型,所以ObjectEncoder不拦截,直接出栈,传递到c++客户端了。

提醒一下,如果消息是在LightsHandler中发出去的,那么一定要用ctx.pipeline().writeAndFlush,不然无法被DelimiterBasedFrameEncoder编码,因为ctx.writeAndFlush只会从当前位置往上找编码器,ctx.pipeline().writeAndFlush会从最下方开始找编码器。

netty客户端发消息,netty服务端收消息

在这里插入图片描述
客户端发送对象消息过来,被MyObjectDecoder接收并解析,然后因为消息是实现了Serializable接口的,所以优先被LightsHandler拦截,最终处理完成,不再继续传递。

netty服务端发消息,netty客户端收消息

在这里插入图片描述
不用再解释了吧!!!
这里可能有人有疑问,如果在RsuHandler发送对象信息,那么消息岂不是会被DelimiterBasedFrameEncoder编码加上分隔符?那不会,因为DelimiterBasedFrameEncoder的泛型是String,不会拦截对象消息,另外,对象消息实现了Serializable接口,优先会被ObjectEncoder拦截。

Ok,结束……

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值