Netty——进阶

目录

1、粘包与半包

1.1、现象分析

1.2、解决方案

1.2.1、短连接

1.2.2、定长解码器

1.2.3、基于分割符的解码器

1.2.4、LTC解码器

2、协议设计与解析

2.1、自定义协议要素


1、粘包与半包

TCP以一个段(segment)为单位,每发送一个段就要进行一次确认应答(ack)处理,但如果这么做,缺点是包的往返时间越长性能就越差。为了解决此问题,引入了窗口概念,窗口大小即决定了无需等待应答而可以继续发送的数据最大值。

窗口实际就起到一个缓冲区的作用,同时也能起到流量控制的作用。

1.1、现象分析

粘包现象:发送abc def,接收abcdef。原因:

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

半包现象:发送abcdef,接收abc def。原因:

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

本质是因为TCP是流式协议,消息无边界。

1.2、解决方案

1.2.1、短连接

package com.clp.nettyPlus;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.AdaptiveRecvByteBufAllocator;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;

@Slf4j
class HelloWorldServer {
    public static void main(String[] args) {
        new HelloWorldServer().start();
    }

    void start() {
        NioEventLoopGroup boss = new NioEventLoopGroup();
        NioEventLoopGroup worker = new NioEventLoopGroup();

        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap()
//                    .option(ChannelOption.SO_RCVBUF, 10) //设置系统的接收缓冲区(接收的滑动窗口)
                    //调整netty的接收缓冲区,即ByteBuf(最小值、初始值、最大值)
                    .childOption(ChannelOption.RCVBUF_ALLOCATOR, new AdaptiveRecvByteBufAllocator(16,16,16))
                    .group(boss, worker)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            //调试打印出服务端受到的信息
                            ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
                        }
                    });
            ChannelFuture channelFuture = serverBootstrap.bind(8080).sync();
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            log.error("server error", e);
        } finally {
            boss.shutdownGracefully();
            worker.shutdownGracefully();
        }
    }
}
package com.clp.nettyPlus;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloWorldClient {
    static final Logger log = LoggerFactory.getLogger(HelloWorldClient.class);

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            send();
        }
        System.out.println("finish");
    }

    public static void send() {
        NioEventLoopGroup worker = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap()
                    .group(worker)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer() {
                        @Override
                        protected void initChannel(SocketChannel channel) throws Exception {
                            channel.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                                @Override //在连接 channel 建立成功后触发active事件
                                public void channelActive(ChannelHandlerContext ctx) throws Exception {
                                    ByteBuf buf = ctx.alloc().buffer(16); //创建字节缓冲区
                                    buf.writeBytes(new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
                                            10, 11, 12, 13, 14, 15, 16,17});
                                    ctx.writeAndFlush(buf); //发送这些数据
                                    ctx.channel().close(); //断开连接
                                    super.channelActive(ctx);
                                }
                            });
                        }
                    });
            ChannelFuture channelFuture = bootstrap.connect("localhost", 8080).sync();
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            log.error("client error", e);
        } finally {
            worker.shutdownGracefully();
        }
    }
}
结果:
09:29:32.081 [nioEventLoopGroup-3-6] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledUnsafeDirectByteBuf(ridx: 0, widx: 2, cap: 16) that reached at the tail of the pipeline. Please check your pipeline configuration.
09:29:32.081 [nioEventLoopGroup-3-6] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded message pipeline : [LoggingHandler#0, DefaultChannelPipeline$TailContext#0]. Channel : [id: 0x38d017e9, L:/127.0.0.1:8080 - R:/127.0.0.1:57609].
09:29:32.081 [nioEventLoopGroup-3-6] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x38d017e9, L:/127.0.0.1:8080 - R:/127.0.0.1:57609] READ COMPLETE
09:29:32.081 [nioEventLoopGroup-3-6] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x38d017e9, L:/127.0.0.1:8080 - R:/127.0.0.1:57609] READ COMPLETE
09:29:32.081 [nioEventLoopGroup-3-6] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x38d017e9, L:/127.0.0.1:8080 ! R:/127.0.0.1:57609] INACTIVE
09:29:32.081 [nioEventLoopGroup-3-6] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x38d017e9, L:/127.0.0.1:8080 ! R:/127.0.0.1:57609] UNREGISTERED
09:29:32.081 [nioEventLoopGroup-3-7] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x630e6f03, L:/127.0.0.1:8080 - R:/127.0.0.1:57626] REGISTERED
09:29:32.081 [nioEventLoopGroup-3-7] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x630e6f03, L:/127.0.0.1:8080 - R:/127.0.0.1:57626] ACTIVE
09:29:32.081 [nioEventLoopGroup-3-7] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x630e6f03, L:/127.0.0.1:8080 - R:/127.0.0.1:57626] READ: 16B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
+--------+-------------------------------------------------+----------------+
09:29:32.081 [nioEventLoopGroup-3-7] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledUnsafeDirectByteBuf(ridx: 0, widx: 16, cap: 16) that reached at the tail of the pipeline. Please check your pipeline configuration.
09:29:32.081 [nioEventLoopGroup-3-7] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded message pipeline : [LoggingHandler#0, DefaultChannelPipeline$TailContext#0]. Channel : [id: 0x630e6f03, L:/127.0.0.1:8080 - R:/127.0.0.1:57626].
09:29:32.081 [nioEventLoopGroup-3-7] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x630e6f03, L:/127.0.0.1:8080 - R:/127.0.0.1:57626] READ COMPLETE
09:29:32.081 [nioEventLoopGroup-3-7] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x630e6f03, L:/127.0.0.1:8080 - R:/127.0.0.1:57626] READ: 2B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 10 11                                           |..              |
+--------+-------------------------------------------------+----------------+
...

1.2.2、定长解码器

1.2.3、基于分割符的解码器

1.2.4、LTC解码器

lengthFieldOffset:长度字段偏移量,表明长度字段部分从哪开始
lengthFieldLength:长度字段本身占用的字节数
lengthAdjustment:长度之后还有几个字节才是内容
initialBytesToStrip:经过解析之后,从头开始要剥离几个字节
package com.clp.decodeEncode;

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, 5),
                new LoggingHandler(LogLevel.DEBUG)
        );

        //发送 4 字节内容的长度,然后是实际内容
        ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer();
        write(buffer, "Hello, world");
        write(buffer, "Hi!");
        channel.writeInbound(buffer);
    }

    private static void write(ByteBuf buffer, String content) {
        byte[] bytes = content.getBytes(); //实际内容
        int length = bytes.length; //实际内容长度
        buffer.writeInt(length); //大端表示法写入长度
        buffer.writeByte(1); //加上版本号 1字节
        buffer.writeBytes(bytes); //写入内容
    }
}


结果:
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 48 65 6c 6c 6f 2c 20 77 6f 72 6c 64             |Hello, world    |
+--------+-------------------------------------------------+----------------+
10:32:24.729 [main] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ: 3B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 48 69 21                                        |Hi!             |
+--------+-------------------------------------------------+----------------+
10:32:24.729 [main] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ COMPLETE

2、协议设计与解析

2.1、自定义协议要素

  • 魔数:用来在第一时间判断是否是无效数据包。
  • 版本号:可以支持协议的升级。
  • 序列化算法:消息正文到底采用哪种序列化方式,可以由此扩展,例如:json、protobuf、hessian、jdk。
  • 指令类型:是登录、注册、单聊、群聊...跟业务相关。
  • 请求序号:为了双工通信,提供异步能力。
  • 正文长度。
  • 消息正文。

你可能感兴趣的:(Netty,java,学习)