netty中粘包、半包现象和解决方案

是什么

见下

NianServer

package com.example.netty.nian;

import com.example.test.TestA;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.CharsetUtil;

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

        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(new NioEventLoopGroup(1), new NioEventLoopGroup(10));
        serverBootstrap.channel(NioServerSocketChannel.class);

        //半包现象
        //serverBootstrap.option(ChannelOption.SO_RCVBUF, 10);

        serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {

            @Override
            protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {

                ChannelPipeline pipeline = nioSocketChannel.pipeline();
                pipeline.addLast("logging", new LoggingHandler(LogLevel.INFO));

                pipeline.addLast(new ChannelInboundHandlerAdapter() {
                    @Override
                    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                        TestA.log((ByteBuf) msg);

                        ByteBuf buf = (ByteBuf) msg;
                    }
                });


            }
        });
        //
        serverBootstrap.bind(8080).sync();
    }
}

NianClient

package com.example.netty.nian;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

import java.util.Scanner;

public class Nianclient {
    public static void main(String[] args) throws InterruptedException {
        //
        NioEventLoopGroup group = new NioEventLoopGroup();
        //注意客户端使用的不是 ServerBootstrap 而是 Bootstrap
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(group);
        // 设置客户端通道的实现类(反射)
        bootstrap.channel(NioSocketChannel.class);
        bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {
            @Override
            protected void initChannel(NioSocketChannel ch) throws Exception {
                ChannelPipeline pipeline = ch.pipeline();

                pipeline.addLast(new ChannelInboundHandlerAdapter() {
                    @Override
                    public void channelActive(ChannelHandlerContext ctx) throws Exception {
                        for (int i = 0; i < 10; i++) {
                            ByteBuf buffer = ctx.alloc().buffer(16);
                            buffer.writeBytes(new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15});
                            ctx.writeAndFlush(buffer);
                        }

                    }
                });
            }
        });

        bootstrap.connect("127.0.0.1", 8080).sync();
    }
}

粘包打印
循环发送了十次 一次性接收到了
在这里插入图片描述
在这里插入图片描述

半包打印
这里需要把服务端的注释打开
循环发送10次16字节的 但是末尾一个8字节的接收
在这里插入图片描述
在这里插入图片描述

现象分析

粘包
现象,发送 abc def,接收 abcdef

原因

  • 应用层:接收方 ByteBuf 设置太大(Netty 默认 1024)
  • 滑动窗口:假设发送方 256 bytes 表示一个完整报文,但由于接收方处理不及时且窗口大小足够大,这 256 bytes
    字节就会缓冲在接收方的滑动窗口中,当滑动窗口中缓冲了多个报文就会粘包
  • Nagle 算法:会造成粘包

半包
现象,发送 abcdef,接收 abc def

原因

  • 应用层:接收方 ByteBuf 小于实际发送数据量
  • 滑动窗口:假设接收方的窗口只剩了 128 bytes,发送方的报文大小是 256 bytes,这时放不下了,只能先发送前 128
    bytes,等待 ack 后才能发送剩余部分,这就造成了半包
  • MSS 限制:当发送的数据超过 MSS 限制后,会将数据切分发送,就会造成半包

解决方案

  1. 短链接,发一个包建立一次连接,发完就断,这样连接建立到连接断开之间就是消息的边界,缺点效率太低
  2. 每一条消息采用固定长度,缺点浪费空间
  3. 每一条消息采用分隔符,例如 \n \r,缺点需要转义
  4. 每一条消息分为 head 和 body,head 中包含 body 的长度

123方案有缺点,我们使用第四种方案

使用LengthFieldBasedFrameDecoder
构造参数介绍
int maxFrameLength---------代表每一个消息帧的最大支持长度
int lengthFieldOffset,长度域的起始位置下标
int lengthFieldLength,长度域占用了几个字节
int lengthAdjustment,长度域后还有几个字节是内容(长度域后有时不直接是内容)
int initialBytesToStrip,解码后需要舍弃的字节的数量,这里指从前到后舍弃的

测试代码
这里使用了EmbeddedChannel来进行测试

package com.example.netty.nian;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

public class TestLengthFieldDecoder {
    public static void main(String[] args) {
        EmbeddedChannel channel = new EmbeddedChannel(
                new LengthFieldBasedFrameDecoder(1024,0,4,1,0),
                new LoggingHandler(LogLevel.INFO)
        );

        ByteBuf buf = ByteBufAllocator.DEFAULT.buffer();
        send(buf, "abc");
        send(buf, "www");
        channel.writeInbound(buf);
    }

    public static void send(ByteBuf buf,String content){
        byte[] bytes = content.getBytes();
        int length = bytes.length;

        buf.writeInt(length);
        buf.writeByte(1);
        buf.writeBytes(bytes);

    }
}

输出
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值