netty粘包拆包之LineBasedFrameDecoder解码器

Netty-解码器 该解码器是四个解码器中相对来说稍显复杂一点,不是实现复杂,是因为以下两个方面的原因:一是参数较多,二是准确计算各参数的值。1.发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了。2.发送端将每个数据包封装为固定长度(不够的可以通过补0填充),这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。行解码器:使用换行符\n或者\r\n作为依据,遇到\n或者\r\n都认为是一条完整的消息。 阅读详情

​目录

          一、背景简介

          二、应用

          三、源码


一、背景简介

LineBasedFrameDecoder基于回车换行符解码器,他能够按照我们输入的回车换行符(\r or \r\n)对接收到的消息进行解码,在服务端接收到信息进行解析的时候LineBasedFrameDecoder的构造方法需要传递一个参数maxLength,用来限制对接收到的数据进行解码的时候一次解码的最大长度。如果在解码的过程中对传递的数据搜索到了换行符,那么换行符之前的数据(readerindex之后,换行符之前)length会和maxLength进行比较,如果大于maxLength,则readerindex到本次搜索到的换行符之间的数据就会被丢弃。反之,如果在一次解码的过程中对传递的数据没有搜索到换行符,那么readerindex之后的数据(缓冲区中可读部分)就全部会被丢弃,也就是客户端在发送数据的时候如果没有键入换行符,则数据服务端是不会获取到的(实际上数据还是在ByteBuf缓冲区中的,只是没有被读取到)。

二、应用

下面是LineBasedFrameDecoder的一个简单小应用测试,模拟发送不同信息服务端使用LineBasedFrameDecoder解码出来的信息是什么样的。

服务端:

public class LineBasedFrameDecoderTestServer {
​
    public static void main(String[] args) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) {
                            ch.pipeline()
                                   .addLast(new LineBasedFrameDecoder(1024))
                                    .addLast(new ChannelInboundHandlerAdapter() {
                                        @Override
                                        public void channelRead(ChannelHandlerContext ctx, Object msg) {
                                            if (msg instanceof ByteBuf) {
                                                ByteBuf packet = (ByteBuf) msg;
                                                System.out.println(packet.toString(Charset.defaultCharset()));
                                            }
                                        }
                                    });
                        }
                    });
            ChannelFuture f = b.bind(9000).sync();
            System.out.println("Started LineBasedFrameDecoderTestServer...");
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }
}

客户端:

public class LineBasedFrameDecoderTestClient {
​
    public static void main(String[] args) throws Exception {
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(workerGroup)
                    .channel(NioSocketChannel.class)
                    .option(ChannelOption.SO_KEEPALIVE, true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline()
                                    .addLast(new ChannelInboundHandlerAdapter() {
                                        public void channelActive(ChannelHandlerContext ctx) {
                                            ByteBuf byteBuf = Unpooled.buffer().writeBytes("ABCDEFG杀杀\r\n杀杀杀杀是\n".getBytes());
                                            ctx.writeAndFlush(byteBuf);
                                        }
                                    });
                        }
                    });
            ChannelFuture f = b.connect("127.0.0.1", 9000).sync();
            System.out.println("Started LineBasedFrameDecoderTestClient...");
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
        }
    }
}

这里LineBasedFrameDecoder构造方法中传递的参数maxLength为1024字节。

①、客户端发送的数据不带换行符

可以看到服务端并没有输出任何东西。
②、客户端发送的数据不带有\r or \r\n

③、最后一个换行符后还有数据。

如果最后一个换行符后还有数据,那么之后的数据都没有解析出来。

可以看到服务端会以\r or \r\n为分隔进行数据的解析输出。

三、源码

netty提供的各种解码器统一都继承了ByteToMessageDecoder,ByteToMessageDecoder负责读取字节流并转换成其他消息,而ByteToMessageDecoder又继承了ChannelInboundHandlerAdapter,而ChannelInboundHandlerAdapter正是我们刚才在测试数据的时候在客户端重写了他的方法channelActive(),使管道生效并且数据写入缓冲区并发送数据。

在客户端代码流程原理并不复杂,我们主要看服务端是怎么解码、获取到数据的。

当客户端通过Channel发送数据的时候,服务端会在ByteToMessageDecoder的channelRead方法接收到。我们以channelRead方法为入口看服务端是怎么进行消息解码的。

在channelRead方法中首先会判断消息msg是不是ByteBuf类型的:

①、如果msg不是ByteBuf类型的,直接调用ctx.fireChannelRead(msg)方法,fireChannelRead方法实际上是调用pipeline管道的下一个Handler去继续处理消息其他的逻辑去了,我们进入到AbstractChannelHandlerContext的fireChannelRead方法,发现只有一个方法invokeChannelRead,寻找下一个绑定的Handler并且调用ChannelRead方法。

    @Override
    public ChannelHandlerContext fireChannelRead(final Object msg) {
        //寻找下一个绑定的Handler并且调用ChannelRead方法
        invokeChannelRead(findContextInbound(), msg);
        return this;
    }

继续进入到invokeChannelRead。

static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
        final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            next.invokeChannelRead(m);
        } else {
            executor.execute(() -> next.invokeChannelRead(m));
        }
    }

最终都会调用invokeChannelRead,而invokeChannelRead就是将Handler转换成ChannelInboundHandler类型去执行具体的channelRead方法,去进一步的读取消息并处理相关Handler逻辑。

   private void invokeChannelRead(Object msg) {
        if (invokeHandler()) {
            try {
                ((ChannelInboundHandler) handler()).channelRead(this, msg);
            } catch (Throwable t) {
                notifyHandlerException(t);
            }
        } else {
            fireChannelRead(msg);
        }
    }

②、如果msg是ByteBuf类型的,去调用解码逻辑callDecode方法去解析数据。

protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
        try {
            while (in.isReadable()) {
                int outSize = out.size();
                //将out里的事件执行并且清空out
                if (outSize > 0) {
                    fireChannelRead(ctx, out, outSize);
                    out.clear();
                    // 在继续解码之前,请检查是否已删除此处理程序
                    // 如果已将其删除,则继续在缓冲区上操作是不安全的。
                    if (ctx.isRemoved()) {
                        break;
                    }
                    outSize = 0;
                }
                int oldInputLength = in.readableBytes();
                // 开始解析数据,如果解析出来数据,那么out的长度一定会改变
                decode(ctx, in, out);
​
                // 在继续循环之前,请检查是否已删除此处理程序。
                // 如果已将其删除,则继续在缓冲区上操作是不安全的。
                if (ctx.isRemoved()) {
                    break;
                }
                // 如果没有解析出来数据
                if (outSize == out.size()) {
                    if (oldInputLength == in.readableBytes()) {
                        break;
                    } else {
                        continue;
                    }
                }
​
                if (oldInputLength == in.readableBytes()) {
                    throw new DecoderException(
                            StringUtil.simpleClassName(getClass()) +
                            ".decode() did not read anything but decoded a message.");
                }
                if (isSingleDecode()) {
                    break;
                }
            }
        } catch (DecoderException e) {
            throw e;
        } catch (Throwable cause) {
            throw new DecoderException(cause);
        }
    }

在callDecode方法中,使用while循环去解析数据,这是因为一次发送的数据有可能包含多个换行符,对于循环中的每次解析,首先判断缓冲区中是否还有可读空间,有可读空间执行while循环体,第一次执行循环体的时候out肯定是没有数据的,也就是out的size为0,下面会调用解码逻辑decode(ctx, in, out)去进行数据的第一次解析,我们先不看decode(ctx, in, out)到底执行了什么逻辑,现在假设已经执行了一次decode(ctx, in, out)逻辑,也就是out里面有数据了,就是第一次解析出来的数据,现在out的size为1,现在进入outSize > 0逻辑判断中,可以看到这个if判断逻辑核心就是fireChannelRead(ctx, out, outSize),然后将out清空,size置位0,关于fireChannelRead方法,其实在上面刚才我们也说过了,就是调用下一个Handler处理器去执行下一步处理逻辑,我们再次跟进去。

static void fireChannelRead(ChannelHandlerContext ctx, List<Object> msgs, int numElements) {
        if (msgs instanceof CodecOutputList) {
            fireChannelRead(ctx, (CodecOutputList) msgs, numElements);
        } else {
            for (int i = 0; i < numElements; i++) {
                ctx.fireChannelRead(msgs.get(i));
            }
        }
    }

继续进入到fireChannelRead,来到AbstractChannelHandlerContext的fireChannelRead。

    @Override
    public ChannelHandlerContext fireChannelRead(final Object msg) {
        //寻找下一个绑定的Handler并且调用ChannelRead方法
        invokeChannelRead(findContextInbound(), msg);
        return this;
    }

继续往里走。

static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
        final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            next.invokeChannelRead(m);
        } else {
            executor.execute(() -> next.invokeChannelRead(m));
        }
    }
​
    private void invokeChannelRead(Object msg) {
        if (invokeHandler()) {
            try {
                ((ChannelInboundHandler) handler()).channelRead(this, msg);
            } catch (Throwable t) {
                notifyHandlerException(t);
            }
        } else {
            fireChannelRead(msg);
        }
    }

重点看((ChannelInboundHandler) handler()).channelRead(this, msg),这里将handler转成ChannelInboundHandler类型,执行他的channelRead方法,其实就是我们刚才服务端的重写的channelRead。

也就是说解析完一次数据时候立刻就去执行我们自定义的处理逻辑去了。当然在这里只是一个测试代码,如果真正我们自定义的处理逻辑比较复杂,比如涉及到操作数据库、磁盘IO等,可以考虑放入线程池中进行业务处理,避免造成解析数据缓慢,网络迟钝。

回到我们刚才while循环解析数据的逻辑,进入到decode(ctx, in, out)中。进入到其实现类LineBasedFrameDecoder中。

这里我们先看一下LineBasedFrameDecoder中核心属性。

    // ==========================关键属性=====================
    /** 我们解码的最大长度*/
    private final int maxLength;
    /** 一旦解码超过maxLength,是否触发快速失败机制 默认为false*/
    private final boolean failFast;
    /** 是否开启丢弃模式,默认false.*/
    private final boolean stripDelimiter;
    /** 是否开启丢弃模式,则为true.*/
    private boolean discarding;
    /** 丢弃字节的长度*/
    private int discardedBytes;

接着进入到decode方法中去。

    @Override
    protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        Object decoded = decode(ctx, in);
        if (decoded != null) {
            out.add(decoded);
        }
    }

decoded就是解析完的对象,如果这个对象不为空,就放到out中去,再看decode方法。

    protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
        // 在buffer中搜索换行符
        final int eol = findEndOfLine(buffer);
        // 如果是非丢弃模式,正常往下执行
        if (!discarding) {
            // 如果缓冲区中存在换行符
            if (eol >= 0) {
                // frame用以保存下面读取到的数据
                final ByteBuf frame;
                // readerindex和eol之间就是可读范围
                final int length = eol - buffer.readerIndex();
                //标识\r or \r\n
                final int delimLength = buffer.getByte(eol) == '\r'? 2 : 1;
                // 如果本次可读的范围超过了最大阈值,那么跳过这段数据
                if (length > maxLength) {
                    // 设置readerIndex,跳到该换行符之后
                    buffer.readerIndex(eol + delimLength);
                    // 抛出异常信息
                    fail(ctx, length);
                    return null;
                }
                // 最终读取的数据是否要跳过换行符
                if (stripDelimiter) {
                    //跳过
                    frame = buffer.readRetainedSlice(length);
                    buffer.skipBytes(delimLength);
                } else {
                    //不跳过
                    frame = buffer.readRetainedSlice(length + delimLength);
                }
                return frame;
            } else {
                // 如果在buffer中没有搜索到换行符
                final int length = buffer.readableBytes();
                // 且可读的范围超过了最大阈值,那么直接跳过这段
                if (length > maxLength) {
                    discardedBytes = length;
                    // 将readerindex移动到writerindex,表示这一段数据全部丢弃
                    buffer.readerIndex(buffer.writerIndex());
                    // 进入丢弃模式
                    discarding = true;
                    // 如果开启了快速失败机制,则触发
                    if (failFast) {
                        fail(ctx, "over " + discardedBytes);
                    }
                }
                return null;
            }
            // 如果是丢弃模式
        } else {
            // 如果搜索到换行符, 那么这一段直接丢弃
            if (eol >= 0) {
                // 计算丢弃长度
                final int length = discardedBytes + eol - buffer.readerIndex();
                //标识\r or \r\n
                final int delimLength = buffer.getByte(eol) == '\r'? 2 : 1;
                // 直接跳到换行符之后
                buffer.readerIndex(eol + delimLength);
                // 丢弃模式转正常模式
                discardedBytes = 0;
                discarding = false;
                // 如果开启了快速失败机制,则触发
                if (!failFast) {
                    fail(ctx, length);
                }
            } else {
                // 如果还没有找到换行符, 那么这次的全部丢弃
                discardedBytes += buffer.readableBytes();
                buffer.readerIndex(buffer.writerIndex());
            }
            return null;
        }
    }

这部分代码是LineBasedFrameDecoder解析数据的核心逻辑,代码中注释已经比较清楚了,首先findEndOfLine(buffer)作用就是从缓冲区中搜索换行符,我们进去看其执行逻辑。

    private static int findEndOfLine(final ByteBuf buffer) {
        int i = buffer.forEachByte(ByteProcessor.FIND_LF);
        if (i > 0 && buffer.getByte(i - 1) == '\r') {
            i--;
        }
        return i;
    }

在这里buffer.forEachByte是从readerindex开始依次搜索寻找换行符,返回的i值就是换行符的下标(代表\n的位置)。下面对i进行判断,如果i大于零并且i代表\n的位置前面还有个\r,那么i的值就减一,也就是说i始终是指向一行的结束的下一个字符。

继续往forEachByte里走。

     @Override
    public int forEachByte(ByteProcessor processor) {
        ensureAccessible();
        try {
            return forEachByteAsc0(readerIndex, writerIndex, processor);
        } catch (Exception e) {
            PlatformDependent.throwException(e);
            return -1;
        }
    }
    
    private int forEachByteAsc0(int start, int end, ByteProcessor processor) throws Exception {
        for (; start < end; ++start) {
            if (!processor.process(_getByte(start))) {
                return start;
            }
        }
        return -1;
    }

到这里从应用的层面来看已经逻辑很清楚了,就是从readerIndex开始进行一次搜索到writerIndex看是否有换行符(\n),如果有,返回其在缓冲区中的位置。

好了,现在我们回到之前decode方法中调用findEndOfLine的地方,继续往下走,调用完findEndOfLine方法,返回的eol就是从readerindex开始第一次\n出现的位置,然后判断是否开启了丢弃模式,默认是没有开启的,所以接下来正常执行,判断缓冲区中是否存在换行符:

①、如果我们搜索到了换行符的话,readerindex和eol之间就是我们需要读取数据的范围,然后delimLength 标识\r or \r\n , 接下来判断如果本次可读的范围超过了最大阈值,那么跳过这段数据,其实就是设置readerIndex,跳到该换行符之后。

②、如果在buffer中没有搜索到换行符,可读的范围如果超过了最大阈值,那么直接跳过这段数据。将readerindex移动到writerindex,表示这一段数据全部丢弃。如果没有超过最大阈值,则直接返回null,读写索引位置都不变。

继续回到之前判断是否丢弃模式的逻辑,现在如果是丢弃模式,而且还没有搜索到换行符的话,那么认为发送的数据还不是一个完成的数据包,所有的数据都会被再次丢弃,反之如果搜索到换行符, 直接把分隔符之前的数据(也包括分隔符)全部丢弃,在此过程中丢弃模式转正常模式、如果开启了快速失败机制,则触发。

也就是说如果在某次数据传输过程中,传输的数据大于了阈值maxLength,而且传输的数据中没有换行符,那么就会进入丢弃模式,之后传输的数据如果一直没有换行符,那么数据就会一直被丢弃,直到出现了一次传输的数据中出现了换行符,直接把分隔符之前的数据(也包括分隔符)全部丢弃,丢弃之后后面再次发送的数据就有可能是正常的数据包,下一次解包的时候就会进入正常的解包流程那么这次出现了丢弃模式被解除。但是这里有个疑问,解除丢弃模式为什么要抛出异常信息,是为了作为解除丢弃模式的提示信息?

至此,服务端利用LineBasedFrameDecoder解码器解析数据的流程也就分析完了。

个人才疏学浅、信手涂鸦,netty框架更多模块解读相关源码持续更新中,感兴趣的朋友请移步至个人公众号,谢谢支持😜😜......

公众号:wenyixicodedog

Netty4实战 - TCP粘包&拆包解决方案 阅读详情

相关推荐

NPU、CPU和GPU

NPU、CPU和GPU是三种不同类型的处理器,它们各自有不同的设计特点和应用场景。NPU(Neural Processing Unit,神经网络处理单元)是一种专门为加速神经网络计算而设计的处理器。

m0_60697583的博客 1755

io.netty学习(十六)自定义解码器、编码器、编解码器

我们今天继续来分析 Netty 的编解码器,这次我们要自己动手实现自定义的编码器、解码器和编解码器。LineBasedFrameDecoder 类是基于换行的,意味着只要在接收数据时遇到以换行符\n或者回车换行符\r\n结尾时,就表明数据已经接收完成可以被处理了。LineBasedFrameDecoder 类继承自 ByteToMessageDecoder,并重写了decode方法。/** 帧的最大长度限制 *//** 帧超长时是否抛出异常 */

码灵的博客 2767

【编译原理】LL(1)分析方法 生成语法树(c++实现)

基本流程 输入文法->消除左递归->提取左公共因子->求FIRST集->求FOLLOW集->LL(1)分析表->表驱动的预测语法分析过程->输出语法分析结果和语法树

qq_43417265的博客 1万+

Netty技术全解析:LineBasedFrameDecoder类深度解析

LineBasedFrameDecoder是Netty中用于处理基于换行符的帧划分的解码器。通过提供灵活的帧划分策略和错误处理机制,它使得开发者可以更容易地编写基于文本协议的网络通信程序。

码到三十五 1628

GCC调试黑科技:-fomit-frame-pointer优化下如何保证栈回溯能力

本文深入探讨了GCC编译器在启用`-fomit-frame-pointer`优化选项时如何保证栈回溯能力。通过分析`.eh_frame`段的结构与DWARF调试信息,提供了从编译选项配置到实战调试的完整解决方案,帮助开发者在性能优化与调试能力之间找到最佳平衡点。

u6v7w8x的博客 906

3-RRR并联机器人结构详解:从运动学到奇异点分析(附MATLAB仿真代码)

本文详细解析了3-RRR并联机器人的结构设计、运动学建模及奇异点分析,并提供了MATLAB仿真代码。通过对称性设计和可分析性,3-RRR结构成为教学与研究的理想对象。文章从基础杆件参数设置到奇异位形的实时捕捉,深入探讨了机构运动的数学本质,并分享了仿真调试中的实用技巧,助力工业自动化和精密控制应用。

sunny的博客 369

LineBasedFrameDecoder

LineBasedFrameDecoder 和LineEncoder采用的通信协议非常简单,即按照行进行分割,遇到一个换行符,则认为是一个完整的报文。在发送方,使用LineEncoder为数据添加换行符;在接受方,使用LineBasedFrameDecoder对换行符进行解码。 1 LineBasedFrameDecoder LineBasedFrameDecoder采用的通信协议格式非常简单:使用换行符\n或者\r\n作为依据,遇到\n或者\r\n都认为是一条完整的消息。 LineBasedFram

h_sn9999的专栏 1155

【汇编实战开发笔记】一段汇编代码如何“反编译”成C代码?

介绍了一种由汇编代码得到C代码的方法

一个专注于嵌入式IoT领域的架构师,深耕IoT领域多年,深度掌握IoT领域的相关技术栈,包括但不限于RTOS内核的实现及其移植、硬件驱动移植开发、网络通讯协议开发、编译构建原理及其实现、底层汇编及编译原理、编译优化及代码重构、嵌入式IoT系统的架构设计等。 1万+

Netty源码分析 (十一)----- 拆包器之LengthFieldBasedFrameDecoder

本篇文章主要是介绍使用LengthFieldBasedFrameDecoder解码器自定义协议。通常,协议的格式如下: LengthFieldBasedFrameDecoder是netty解决拆包粘包问题的一个重要的类,主要结构就是header+body结构。我们只需要传入正确的参数就可以发送和接收正确的数据,那么重点就在于这几个参数的意义。下面我们就具体了解一下这几个参数的意义。...

weixin_30294709的博客 497

92. SAP ABAP 系统里长文本 Long Text 的设计和读取函数 READ_TEXT 的详细讲解

SAP ABAP中的长文本(Long Text)是指在SAP系统中存储和管理较长文本信息的一种机制。长文本通常用于存储描述性信息、注释、说明等,这些文本信息可能超过了标准数据库表字段的长度限制。 长文本的作用主要有以下几点: 1. 更多的信息存储:长文本允许您存储和显示比标准表字段允许的更多信息。例如,产品描述、用户注释、维护计划等可能需要大量的文本来详细描述。 2. 更好的可读性:长文本提供了更好的文本格式化选项,可以改善可读性。用户可以使用段落、列表、字体样式等格式化文本,以便更容易地理解和阅读。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 1999

Vue2 - 最新详细实现接入天地图详细配置流程及示例代码教程,提供获取用户当前定位的城市名称及省市区数据、精准经纬度信息、IP属地定位、地图搜索标点等示例,解决天地图跨域报错/页面白屏/定位不准问题

vue2,nuxt2,天地图,使用教程,vue接入国家天地图,定位api,ip属地定位,获取用户当前位置数据,省市区,地图不显示,白屏,报错,vue2如何使用天地图,地图选点,cors跨域,白名单设置了还是不行,vue项目调用天地图api实现定位,Vue2中使用天地图,详细教程,天地图申请密钥key教程,经纬度,经纬度不准确,一直定位北京,定位在北京不准怎么办,nuxt项目天地图,网站,网页,h5页面,地图显示不全,获取当前定位,如何使用天地图,拿到客户ip地址定位城市名称,距离计算,实时搜索地址逆解析,天

🏆 前端领域优质创作者 5328

netty系列之:netty中的frame解码器

文章目录简介LineBasedFrameDecoderDelimiterBasedFrameDecoderFixedLengthFrameDecoderLengthFieldBasedFrameDecoder总结 简介 netty中的数据是通过ByteBuf来进行传输的,一个ByteBuf中可能包含多个有意义的数据,这些数据可以被称作frame,也就是说一个ByteBuf中可以包含多个Frame。 对于消息的接收方来说,接收到了ByteBuf,还需要从ByteBuf中解析出有用而数据,那就需要将ByteBu

程序那些事 1668

YOLOv8中的可变形卷积改进 | 从DCNv1到DCNv3的多位置替换可变形卷积保姆级实操

可变形卷积(Deformable Convolution)最早由Dai等人在2017年提出,其核心思想是在标准卷积操作的基础上,引入可学习的偏移量(offsets),使卷积核能够自适应调整其采样位置,从而增强模型对目标变形的感知能力。return x本文通过深入探讨可变形卷积在YOLOv8中的应用,详细介绍了DCNv1、DCNv2和DCNv3的原理和实现,并通过实验验证了其在目标检测任务中的有效性。未来,我们将继续探索更多优化策略和技术,提升YOLOv8模型的性能,以应对更复杂的目标检测任务。

一键难忘的博客 4051

Netty详解(五):Netty TCP粘包 拆包

1. 概述 无论是服务端还是客户端,我们读取或者发送消息的时候,都需要考虑TCP底层的粘包和拆包机制。下面我们来通过Netty来详解TCP底层的粘包和拆包机制。 2. TCP底层的粘包和拆包机制 TCP是一个“流”协议,所谓流,就是没有界限的一串数据。大家可以想想河里的水流,它们是连城有一片的,期间没有界限。TCP底层并不了解上层业务数据的具体含义,他会根据TCP缓冲区的实际情况进行包的划分,所以...

Master-TJ的个人博客 1667

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

Netty学习——高级篇1 拆包 、粘包与编解码技术 备份

当Server在读取客户端数据的时候,如果一次读取不完整,就会触发channelRead事件,那么Netty是如何处理这类问题的?本节会详细讲解。

geminigoth的博客 1153

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

TCP粘包/拆包问题之Netty解决方案

TCP以流的方式进行数据传输,上层的应用协议为了对消息进行区分,往往采用如下四种方式: 消息长度固定,累计读取到长度总和为定长LEN的报文后,就认为读取到了一个完整的消息;将计数器置位,重新开始读取下一个数据报; 将回车换行符作为消息结束符,例如FTP协议,这种方式在文本协议中应用比较广泛; 将特殊的分隔符作为消息的结束标志,回车换行符就是一种特殊的结束分隔符; 通过在消息头中定义...

. 449

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

Netty学习——高级篇1 拆包 、粘包与编解码技术

当Server在读取客户端数据的时候,如果一次读取不完整,就会触发channelRead事件,那么Netty是如何处理这类问题的?本节会详细讲解。

geminigoth的博客 1326

初识Netty之拆包和粘包

Netty 是由 JBOSS 提供的基于NIO一个 Java 开源网络编程框架。Netty 提供异步的、基于事件驱动的网络应用程序框架,用以快速开发高性能、高可靠性的网络 IO 程序。作为当前最流行的 NIO 框架,Netty应用很广泛,hadoop、dubbo等底层RPC都是基于Netty实现的。以下是实现的章节: 初识Netty之对BIO和NIO的认识 初识Netty之线程模型和异步模型...

anyway8090的博客 214

【Netty权威指南】04-TCP粘包/拆包问题的解决之道

1、TCP粘包/拆包 TCP是个“流”协议,所谓流,就是没有界限的一串数据。大家可以想想河里的流水它们是连成一片的,其间并没有分界线。TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题。 1.1、TCP粘包/拆包问题...

通往神秘的道路的专栏 833

Netty-4 TCP粘包拆包问题

一、TCP粘包/拆包问题 TCP是一个“流”协议,所谓流,也就是一串没界限的数字,在TCP底层是连接成一片的,没有界限,因为不知道业务层含义,所以只会根据TCP缓冲区的实际情况进行划分,导致在业务层来看,一个完整的包可能会被TCP拆分成多个包,也可能把多个小的包封装起来,形成一个大的包。 粘包和拆包可能会出现如下情况: 假设客户端发送两个数据包D1和D2给服务端,由于服务端一次读到的字节数是不固...

mail_liuxing的专栏 806

Netty-粘包半包:拆包器(编码器和解码器)

粘包半包 粘包:多个字符串“粘”在了一起,这种 ByteBuf 为粘包 半包:一个字符串被“拆”开,形成一个破碎的包,这种 ByteBuf 为半包 拆包 我们需要知道,尽管我们在应用层面使用了 Netty,但是对于操作系统来说,只认 TCP 协议,尽管我们的应用层是按照 ByteBuf 为 单位来发送数据,但是到了底层操作系统仍然是按照字节流发送数据,因此,数据到了服务端,也是按照字节流的方式读入,然后到了 Netty 应用层面,重新拼装成 ByteBuf,而这里的 ByteB..

小毛贼_哪里逃 580

Android Netty的使用-入门指南

前言 很久没更新博客了,主要是 最近发生的事情太多了。开始学习~ Netty使用步骤前言简要解码器介绍(需要了解的)粘包/拆包LineBasedFrameDecoder(行解码器)DelimiterBasedFrameDecoder(特殊分隔符解码器)FixedLengthFrameDecoder(固定长度解码器)StringDecoder(字符串解码器)备注使用步骤(代码中集合了三种案例)服务端EchoServer1.class 准备工作类EchoServerHandler.class 处理发送 接收消息

男儿不展风云志,空负天生八尺躯。 4417

学习netty遇到的关于 LineBasedFrameDecoder 的问题

最近在看《Netty权威指南》这本书,关于TCP粘包/拆包,书中使用的是LineBasedFrameDecoder 来解决的,但是我在实践的过程中出现了问题,上代码吧。 这个是 server 的代码 package com.cd.netty4.zhc.demo.ex01; import java.text.SimpleDateFormat; import java.ut...

weixin_30565199的博客 452

Netty 解决粘包拆包

While parsing a protocol message, the input ended unexpectedly in the middle of a field. This could mean either that the input has been truncated or that an embedded message misreported its own length...

haoxiaoyong1014的博客 2993

Netty 解码器深入分析:调用时机与继承关系

定义:解码器是 Netty 中用来从 ByteBuf 中提取消息内容的组件。作用:将字节流转化为 Java 对象,以供业务层处理。常见解码器:LengthFieldBasedFrameDecoder、LineBasedFrameDecoder、DelimiterBasedFrameDecoder等。类定义:LineBasedFrameDecoder是一个基于行分隔符的帧解码器,通常用于处理类似HTTP、IRC 等协议。构造方法:maxLength:设置消息的最大长度,用于防止无限制读取数据。

m0_71749752的博客 1129

Netty网络编程五:Netty粘包拆包解码器源码分析与自定义解码器

Netty网络编程五:Netty粘包拆包解码器源码分析之自定义解码器 一:概述 由上一篇文章讲完了ByteToMessageDecoder,再来看其实现LineBasedFrameDecoder ,DelimiterBasedFrameDecoder,FixedLengthFrameDecoder发现只要实现decode()方法即可,并按照指定的分割条件进行字节流数据分割,然后将处理好的数据加入到...

shelly南瓜 290
上一篇: netty粘包拆包之FixedLengthFrameDecoder解码器
下一篇: Mac 安装 brew
wenyixicodedog
博客等级 码龄8年 87粉丝 116原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值