Netty笔记07-粘包与半包(上)

news2024/11/15 23:37:36

文章目录

  • 前言
    • 1. 粘包
      • 造成粘包的原因
      • 解决粘包的方法
    • 2. 半包
      • 造成半包的原因
      • 解决半包的方法
  • 粘包现象
    • 服务端代码示例
    • 客户端代码示例
  • 半包现象
  • 现象分析
    • 粘包
    • 半包
    • 滑动窗口
    • MSS 限制
    • Nagle 算法


前言

粘包和半包问题是网络编程中常见的问题,特别是在TCP协议中。通过合理的设计和实现,比如使用消息头、特殊分隔符或协议定义等方法,可以有效地解决这些问题。

1. 粘包

“粘包”指的是在网络传输过程中,发送方连续发送的多个数据包在接收方看来像是一个连续的数据流,无法区分这些数据包的边界。这种情况通常发生在TCP协议中,因为TCP是一个面向连接的、可靠的数据流传输协议,它不保证发送的数据包一定会按原来的方式到达接收方。

造成粘包的原因

发送方快速发送:如果发送方快速连续发送多个数据包,而接收方的接收缓冲区不足以区分这些数据包,就可能发生粘包现象。
接收方处理缓慢:如果接收方处理数据的速度慢于发送方发送数据的速度,也可能导致粘包。
网络拥塞:在网络拥塞的情况下,数据包可能会被合并成更大的数据块进行传输。

解决粘包的方法

定长分隔:发送固定长度的消息。这种方式简单但不够灵活。
消息头:在消息前加上消息长度的描述,接收方根据长度来确定消息的边界。
特殊分隔符:在消息末尾加上特定的分隔符(如 \n 或其他特殊字符),接收方根据分隔符来判断消息的结束。
心跳包:定期发送心跳包来同步发送方和接收方的状态,确保数据包的正确接收。
协议定义:明确消息的格式和边界,例如使用特定的帧格式(如HTTP/2、MQTT等)。

2. 半包

“半包”指的是在接收方收到的数据包不完整,即接收到的数据包只包含了一个完整消息的一部分。这种情况通常是由于接收方的接收缓冲区不足以容纳整个消息导致的。

造成半包的原因

接收缓冲区不足:如果接收方的接收缓冲区太小,不足以一次接收完整的消息,就会出现半包现象。
网络延迟:在网络延迟较高的情况下,数据包的到达时间不一致,可能导致接收方未能及时接收到后续的数据包。

解决半包的方法

消息头:类似于解决粘包的方法,通过消息头来确定消息的长度,从而判断是否接收到完整的消息。
重发机制:如果接收方发现消息不完整,可以请求发送方重新发送缺失的部分。
累积接收:接收方可以累积接收到的数据,直到接收到完整的消息为止。
超时机制:设置合理的超时时间,如果在超时时间内未能接收到完整的消息,则可以判定消息丢失或不完整。

粘包现象

服务端代码示例

public class HelloWorldServer {
    static final Logger log = LoggerFactory.getLogger(HelloWorldServer.class);
    void start() {
        NioEventLoopGroup boss = new NioEventLoopGroup(1);
        NioEventLoopGroup worker = new NioEventLoopGroup();
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.channel(NioServerSocketChannel.class);
            serverBootstrap.group(boss, worker);
            serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
                    ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                        @Override
                        public void channelActive(ChannelHandlerContext ctx) throws Exception {
                            log.debug("connected {}", ctx.channel());
                            super.channelActive(ctx);
                        }

                        @Override
                        public void channelInactive(ChannelHandlerContext ctx) throws Exception {
                            log.debug("disconnect {}", ctx.channel());
                            super.channelInactive(ctx);
                        }
                    });
                }
            });
            ChannelFuture channelFuture = serverBootstrap.bind(8080);
            log.debug("{} binding...", channelFuture.channel());
            channelFuture.sync();
            log.debug("{} bound...", channelFuture.channel());
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            log.error("server error", e);
        } finally {
            boss.shutdownGracefully();
            worker.shutdownGracefully();
            log.debug("stoped");
        }
    }

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

客户端代码示例

客户端代码希望发送 10 个消息,每个消息是 16 字节

public class HelloWorldClient {
    static final Logger log = LoggerFactory.getLogger(HelloWorldClient.class);
    public static void main(String[] args) {
        NioEventLoopGroup worker = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.channel(NioSocketChannel.class);
            bootstrap.group(worker);
            bootstrap.handler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    log.debug("connetted...");
                    ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                        //在连接channel建立成功后,就会触发active事件
                        @Override
                        public void channelActive(ChannelHandlerContext ctx) throws Exception {
                            log.debug("sending...");
                            Random r = new Random();
                            char c = 'a';
                            for (int i = 0; i < 10; i++) {
                                ByteBuf buffer = ctx.alloc().buffer();
                                buffer.writeBytes(new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15});
                                ctx.writeAndFlush(buffer);
                            }
                        }
                    });
                }
            });
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8080).sync();
            channelFuture.channel().closeFuture().sync();

        } catch (InterruptedException e) {
            log.error("client error", e);
        } finally {
            worker.shutdownGracefully();
        }
    }
}

服务器端的某次输出,可以看到一次就接收了 160 个字节,而非分 10 次接收

08:24:46 [DEBUG] [main] c.i.n.HelloWorldServer - [id: 0x81e0fda5] binding...
08:24:46 [DEBUG] [main] c.i.n.HelloWorldServer - [id: 0x81e0fda5, L:/0:0:0:0:0:0:0:0:8080] bound...
08:24:55 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x94132411, L:/127.0.0.1:8080 - R:/127.0.0.1:58177] REGISTERED
08:24:55 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x94132411, L:/127.0.0.1:8080 - R:/127.0.0.1:58177] ACTIVE
08:24:55 [DEBUG] [nioEventLoopGroup-3-1] c.i.n.HelloWorldServer - connected [id: 0x94132411, L:/127.0.0.1:8080 - R:/127.0.0.1:58177]
08:24:55 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x94132411, L:/127.0.0.1:8080 - R:/127.0.0.1:58177] READ: 160B
         +-------------------------------------------------+
         |  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 |................|
|00000010| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000020| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000030| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000040| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000050| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000060| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000070| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000080| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000090| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
+--------+-------------------------------------------------+----------------+
08:24:55 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x94132411, L:/127.0.0.1:8080 - R:/127.0.0.1:58177] READ COMPLETE

半包现象

客户端代码希望发送 1 个消息,这个消息是 160 字节,代码改为

public class HelloWorldClient {
    static final Logger log = LoggerFactory.getLogger(HelloWorldClient.class);
    public static void main(String[] args) {
        NioEventLoopGroup worker = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.channel(NioSocketChannel.class);
            bootstrap.group(worker);
            bootstrap.handler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    log.debug("connetted...");
                    ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                        //在连接channel建立成功后,就会触发active事件
                        @Override
                        public void channelActive(ChannelHandlerContext ctx) throws Exception {
                            log.debug("sending...");
                            Random r = new Random();
                            char c = 'a';
                            for (int i = 0; i < 10; i++) {
                                ByteBuf buffer = ctx.alloc().buffer(16);
//                                ByteBuf buffer = ctx.alloc().buffer();
                                buffer.writeBytes(new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15});
                                ctx.writeAndFlush(buffer);
                            }
                        }
                    });
                }
            });
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8080).sync();
            channelFuture.channel().closeFuture().sync();

        } catch (InterruptedException e) {
            log.error("client error", e);
        } finally {
            worker.shutdownGracefully();
        }
    }
}

为现象明显,服务端修改一下接收缓冲区,其它代码不变

public class HelloWorldServer {
    static final Logger log = LoggerFactory.getLogger(HelloWorldServer.class);
    void start() {
        NioEventLoopGroup boss = new NioEventLoopGroup(1);
        NioEventLoopGroup worker = new NioEventLoopGroup();
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.channel(NioServerSocketChannel.class);
            //配置接受缓冲区为10字节,观察客户端发送数据时出现的黏包半包问题
            serverBootstrap.option(ChannelOption.SO_RCVBUF,10);
            serverBootstrap.group(boss, worker);
            serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
//                    ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
//                        @Override
//                        public void channelActive(ChannelHandlerContext ctx) throws Exception {
//                            log.debug("connected {}", ctx.channel());
//                            super.channelActive(ctx);
//                        }
//
//                        @Override
//                        public void channelInactive(ChannelHandlerContext ctx) throws Exception {
//                            log.debug("disconnect {}", ctx.channel());
//                            super.channelInactive(ctx);
//                        }
//                    });
                }
            });
            ChannelFuture channelFuture = serverBootstrap.bind(8080);
            log.debug("{} binding...", channelFuture.channel());
            channelFuture.sync();
            log.debug("{} bound...", channelFuture.channel());
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            log.error("server error", e);
        } finally {
            boss.shutdownGracefully();
            worker.shutdownGracefully();
            log.debug("stoped");
        }
    }

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

服务器端的某次输出,可以看到接收的消息被分为两节,第一次 20 字节,第二次 140 字节

08:43:49 [DEBUG] [main] c.i.n.HelloWorldServer - [id: 0x4d6c6a84] binding...
08:43:49 [DEBUG] [main] c.i.n.HelloWorldServer - [id: 0x4d6c6a84, L:/0:0:0:0:0:0:0:0:8080] bound...
08:44:23 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x1719abf7, L:/127.0.0.1:8080 - R:/127.0.0.1:59221] REGISTERED
08:44:23 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x1719abf7, L:/127.0.0.1:8080 - R:/127.0.0.1:59221] ACTIVE
08:44:23 [DEBUG] [nioEventLoopGroup-3-1] c.i.n.HelloWorldServer - connected [id: 0x1719abf7, L:/127.0.0.1:8080 - R:/127.0.0.1:59221]
08:44:24 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x1719abf7, L:/127.0.0.1:8080 - R:/127.0.0.1:59221] READ: 20B
         +-------------------------------------------------+
         |  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 |................|
|00000010| 00 01 02 03                                     |....            |
+--------+-------------------------------------------------+----------------+
08:44:24 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x1719abf7, L:/127.0.0.1:8080 - R:/127.0.0.1:59221] READ COMPLETE
08:44:24 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x1719abf7, L:/127.0.0.1:8080 - R:/127.0.0.1:59221] READ: 140B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|
|00000010| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|
|00000020| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|
|00000030| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|
|00000040| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|
|00000050| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|
|00000060| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|
|00000070| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|
|00000080| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f             |............    |
+--------+-------------------------------------------------+----------------+
08:44:24 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x1719abf7, L:/127.0.0.1:8080 - R:/127.0.0.1:59221] READ COMPLETE

注意 serverBootstrap.option(ChannelOption.SO_RCVBUF, 10) 影响的底层接收缓冲区(即滑动窗口)大小,仅决定了 netty 读取的最小单位,netty 实际每次读取的一般是它的整数倍

现象分析

粘包

  • 现象,发送 abc def,接收 abcdef
  • 原因
    • 应用层:接收方 ByteBuf 设置太大(Netty 默认 1024)
    • 滑动窗口:假设发送方 256 bytes 表示一个完整报文,但由于接收方处理不及时且窗口大小足够大,这 256 bytes 字节就会缓冲在接收方的滑动窗口中,当滑动窗口中缓冲了多个报文就会粘包
    • Nagle 算法:会造成粘包(因为tcp和ip的报头要分别占用20个字节,所以就算发送一个字节,那么总共也要发送41个字节,所以使用粘包来减少发送次数,尽可能多的发送数据)

半包

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

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

滑动窗口

  • TCP 以一个段(segment)为单位,每发送一个段就需要进行一次确认应答(ack)处理,但如果这么做,缺点是包的往返时间越长性能就越差
    在这里插入图片描述
    为了解决此问题,引入了窗口概念,窗口大小即决定了无需等待应答而可以继续发送的数据最大值
    在这里插入图片描述
    图中窗口大小可以同时发送四个请求,当第一个请求响应后窗口向下移动,第五个请求才可以发送,第二个请求响应后窗口下移,第六个请求发送。
    窗口实际就起到一个缓冲区的作用,同时也能起到流量控制的作用
  • 图中深色的部分即要发送的数据,高亮的部分即窗口
  • 窗口内的数据才允许被发送,当应答未到达前,窗口必须停止滑动
  • 如果 1001~2000 这个段的数据 ack 回来了,窗口就可以向前滑动
  • 接收方也会维护一个窗口,只有落在窗口内的数据才能允许接收

MSS 限制

  • 链路层对一次能够发送的最大数据有限制,这个限制称之为 MTU(maximum transmission unit),不同的链路设备的 MTU 值也有所不同,例如
  • 以太网的 MTU 是 1500
  • FDDI(光纤分布式数据接口)的 MTU 是 4352
  • 本地回环地址的 MTU 是 65535 - 本地测试不走网卡
  • MSS 是最大段长度(maximum segment size),它是 MTU 刨去 tcp 头和 ip 头后剩余能够作为数据传输的字节数
  • ipv4 tcp 头占用 20 bytes,ip 头占用 20 bytes,因此以太网 MSS 的值为 1500 - 40 = 1460
  • TCP 在传递大量数据时,会按照 MSS 大小将数据进行分割发送
  • MSS 的值在三次握手时通知对方自己 MSS 的值,然后在两者之间选择一个小值作为 MSS
    在这里插入图片描述

Nagle 算法

  • 即使发送一个字节,也需要加入 tcp 头和 ip 头,也就是总字节数会使用 41 bytes,非常不经济。因此为了提高网络利用率,tcp 希望尽可能发送足够大的数据,这就是 Nagle 算法产生的缘由
  • 该算法是指发送端即使还有应该发送的数据,但如果这部分数据很少的话,则进行延迟发送
  • 如果 SO_SNDBUF 的数据达到 MSS,则需要发送
  • 如果 SO_SNDBUF 中含有 FIN(表示需要连接关闭)这时将剩余数据发送,再关闭
  • 如果 TCP_NODELAY = true,则需要发送
  • 已发送的数据都收到 ack 时,则需要发送
  • 上述条件不满足,但发生超时(一般为 200ms)则需要发送
  • 除上述情况,延迟发送

全部文章:
Netty笔记01-Netty的基本概念与用法
Netty笔记02-组件EventLoop
Netty笔记03-组件Channel
Netty笔记04-组件Future & Promise
Netty笔记05-组件Handler & Pipeline
Netty笔记06-组件ByteBuf
Netty笔记07-粘包与半包(上)
Netty笔记08-粘包与半包(下)
Netty笔记09-网络协议设计与解析
Netty笔记10-Netty参数调优

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2159237.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++编程语言:基础设施:源文件和程序(Bjarne Stroustrup)

第15章 源文件和程序 (Source Files and Programs) 目录 15.1 单独编译(Separate Compilation) 15.2 链接(Linkage) 15.2.1 文件局部名(File-Local Names) 15.2.2 头文件(Header Files) 15.2.3 一次定义原则(The One-Definition Rule) 15.2.4 标准库头文件 1…

Python办公自动化教程(001):PDF内容提取

1、Pdfplumber介绍 pdfplumber的github地址&#xff1a; https://github.com/jsvine/pdfplumber/【介绍】&#xff1a;pdfplumber 是一个用于处理 PDF 文件的 Python 第三方库&#xff0c;它提供了一种方便的方式来提取 PDF 文件中的文本、表格和其他信息。【功能】&#xff…

JEDEC DDR3 SRAM standard

DDRDouble Data Rate双倍速率,DDR SDRAM双倍速率同步动态随机存储器&#xff0c;人们习惯称为DDR&#xff0c;其中&#xff0c;SDRAM 是Synchronous Dynamic Random Access Memory的缩写&#xff0c;即同步动态随机存取存储器。而DDR SDRAM是Double Data Rate SDRAM的缩写&…

交叉熵损失函数的使用

交叉熵损失函数 交叉熵损失函数&#xff08;Cross-Entropy Loss&#xff09;&#xff0c;也称为对数损失&#xff08;Log Loss&#xff09;&#xff0c;是机器学习和深度学习中常用的损失函数之一&#xff0c;尤其在分类问题中。它衡量的是模型预测的概率分布与真实标签的概率…

写作文的AI,不妨试试这些工具

写作文的AI&#xff0c;随着人工智能技术的迅猛发展&#xff0c;AI写作软件已经成为许多作家、学生和内容创作者的得力助手。这些软件不仅能帮助用户生成高质量的文章&#xff0c;还能提供创意灵感、语法校正和内容优化等多重功能。下面&#xff0c;本文将介绍五款各具特色的AI…

s3c2440——UART串口通信

一、通信方式 1、并行通信&#xff1a;一次可收发2bit/2bit以上。通信效率高&#xff1b;但对SOC硬件资源占用率太高。 串行通信&#xff1a;一次通信传递1bit。降低对硬件占用率&#xff1b;但通信速率低。 2、单工通信&#xff1a;只能A发送&#xff0c;B接收。 半双工通信…

D. Minimize the Difference (Codeforces Round 973 Div. 2)

D. Minimize the Difference 思路&#xff1a; 发现操作是单向的从左往右取高补低&#xff0c;最终目标是尽可能趋于平均&#xff0c;使最大值最小和使最小值最大。可以用二分答案法分别找到两个最值&#xff0c;然后做差即可。 关于这种算法的正确性没有做严格的证明&#x…

国庆节适合买什么东西?精选五款实用又优惠的多功能好物!

临近国庆&#xff0c;我猜很多朋友已经开始为假期做好准备&#xff0c;计划开启出游和购物的节奏了&#xff01;大家都希望在国庆期间&#xff0c;买到一些平时因为价格太贵而舍不得下单的好物&#xff01;作为一名家居兼数码博主&#xff0c;每年国庆的时候我都会疯狂采购各种…

那年我双手插兜,使用IPv6+DDNS动态域名解析访问NAS

估计有很多科技宅和我一样&#xff0c;会买一个NAS存储或者自己折腾刷一下黑群晖玩玩&#xff0c;由于运营商不给分配固定的公网IP&#xff0c;就导致我在外出的时候无法访问家里的NAS&#xff0c;于是远程访问常常受到IP地址频繁变动的困扰。为了解决这一问题&#xff0c;结合…

对FPGA加载过程中不同寄存器初始化方式现象的分析

对FPGA加载过程中不同寄存器初始化方式现象的分析 概述目的术语和缩略语参考资料 相关原理分析MMCM时钟锁定分析声明信号时进行初始化RTL测试代码示波器现象 同步复位/置位初始化RTL测试代码示波器现象 异步复位/置位初始化RTL测试代码示波器现象 不进行任何初始化操作&#xf…

JSP 指令标识和脚本标识的使用

文章目录 前言一、JSP 页面是什么&#xff1f;二、JSP 基本语法 1.指令标识 &#xff08;1&#xff09;page 指令&#xff08;2&#xff09;include 指令&#xff08;3&#xff09;taglib 指令2.脚本标识总结 前言 在进行Java Web 应用开发的过程中&#xff0c;JSP 是必不可少的…

9.19工作笔记

怎么做多空对冲 脚本2 read_coin选币单币涨跌幅计算单币资金曲线单币资金曲线均值得到周期总体资金曲线周期总体资金曲线得到周期总体涨跌幅周期总体涨跌幅计算得到总体资金曲线 脚本2怎么实现多空对冲的 首先读取factors和periods中的文件&#xff0c;然后read_coin得到结…

Proteus-7.8sp2安装

一、D盘新建空文件夹&#xff0c;名为Proteus。 二、安装软件 1.双击P7.8sp2.exe 2.next 三、破解 1.双击 Proteus Pro 7.8 SP2破解 1.0.exe 2. 升级 打开软件 四、汉化 1.将如下两个文件复制到 D:\Proteus\BIN 路径中 重新打开软件&#xff0c;汉化成功

【在.net6和WPF框架下进行海康SDK开发】(一)如何引用Dll

最近有个上位机项目&#xff0c;需要将海康VisionMaster的部分功能嵌入到统一的界面。项目使用WPFdotNet6开发&#xff0c;UI库使用HandyControl。 先说下需求&#xff0c;在某个TabItem内嵌入一个UserControl&#xff0c;UserContr内嵌入VisionMaster运行界面。 本以为按照海康…

10年计算机考研408-计算机网络

【题33】下列选项中&#xff0c;不属于网络体系结构所描述的内容是&#xff08;&#xff09; A.网络的层次 B.每一层使用的协议 C.协议的内部实现细节 D.每一层必须完成的功能 解析&#xff1a; 本题考查的是网络体系结构相关的概念。 图1描述了网络的7层架构以及每一层所要完成…

智能新突破:AIOT 边缘计算网关让老旧水电表图像识别

数字化高速发展的时代&#xff0c;AIOT&#xff08;人工智能物联网&#xff09;技术正以惊人的速度改变着我们的生活和工作方式。而其中&#xff0c;AIOT 边缘计算网关凭借其强大的功能&#xff0c;成为了推动物联网发展的关键力量。 这款边缘计算网关拥有令人瞩目的 1T POS 算…

某文书网爬虫逆向

一、抓包分析 请求参数和响应数据都有加密 二、逆向分析 老方法、下xhr断点 加密实现逻辑都在这个方法里 执行到这的时候&#xff0c;在向下跟栈数据就已经渲染出来了&#xff0c;说明是在这个方法里进行的解密 解密方法&#xff0c;data.result为加密数据&#xff0c;data.s…

Plant Disease Expert:植物病害数据集(猫脸码客 第197期)

Plant Disease Expert 数据集详细介绍 一、引言 在农业生产的广阔领域中&#xff0c;植物病害始终是一个不可忽视的挑战。它不仅直接威胁到作物的健康生长&#xff0c;还可能导致严重的产量下降&#xff0c;进而影响全球粮食安全和农业经济稳定。据权威机构估计&#xff0c;全…

18 基于51单片机的心率体温监测报警系统(包括程序、仿真、原理图、流程图)

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 基于51单片机 ds18B20读取温度&#xff0c; 设置初始心率65 设置温度阈值38 心率阈值60 100 如果超过阈值&#xff0c;蜂鸣器报警&#xff0c;led灯亮 二、硬件资源 基于KEIL5编写C代码&#xf…

栈的操作:进栈,出栈,读栈顶元素

代码&#xff1a; #include<iostream> using namespace std; template<class T> class sq_Stack {private:int mm;int top;T *s;public:sq_Stack(int);void prt_sq_Stack();int flag_sq_Stack();void ins_sq_Stack(T);T del_sq_Stack();T read_sq_Stack(); }; tem…