《Netty學習打卡--從小白到放棄》----- 10 - netty 之protobuf單消息協議傳遞

打卡日期(2019-07-10)

學習要點

-   1.ProtobufVarint32FrameDecoder
-   2.ProtobufDecoder
-   3.ProtobufVarint32LenthFieldPrepender

1.ProtobufVarint32FrameDecoder

    用於decode(解碼)前解決半包和粘包問題(利用包頭中包含數組長度來識別半包沾包)

什麼是粘包

    首先得了解一下TPC/IP的協議,在用戶數據量非常小的情況下,極端情況下,一個字節,該TCP數據包的有效載荷非常低,傳遞100自己數據,需要循環100次TCP傳送,100次ACK確認,在應用及時性不高的情況下,將這100個有效的數據拼接成一個數據包,這樣就會壓縮成一個TCP數據包,以及一個ack確認,有效載荷也提高了,帶寬也省下來了。
    非常極端的情況下,有可能兩個數據包拼接成一個數據包,也有可能是一個半數據包拼接成一個數據包,也有可能是兩個半數據包拼接成一個數據包。這個多個數據包拼接成一個數據包的過程就叫做粘包。

什麼是拆包

    拆包和粘包是相對的。一端粘了包另一端就需要去拆包。例如:客戶端發送的3個數據包粘成了2個數據包,服務端接收到數據之後就需要把這2個粘好的包拆分成3個單獨的數據包進行處理。

拆包的原理

    數據包每次讀取完都需要判斷是否是一個完整的數據包

  • 1.如果當前讀取的數據不足以拼接成一個完整的業務數據包,那就保留改數據,繼續從tcp緩衝區中獲取數據值,直到得到一個完整的數據包
  • 2.如果當前讀到的數據加上已經讀取的數據足夠拼接成一個完整的數據包,那就將已經讀到的數據拼接上次讀取的數據,組成一個完整的數據包,多餘的任然保留,以便和下次讀取的數據進行拼接

2.ProtobufDecoder

    ProtobufDecoder(MessageLite),這個解析器實際上就是告訴ProtobufDecoder要處理的目標類是什麼,否則僅僅從字節數組中是無法判斷出要解析的目標類型信息

3.ProtobufVarint32LengthFieldPrepender

    netty 爲protobuf提供的一個編碼器

netty 利用protobuf單協議消息支持

    《Netty學習打卡–從小白到放棄》----- 09 - netty 之protobuf 潛入protobuf 簡單的案例
    已經寫好了Student.proto,並且利用protoc生成了Person類,接下來利用這個類來實現protobuf但協議消息支持

服務器端(Server)

package com.dragon.protobuf.server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

public class MyProtobufServer {
    public static void main(String[] args) throws Exception{
        EventLoopGroup acceptorGroup = new NioEventLoopGroup();
        EventLoopGroup handlerGroup = new NioEventLoopGroup();
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        try{
            serverBootstrap.group(acceptorGroup,handlerGroup)
                    .channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .childHandler(new MyProtobufServerInit());
            ChannelFuture future = serverBootstrap.bind(8080).sync();
            future.channel().closeFuture().sync();
        }finally {
            acceptorGroup.shutdownGracefully();
            handlerGroup.shutdownGracefully();
        }
    }
}
package com.dragon.protobuf.server;

import com.dragon.protobuf.Person;
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;
public class MyProtobufServerInit extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        //用於decode(解碼)前解決半包和粘包問題(利用包頭中包含數組長度來識別半包沾包)
        pipeline.addLast(new ProtobufVarint32FrameDecoder());
        //這個解析器實際上就是告訴ProtobufDecoder要處理的目標類是什麼,否則僅僅從字節數組中是無法判斷出要解析的目標類型信息
        pipeline.addLast(new ProtobufDecoder(Person.Student.getDefaultInstance()));
        //protobuf編碼器 對protobuf協議的的消息頭上加上一個長度爲32的整形字段,用於標誌這個消息的長度
        pipeline.addLast(new ProtobufVarint32LengthFieldPrepender());
        //protobuf編碼器
        pipeline.addLast(new ProtobufEncoder());
        //自定義處理器
        pipeline.addLast(new MyProtobufServerHandler());
    }
}
package com.dragon.protobuf.server;
import com.dragon.protobuf.Person;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

public class MyProtobufServerHandler extends SimpleChannelInboundHandler<Person.Student> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Person.Student msg) throws Exception {
        System.out.println(msg.getAge());
        System.out.println(msg.getName());
        System.out.println(msg.getAddress());
    }
}

客戶端(Client)

package com.dragon.protobuf.client;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;

public class MyProtobufClient {
    public static void main(String[] args) throws InterruptedException {

        Bootstrap bootstrap = new Bootstrap();
        EventLoopGroup clientGroup = new NioEventLoopGroup();
        try{
            bootstrap.group(clientGroup).
                    channel(NioSocketChannel.class).
                    handler(new MyProtobufClientInit());
            ChannelFuture channelFuture = bootstrap.connect("localhost",8080).sync();
            channelFuture.channel().closeFuture().sync();
        }finally {
            clientGroup.shutdownGracefully();
        }
    }
}
package com.dragon.protobuf.client;

import com.dragon.protobuf.Person;
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;

public class MyProtobufClientInit extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        //用於decode(解碼)前解決半包和粘包問題(利用包頭中包含數組長度來識別半包沾包)
        pipeline.addLast(new ProtobufVarint32FrameDecoder());
        //這個解析器實際上就是告訴ProtobufDecoder要處理的目標類是什麼,否則僅僅從字節數組中是無法判斷出要解析的目標類型信息
        pipeline.addLast(new ProtobufDecoder(Person.Student.getDefaultInstance()));
        //protobuf編碼器 對protobuf協議的的消息頭上加上一個長度爲32的整形字段,用於標誌這個消息的長度
        pipeline.addLast(new ProtobufVarint32LengthFieldPrepender());
        //protobuf編碼器
        pipeline.addLast(new ProtobufEncoder());
        //自定義處理器
        pipeline.addLast(new MyProtobufClientHandler());
    }
}
package com.dragon.protobuf.client;

import com.dragon.protobuf.Person;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

public class MyProtobufClientHandler extends SimpleChannelInboundHandler<Person.Student> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Person.Student msg) throws Exception {

    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        Person.Student student = Person.Student.newBuilder()
                .setAge(100)
                .setAddress("北京市西城區")
                .setName("大江東去浪淘盡")
                .build();
        ctx.writeAndFlush(student);
    }
}
分別運行Server和client,運行結果如下:

七月 10, 2019 3:53:42 下午 io.netty.handler.logging.LoggingHandler channelRegistered
信息: [id: 0xc3ba1456] REGISTERED
七月 10, 2019 3:53:42 下午 io.netty.handler.logging.LoggingHandler bind
信息: [id: 0xc3ba1456] BIND: 0.0.0.0/0.0.0.0:8080
七月 10, 2019 3:53:42 下午 io.netty.handler.logging.LoggingHandler channelActive
信息: [id: 0xc3ba1456, L:/0:0:0:0:0:0:0:0:8080] ACTIVE
七月 10, 2019 3:53:51 下午 io.netty.handler.logging.LoggingHandler channelRead
信息: [id: 0xc3ba1456, L:/0:0:0:0:0:0:0:0:8080] READ: [id: 0xec4a0631, L:/127.0.0.1:8080 - R:/127.0.0.1:3099]
七月 10, 2019 3:53:51 下午 io.netty.handler.logging.LoggingHandler channelReadComplete
信息: [id: 0xc3ba1456, L:/0:0:0:0:0:0:0:0:8080] READ COMPLETE
100
大江東去浪淘盡
北京市西城區

    已經成功運行,並且得到了相應的運行結果
但是現在有個問題,在Server端和Client的初始化容器中,分別有

pipeline.addLast(new ProtobufDecoder(Person.Student.getDefaultInstance()));

這樣只支持單消息協議傳遞,那麼如何實現多消息協議傳遞,下一章會進行解釋
《Netty學習打卡–從小白到放棄》----- 11 - netty 之protobuf多消息協議傳遞

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章