netty+tcp,传输数据使用protobuf,以及编解码分析

netty实现tcp见:netty4+tcp+springboot,server+client

下面只记录了数据传输使用protobuf。

1.ServerChannelInitializer、ClientChannelInitializer中使用netty提供的protubuf的编解码处理类:

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleStateHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * description: 通道初始化,主要用于设置各种Handler
 * author:
 * date: 2018-11-28 14:55
 **/
@Component
public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {

    @Autowired
    ServerChannelHandler serverChannelHandler;

    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();
        //IdleStateHandler心跳机制,如果超时触发Handle中userEventTrigger()方法
        pipeline.addLast("idleStateHandler",
                new IdleStateHandler(15, 0, 0, TimeUnit.MINUTES));
        //字符串编解码器
//        pipeline.addLast(
//                new StringDecoder(),
//                new StringEncoder()
//        );
        //请求的解码
        pipeline.addLast(new ProtobufVarint32FrameDecoder());
        //解码封装成的目标类
        pipeline.addLast(new ProtobufDecoder(ConfigMsgBuilder.ConfigMsg.getDefaultInstance()));//ChannelInboundHandlerAdapter
        //响应的编码
        pipeline.addLast(new ProtobufVarint32LengthFieldPrepender());
        pipeline.addLast(new ProtobufEncoder());//ChannelOutboundHandlerAdapter
        //自定义Handler
        pipeline.addLast("serverChannelHandler", serverChannelHandler);
    }
}

ClientChannelInitializer也是一样,核心就是使用protobuf的编解码类替换之前的字符串编解码类。

其中ConfigMsgBuilder.ConfigMsg是protobuf生成的java类,用来封装传输的数据。

2.ServerChannelHandler中响应数据使用protobuf生成的java传输类:

@Component
@ChannelHandler.Sharable
@Slf4j
public class ServerChannelHandler extends SimpleChannelInboundHandler<Object> {

    /**
     * 拿到传过来的msg数据,开始处理
     *
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("Netty tcp server receive msg : " + msg);
//        ctx.channel().writeAndFlush(" response msg ").syncUninterruptibly();
        ConfigMsgBuilder.ConfigMsg.Builder builder = ConfigMsgBuilder.ConfigMsg.newBuilder();
        builder.setMessageType(2);
        builder.setMessage("no change2");
        builder.setVersion(10002);
        ConfigMsgBuilder.ConfigMsg build = builder.build();
        ctx.channel().writeAndFlush(build).syncUninterruptibly();
    }
}

3.NettyTcpClient的请求数据也换为protobuf的格式:

import com.h3c.iot.app.engine.netty.tcp.client.NettyTcpClient;
import com.h3c.iot.app.engine.netty.tcp.server.NettyTcpServer;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;

/**
 * ClassName: SpringBootApplication
 * description:
 * author: 
 * date: 2018-09-30 09:15
 **/
@org.springframework.boot.autoconfigure.SpringBootApplication//@EnableAutoConfiguration @ComponentScan
public class SpringBootApplication implements CommandLineRunner {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootApplication.class, args);
    }


    @Autowired
    NettyTcpServer nettyTcpServer;
    @Autowired
    NettyTcpClient nettyTcpClient;

    @Override
    public void run(String... args) throws Exception {
        ChannelFuture start = nettyTcpServer.start();

        //启动客户端,发送数据
        nettyTcpClient.connect();
//        for (int i = 0; i < 10; i++) {
//            nettyTcpClient.sendMsg("hello world" + i);
//        }
        ConfigMsgBuilder.ConfigMsg.Builder builder = ConfigMsgBuilder.ConfigMsg.newBuilder();
        builder.setMessageType(1);
        builder.setMessage("no change");
        builder.setVersion(100);
        ConfigMsgBuilder.ConfigMsg build = builder.build();
        nettyTcpClient.sendMsg(build);


        start.channel().closeFuture().syncUninterruptibly();
    }
}

这样就实现了netty的tcp传输数据时使用protobuf的序列化数据协议。

重点补充:ProtobufVarint32FrameDecoder解码器、ProtobufVarint32LengthFieldPrepender编码器作用。

1。protobuf 消息格式举例:Length + Protobuf Data (消息头+消息数据)

Length表示Data的大小。为了更减少传输量,消息头采用的是varint 格式。

什么是varint?

Varint 是一种紧凑的表示数字的方法。它用一个或多个字节来表示一个数字,值越小的数字使用的字节数越少。
Varint 中的每个byte 的最高位 bit 有特殊的含义,如果该位为 1,表示下一个 byte 也是该数字的一部分,如果该位为 0,则结束。每个byte剩余的 7 位 bit 都用来表示数字。因此小于 128 的数字都可以用一个 byte 表示。大于 128 的数字,会用两个字节。

例如整数1的表示,仅需一个字节:

0000 0001

例如300的表示,需要两个字节:

1010 1100 0000 0010

采 用 Varint,对于很小的 int32 类型的数字,则可以用 1 个 byte 来表示。当然凡事有好的也有不好的一面,采用 Varint 表示法,大的数字则需要 5 个 byte 来表示。多数情况下,Length是比较小的,所以,采用 Varint 后,可以用更少的字节数来表示数字信息。

2。server端处理数据流程:

接收到protobuf数据 --> ProtobufVarint32FrameDecoder 消息头的解码(处理半包) --> ProtobufDecoder 消息数据体的解码 --> 自定义ChannelInboundHandler 即业务逻辑的处理 --> 自定义ChannelOutboundHandler 即输出数据的处理 --> ProtobufVarint32LengthFieldPrepender 消息头的编码,即加长度 --> ProtobufEncoder 消息数据体的编码 --> 响应protubuf数据

以上就是我个人理解的netty的server端处理protobuf的流程,client端同理。解释如下:

2.1 ProtobufVarint32LengthFieldPrepender 是对protobuf协议的的消息头上加上一个长度为32的整形字段,用于标志这个消息的长度,即Length。

2.2 ProtobufVarint32FrameDecoder 是针对protobuf协议的ProtobufVarint32LengthFieldPrepender 所加长度属性的解码器。

2.3 ProtobufDecoder 是将消息数据体解码成对应的的java类。

2.4 ProtobufEncoder 是将java类编码成protobuf的消息数据体,它是一个ChannelOutboundHandler,会自动将编码后数据加入到out中,响应给client。

网上解释记录:

    ProtobufEncoder:非常简单,内部直接使用了message.toByteArray()将字节数据放入bytebuf中输出(out中,交由下一个encoder处理)。

    ProtobufVarint32LengthFieldPrepender:因为ProtobufEncoder只是将message的各个filed按照规则输出了,并没有serializedSize,所以socket无法判定package(封包)。这个Encoder的作用就是在ProtobufEncoder生成的字节数组前,prepender(前置)一个varint32数字,表示serializedSize。

    ProtobufVarint32FrameDecoder:这个decoder和Prepender做的工作正好对应,作用就是“成帧”,根据seriaziedSize读取足额的字节数组--一个完整的package。

    ProtobufDecoder:和ProtobufEncoder对应,这个Decoder需要指定一个默认的instance,decoder将会解析byteArray,并根据format规则为此instance中的各个filed赋值。

网上解释个人总结:ProtobufVarint32LengthFieldPrepender 让socket知道packet大小,从而进行封包;

                                    ProtobufVarint32FrameDecoder 根据seriaziedSize读取足额的字节数组,得到一个完整的package。

参考博客:

理解netty对protocol buffers的编码解码

Protobuf与JAVA

猜你喜欢

转载自blog.csdn.net/yzh_1346983557/article/details/85992969
今日推荐