Netty(二):源码解析之粘包、半包和编解码

1.什么是粘包和半包

假设我发送一个消息ABC DEF两块消息,此时接收方不一定是通过两个包就接收到两块消息,可能产生的情况有下述几种

例如:

  1. 一个包内包含ABCDEF的信息,这种叫做粘包
  2. 也可能分为三个包AB、CD、EF,这种叫做半包

换个角度理解

收发角度来看:一个发送可能会被多次接收,多个发送可能会被一次接收

传输角度来看:一个发送可能占用多个传输包,多个发送可能共用一个传输包

那么会什么会出现粘包和半包现象呢?

2.为什么TCP会出现粘包和半包

总结为一句话:TCP是流式协议,消息无边界

UDP 像邮寄的包裹,虽然一次运输多个,但每个包裹都有“界限”,一个一个签收,所以无粘包、半包问题

粘包的主要原因

  • 发送方每次写入数据 < 套接字缓冲区大小
  • 接收方读取套接字缓冲区数据不够及时

半包的主要原因

  • 发送方每次写入数据 > 套接字缓冲区大小
  • 发送的数据大于协议的 MTU(Maximum Transmission Unit,最大传输单元),必须拆包

image-20210922163825472

3.解决粘包和半包问题的几种常见方法

image-20210922164056495

4.Netty对三种常用封帧方式的支持

image-20210922164351512

其中固定长度和分割符的方式编码比较简单,因此netty并没有创建具体的类去实现

5.Netty处理粘包、半包的源码

image-20210922171345367

5.1.解码核心工作流程

5.2.解码中两种数据积累器的区别

5.3.三种解码器的常用额外控制参数有哪些

6.二次解码

6.1.什么是二次解码

一次解码负责通过新增长度信息等方式解决粘包、半包的问题,二次解码负责将数据按照一定的编码方式(JSON、XML等)进行序列化及反序列化(例如将报文数据反序列化为Java对象)

image-20210922173415451

image-20210922175434413

6.2.选择编解码方式的要点

image-20210922180222822

6.3.Protobuf 简介与使用

Protobuf 是一个灵活的、高效的用于序列化数据的协议。

相比较 XML 和 JSON 格式,Protobuf 更小、更快、更便捷。

Protobuf 是跨语言的,并且自带了一个编译器(protoc),只需要用它进行编译,可

以自动生成 Java、python、C++ 等代码,不需要再写其他代码。

6.4.netty二次解码的源码

使用示例:io.netty.example.worldclock.WorldClockClientInitializer

/*
 * Copyright 2012 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package io.netty.example.worldclock;

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.ssl.SslContext;

public class WorldClockClientInitializer extends ChannelInitializer<SocketChannel> {

    private final SslContext sslCtx;

    public WorldClockClientInitializer(SslContext sslCtx) {
        this.sslCtx = sslCtx;
    }

    @Override
    public void initChannel(SocketChannel ch) {
        ChannelPipeline p = ch.pipeline();
        if (sslCtx != null) {
            p.addLast(sslCtx.newHandler(ch.alloc(), WorldClockClient.HOST, WorldClockClient.PORT));
        }

        p.addLast(new ProtobufVarint32FrameDecoder());
        p.addLast(new ProtobufDecoder(WorldClockProtocol.LocalTimes.getDefaultInstance()));

        p.addLast(new ProtobufVarint32LengthFieldPrepender());
        p.addLast(new ProtobufEncoder());

        p.addLast(new WorldClockClientHandler());
    }
}
# Netty 

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×