记录一次Netty的WSS异常

news2024/11/15 11:57:33

概述

业务场景

应用通过 WSS 客户端连接三方接口。在高并发压测时,出现了请求服务器写入失败的异常,该异常是偶发,出现的概率不到千分之一,异常如下图所示。
在这里插入图片描述

问题概述

注意

  • 因为握手是通过 http 协议进行的。所以,需要挂载 http 编解码器。
  • 而在握手成功后。需要从 pipeline 中删除 http 编解码器,并挂载 WebSocket 编解码器。即从 http 协议升级为 WebSocket 协议。

向第三方接口请求时(channel.writeAndFlush()),抛出了 “unsupported message type” 异常。

该异常,是消息类型不正确导致的,由异常提示可知,要求消息类型是 ByteBufFileRegion

因 BUG 出现的概率极低,在服务中无法复现,只能通过查看源码和日志,分析原因。

整个握手的过程如下所示:

  1. 应用与第三方建立连接。
  2. 应用发送握手请求,在请求成功后,挂载 WebSocket 编码器。(有90%的可能是因为这一步的导致的异常)
  3. 第三方接口握手响应。
  4. 应用进行握手完成处理。
  5. 应用与第三方接口握手完成,可以进行正常首发报文。

握手完成后,执行的操作主要是:卸载http编解码器,挂载 WebSocket 解码器。

最终的分析结果:客户端应用在握手期间,虽然请求已经成功发送到第三方接口。但是由于未知原因,造成请求握手的 FutureListener 延迟执行,进而造成 WebSocket 编码器挂载失败。

环境

jdk1.8。

Netty 依赖。

        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.90.Final</version>
        </dependency>

常用的 WSS 通信代码

服务端代码

服务端的代码比较简单,用的都是 Netty 提供的编解码器。

自定义 Handler :收到报文后,响应给客户端。

public class WssServer {

    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workGroup = new NioEventLoopGroup(10);
        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(bossGroup, workGroup)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel channel) throws Exception {
                        ChannelPipeline pipeline = channel.pipeline();
                        // http编解码处理
                        pipeline.addLast("http-codec", new HttpServerCodec());
                        // http聚合处理
                        pipeline.addLast("http-aggregator", new HttpObjectAggregator(65536));
                        // webSocket协议处理器,其中包含了握手的处理逻辑
                        pipeline.addLast(new WebSocketServerProtocolHandler("/", null, false, 65536));
                        pipeline.addLast(new SimpleChannelInboundHandler<Object>() {
                            @Override
                            protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
                                if (msg instanceof TextWebSocketFrame) {
                                    String text = ((TextWebSocketFrame) msg).text();
                                    System.out.println("server received text: " + text);
                                    ctx.writeAndFlush(new TextWebSocketFrame("I received your msg: " + text));
                                }
                            }

                            @Override
                            public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
                                cause.printStackTrace();
                                ctx.close();
                            }
                        });
                    }
                });
        Channel channel = bootstrap.bind(8000).sync().channel();
        System.out.println("server started ... port: " + 8000);
        channel.closeFuture().sync();
    }
}

客户端代码

WSS 客户端类 WsSslClient

在客户端与服务端建立连接成功后,进行握手请求。握手成功后,才是真正的 connect 成功,即 WsSslClient.connect() 的逻辑。

握手逻辑说明:

  1. 握手逻辑发生在链路连接完成后。
  2. 调用 handshaker.handshake(channel) 发送握手请求
  3. ClientBizHandler 收到消息时,首先处理握手,并设置握手异步结果 (handshakeFinishPromise )
  4. 通过 handshakeFinishPromise 判断是否握手成功,进而可以判断是否真正的连接成功。
public class WsSslClient {
    private static final String URL = "wss://localhost:8000";

    private URI server;
    private Bootstrap bootstrap = new Bootstrap();
    /** Web握手类:用于握手处理 */
    private WebSocketClientHandshaker handshaker;
    public WsSslClient() throws Exception {
        server = new URI(URL);
        // 握手处理类
        handshaker = WebSocketClientHandshakerFactory
                .newHandshaker(server, WebSocketVersion.V13, null, true, new DefaultHttpHeaders());
        init();
    }

    public void init() {
        // 客户端线程组-10个线程
        EventLoopGroup group = new NioEventLoopGroup(10);
        bootstrap.option(ChannelOption.TCP_NODELAY, true)
                .group(group)
                .channel(NioSocketChannel.class)
                // 设置WebSocket相关处理器
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel channel) {
                        ChannelPipeline pipeline = channel.pipeline();
                        // http编解码处理
                        pipeline.addLast("http-codec", new HttpClientCodec());
                        // http聚合处理
                        pipeline.addLast("http-aggregator", new HttpObjectAggregator(65536));
                        // webSocket聚合处理
                        pipeline.addLast(new WebSocketFrameAggregator(65536));
                        // webSocket业务处理
                        pipeline.addLast("client-handler", new ClientBizHandler(handshaker));
                    }
                });
        System.out.println("client init success");
    }

    public Channel connect() throws InterruptedException {
        System.out.printf("begin connect to %s\n", URL);
        Channel channel = bootstrap.connect(server.getHost(), server.getPort()).sync().channel();
        System.out.printf("connected to %s\n", URL);

        // 发送握手
        System.out.printf("request handshake %s\n", URL);
        handshaker.handshake(channel);

        // 获取握手异步结果对象
        ClientBizHandler clientBizHandler = (ClientBizHandler)channel.pipeline().get("client-handler");
        ChannelPromise handshakeFinishPromise = clientBizHandler.getHandshakeFinishPromise();
        // 通过promise等待握手完成
        if (!handshakeFinishPromise.awaitUninterruptibly(2000)) {
            close(channel);
            throw new RuntimeException("handshake timeout");
        }
        if (!handshakeFinishPromise.isSuccess()) {
            throw new RuntimeException("handshake error");
        }

        System.out.printf("%s handshake finish, you can send msg now!\n", URL);
        return channel;
    }

    public void request(Channel channel, String msg) {
        System.out.println("request server, msg: " + msg);
        channel.writeAndFlush(new TextWebSocketFrame(msg)).addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                if (!future.isSuccess()) {
                    System.err.println("writeAndFlush fail: "+ future.cause().getMessage());
                }
            }
        });
    }

    public void close(Channel channel) {
        if (channel != null && channel.isActive()) {
            System.out.println("close");
            channel.close();
        }
    }
}

业务处理器类

收到第三方接口响应时,先进行握手处理,然后才处理实际业务。

public class ClientBizHandler extends SimpleChannelInboundHandler<Object> {
    private final WebSocketClientHandshaker handshaker;
    private ChannelPromise handshakeFinishPromise;

    public ClientBizHandler(WebSocketClientHandshaker handshaker) {
        this.handshaker = handshaker;
    }

    public ChannelPromise getHandshakeFinishPromise() {
        return handshakeFinishPromise;
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        System.out.println("handlerAdded");
        // 处理器被添加到实际的上下文时,创建一个异步结果对象,用于WsSslClient的连接函数
        handshakeFinishPromise = ctx.newPromise();
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        Channel channel = ctx.channel();
        // 握手未完成,则进行握手处理
        if (!handshaker.isHandshakeComplete()) {
            try {
                handshaker.finishHandshake(channel, (FullHttpResponse) msg);
                System.out.println("handshake finished");
                // 告知握手结果
                handshakeFinishPromise.setSuccess();
            } catch (Exception e) {
                // 异常也要告知
                System.err.println("handshake error: " + e.getMessage());
                handshakeFinishPromise.setFailure(e);
            }
            return;
        }
        if (msg instanceof TextWebSocketFrame) {
            TextWebSocketFrame textWebSocketFrame = (TextWebSocketFrame) msg;
            System.out.println("received server response: " + textWebSocketFrame.text());
            // 实际处理...
        }
    }
}

进行 WSS 连接和发送的请求的 demo

public class ClientTest {
    public static void main(String[] args) throws Exception {
        WsSslClient wsSslClient = new WsSslClient();
        Channel channel = wsSslClient.connect();
        wsSslClient.request(channel, "hello server, I'm client");
    }
}

正常发起测试

1.首先启动服务端,并输出日志。

server started ... port: 8000

2.运行 ClientTest ,请求服务端,日志输出如下。

客户端日志

client init success
begin connect to wss://localhost:8000
handlerAdded
connected to wss://localhost:8000
request handshake wss://localhost:8000
handshake finished
wss://localhost:8000 handshake finish, you can send msg now!
request server, msg: hello server, I'm client

服务端日志

server received text: hello server, I'm client

客户端日志

received server response: I received your msg: hello server, I'm client

异常分析

查看 handshaker.handshake(channel) 源码,可知其用来发送握手消息,并添加异步监听。

异步监听作用:在握手消息发送成功后,添加 WebSocket 编码器 WebSocketFrameEncoder这一步很关键,是造成异常的主要元凶。 因为是异步进行的监听,有可能会导致执行的延迟。

在这里插入图片描述

WebSocketFrameEncoder 编码器的功能,正是将 WebSocketFrame 类型的消息转化为 ByteBuf

我们可以推理一下,如果由于未知原因(如并发高、线程切换阻塞),导致握手消息发送成功,但是执行监听延迟

  • 也就是说 WebSocketFrameEncoder 还未挂载到 channel 的 pipeline 时,
  • 应用已经收到第三方的握手响应,完成握手响应逻辑处理,设置 handshakeFinishPromise 异步结果为成功。
  • WsSslClient.connect() 函数中阻塞等待 handshakeFinishPromise 放行,即连接函数执行成功。
  • 执行WsSslClient.request(),发生真实请求(此时 pipeline 上无 WebSocketFrameEncoder)。
  • 抛出 unsupported message type 异常。

重现异常

为了模拟 unsupported message type 异常,定义了一个 CustomWebSocketClientHandshaker13 ,用于替代原客户端代码中的 handshaker

我用的是 W13 版本,大家根据实际情况,使用其他版本。

CustomWebSocketClientHandshaker13 重写了发送握手请求方法 handshake(),握手请求监听处增加了延迟执行的逻辑。

public class CustomWebSocketClientHandshaker13 extends WebSocketClientHandshaker13 {
    public CustomWebSocketClientHandshaker13(URI webSocketURL, WebSocketVersion version, String subprotocol,
                                             boolean allowExtensions, HttpHeaders customHeaders,
                                             int maxFramePayloadLength) {
        super(webSocketURL, version, subprotocol, allowExtensions, customHeaders, maxFramePayloadLength);
    }

    /**
     * 重写该方法,主要是用于复现出现的问题
     * @param channel
     * @return
     */
    @Override
    public ChannelFuture handshake(Channel channel) {
        ChannelPromise promise = channel.newPromise();
        FullHttpRequest request = this.newHandshakeRequest();
        channel.writeAndFlush(request).addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws InterruptedException {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        // 写握手请求时,因未知原因,导致握手后编码器未挂载成功,
                        // 或者发送成功,但是因为未知原因,导致监听延迟
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                        if (future.isSuccess()) {
                            ChannelPipeline p = future.channel().pipeline();
                            ChannelHandlerContext ctx = p.context(HttpRequestEncoder.class);
                            if (ctx == null) {
                                ctx = p.context(HttpClientCodec.class);
                            }
                            if (ctx == null) {
                                promise.setFailure(new IllegalStateException("ChannelPipeline does not contain an HttpRequestEncoder or HttpClientCodec"));
                                return;
                            }
                            p.addAfter(ctx.name(), "ws-encoder", CustomWebSocketClientHandshaker13.this.newWebSocketEncoder());
                            promise.setSuccess();
                        } else {
                            promise.setFailure(future.cause());
                        }
                    }
                }).start();
            }
        });
        return promise;
    }
}

不要忘记 WsSslClient 中的 handshaker 喔~~。 它要换成我们自定义的异常类 CustomWebSocketClientHandshaker13 ,代码如下图所示。
在这里插入图片描述

handshaker = new CustomWebSocketClientHandshaker13(server, WebSocketVersion.V13, null, true,
                new DefaultHttpHeaders(), 65536);

执行 ClientTest 就会复现该异常。

在这里插入图片描述

修复异常

第一个修复点 - WsSslClient

connect() 函数中, handshaker.handshake(channel) 会返回一个 ChannelFuture 对象,用于告知握手请求的执行结果。

也就是握手请求监听函数真正执行的结果。

我们拿到这个 Future 对象后,传递给业务处理器 clientBizHandler

在这里插入图片描述

改造后的 WsSslClient 源码。

public class WsSslClient {
    private static final String URL = "wss://localhost:8000";

    private URI server;
    private Bootstrap bootstrap = new Bootstrap();
    /** Web握手类:用于握手处理 */
    private WebSocketClientHandshaker handshaker;
    public WsSslClient() throws Exception {
        server = new URI(URL);
        // 握手处理类
//        handshaker = WebSocketClientHandshakerFactory
//                .newHandshaker(server, WebSocketVersion.V13, null, true, new DefaultHttpHeaders());
        // 为复现问题,自己定义的握手类
        handshaker = new CustomWebSocketClientHandshaker13(server, WebSocketVersion.V13, null, true,
                new DefaultHttpHeaders(), 65536);
        init();
    }

    public void init() {
        // 客户端线程组-10个线程
        EventLoopGroup group = new NioEventLoopGroup(10);
        bootstrap.option(ChannelOption.TCP_NODELAY, true)
                .group(group)
                .channel(NioSocketChannel.class)
                // 设置WebSocket相关处理器
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel channel) {
                        ChannelPipeline pipeline = channel.pipeline();
                        // http编解码处理
                        pipeline.addLast("http-codec", new HttpClientCodec());
                        // http聚合处理
                        pipeline.addLast("http-aggregator", new HttpObjectAggregator(65536));
                        // webSocket聚合处理
                        pipeline.addLast(new WebSocketFrameAggregator(65536));
                        // webSocket业务处理
                        pipeline.addLast("client-handler", new ClientBizHandler(handshaker));
                    }
                });
        System.out.println("client init success");
    }

    public Channel connect() throws InterruptedException {
        System.out.printf("begin connect to %s\n", URL);
        Channel channel = bootstrap.connect(server.getHost(), server.getPort()).sync().channel();
        System.out.printf("connected to %s\n", URL);

        // 发送握手
        System.out.printf("request handshake %s\n", URL);
        ChannelFuture handshakeRequestFuture = handshaker.handshake(channel);
        // 获取握手异步结果对象
        ClientBizHandler clientBizHandler = (ClientBizHandler)channel.pipeline().get("client-handler");
        // 把握手异步结果,设置到clientBizHandler
        clientBizHandler.setHandshakeRequestFuture(handshakeRequestFuture);
        ChannelPromise handshakeFinishPromise = clientBizHandler.getHandshakeFinishPromise();
        // 通过promise等待握手完成
        if (!handshakeFinishPromise.awaitUninterruptibly(2000, TimeUnit.MILLISECONDS)) {
            close(channel);
            throw new RuntimeException("handshake timeout");
        }
        if (!handshakeFinishPromise.isSuccess()) {
            throw new RuntimeException("handshake error");
        }

        System.out.printf("%s handshake finish, you can send msg now!\n", URL);
        return channel;
    }

    public void request(Channel channel, String msg) {
        System.out.println("request server, msg: " + msg);
        channel.writeAndFlush(new TextWebSocketFrame(msg)).addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                if (!future.isSuccess()) {
                    System.err.println("writeAndFlush fail: "+ future.cause().getMessage());
                }
            }
        });
    }

    public void close(Channel channel) {
        if (channel != null && channel.isActive()) {
            System.out.println("close");
            channel.close();
        }
    }
}

第二个改造类 - ClientBizHandler

收到握手响应后,等待握手请求完成后,再进行握手 finish 处理(handshaker.finishHandshake(channel, (FullHttpResponse) msg))。

在这里插入图片描述

改造后的业务处理类源码。

public class ClientBizHandler extends SimpleChannelInboundHandler<Object> {
    private final WebSocketClientHandshaker handshaker;
    private ChannelPromise handshakeFinishPromise;
    private ChannelFuture handshakeRequestFuture;

    public ClientBizHandler(WebSocketClientHandshaker handshaker) {
        this.handshaker = handshaker;
    }

    public ChannelPromise getHandshakeFinishPromise() {
        return handshakeFinishPromise;
    }

    public void setHandshakeRequestFuture(ChannelFuture handshakeRequestFuture) {
        this.handshakeRequestFuture = handshakeRequestFuture;
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        System.out.println("handlerAdded");
        // 处理器被添加到实际的上下文时,创建一个异步结果对象,用于WsSslClient的连接函数
        handshakeFinishPromise = ctx.newPromise();
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        Channel channel = ctx.channel();
        // 握手未完成,则进行握手处理
        if (!handshaker.isHandshakeComplete()) {
            handshakeRequestFuture.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    if (!future.isSuccess()) {
                        throw new RuntimeException("handshake request fail");
                    }
                    try {
                        handshaker.finishHandshake(channel, (FullHttpResponse) msg);
                        System.out.println("handshake finished");
                        // 告知握手结果
                        handshakeFinishPromise.setSuccess();
                    } catch (Exception e) {
                        // 异常也要告知
                        System.err.println("handshake error: " + e.getMessage());
                        handshakeFinishPromise.setFailure(e);
                    }
                }
            });
            return;
        }
        if (msg instanceof TextWebSocketFrame) {
            TextWebSocketFrame textWebSocketFrame = (TextWebSocketFrame) msg;
            System.out.println("received server response: " + textWebSocketFrame.text());
            // 实际处理...
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
    }
}

验证BUG是否修复

后面就可以正常请求啦!!

输出的日志如下,可以发现,的确是等待请求处理成功后,才进行 finish 处理,并且报文也可以正常处理。
在这里插入图片描述

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

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

相关文章

在AndroidStudio创建虚拟手机DUB-AI20

1.DUB-AI20介绍 DUB-AL20是华为畅享9全网通机型。 华为畅享9采用基于Android 8.1定制的EMUI 8.2系统&#xff0c;最大的亮点是配置了1300万AI双摄、4000mAh大电池以及AI人脸识别功能&#xff0c;支持熄屏快拍、笑脸抓拍、声控拍照、手势拍照等特色的拍照功能&#xff0c;支持移…

1960-2022年世界银行WDI面板数据(1400+指标)

1960-2022年世界银行WDI面板数据&#xff08;1400指标&#xff09; 1、时间&#xff1a;1960-2022年 2、来源&#xff1a;世界银行WDI 指标&#xff1a;包括健康、公共部门、农业与农村发展、城市发展、基础设施、外债、性别、援助效率、教育、气候变化、环境、社会保护与劳…

阿里云百炼大模型使用

阿里云百炼大模型使用 由于阿里云百炼大模型有个新用户福利&#xff0c;有免费的4000000 tokens&#xff0c;我开通了相应的服务试试水。 使用 这里使用Android开发了一个简单的demo。 安装SDK implementation group: com.alibaba, name: dashscope-sdk-java, version: 2.…

【回忆版】数据科学思维与大数据智能分析 2024考试

填空&#xff08;18分&#xff09;18个 1.对数变换对大数值的范围进行压缩&#xff0c;对小数值的范围进行扩展 2.提取出大量高频率项与低频率项相关联的虚假模式&#xff0c;即交叉支持&#xff08;cross-support&#xff09;模式 3.信息论中&#xff08;&#xff09; 4.几种…

Python的pip配置、程序运行、生成exe文件

一、安装Python 通过官网下载对应的版本&#xff0c;安装即可。 下载地址&#xff1a;Download Python | Python.org Python标准库查看&#xff08;Python自带库&#xff09; Python 标准库文档 安装Python的时候&#xff0c;如果选第二个自定义安装要记得勾选安装pip 二、…

HTTP的由来以及发展史

HTML&HTML5的学习探索 01、Html的由来和发展史 01-01、Html的由来 HTML的英文全称是 Hypertext Marked Language&#xff0c;即超文本标记语言。HTML是由Web的发明者 Tim Berners-Lee&#xff08;蒂姆伯纳斯李&#xff09;于1990年创立的一种标记语言&#xff0c; 他是万…

怎么在Qt Designer设计的界面上显示Matplotlib的绘图?

首先&#xff0c;利用Qt Designer设计界面。 设计好后保存为ui文件。 接着&#xff0c;将ui文件转为py文件。 我喜欢在python中进行转换&#xff0c;因此把转换命令封装为函数&#xff0c;运行一下即可。 import os # pyuic5 -o output_file.py input_file.ui #通过命令把.ui…

网络模型-NQA与网络协议联动

一、NQA定义 网络质量分析NQA(Network QualityAnalysis)是一种实时的网络性能探测和统计技术&#xff0c;可以对响应时间、网络抖动、丢包率等网络信息进行统计。NOA能够实时监视网络0oS&#xff0c;在网络发生故障时进行有效的故障诊断和定位。 部署IPv4静态路由与BFD…

phonenumbers,一个强大的 Python 库!

更多Python学习内容&#xff1a;ipengtao.com 大家好&#xff0c;今天为大家分享一个强大的 Python 库 - phonenumbers。 Github地址&#xff1a;https://github.com/daviddrysdale/python-phonenumbers 在现代应用程序中&#xff0c;处理和验证电话号码是一项常见的需求。无论…

Pytorch深度学习实践笔记5(b站刘二大人)

&#x1f3ac;个人简介&#xff1a;一个全栈工程师的升级之路&#xff01; &#x1f4cb;个人专栏&#xff1a;pytorch深度学习 &#x1f380;CSDN主页 发狂的小花 &#x1f304;人生秘诀&#xff1a;学习的本质就是极致重复! 视频来自【b站刘二大人】 目录 1 Linear Regress…

Windows Subsystem for Linux (WSL)查看在线发行版并在终端安装

在 Windows Subsystem for Linux (WSL) 中&#xff0c;你可以使用以下命令来查看在线可用的 Linux 发行版&#xff1a; 列出可用的 Linux 发行版&#xff1a; 使用以下命令查看可以通过在线商店获取的 Linux 发行版列表&#xff1a; wsl --list --online或者&#xff0c;你也可…

一个月速刷leetcodeHOT100 day11 链表完全解析 以及链表5道easy题

链表 表是一种物理存储单元上非连续、非顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点&#xff08;链表中每一个元素称为结点&#xff09;组成&#xff0c;结点可以在运行时动态生成。每个结点包活两个部分&#xff1a;一…

二十九篇:构建未来:信息系统的核心框架与应用

构建未来&#xff1a;信息系统的核心框架与应用 1. 引言 在这个充满挑战和机遇的信息时代&#xff0c;信息系统已经成为现代组织不可或缺的神经中枢。它们不仅革新了我们处理信息的方式&#xff0c;更是极大地增强了决策制定的效率和质量。在这篇文章中&#xff0c;我将分享我…

基于PID的单片机温度控制系统设计

基于PID的温度控制系统设计 摘要 温度是工业上最基本的参数&#xff0c;与人们的生活紧密相关&#xff0c;实时测量温度在工业生产中越来越受到重视&#xff0c;离不开温度测量所带来的好处&#xff0c;因此研究控制和测量温度具有及其重要的意义。 本设计介绍了以AT89C52单片…

kubenetes中K8S的命名空间状态异常强制删除Terminating的ns

查看ns状态为异常&#xff1a; 查看ns为monitoring的状态为Termingating状态 使用方法一&#xff1a; kubectl delete ns monitoring --force --grace-period0 使用方法二&#xff1a; kubectl get ns monitoring -o json > monitoring.json 修改删除文件中的"kubern…

Docker快速搭建Oracle服务

服务器&#xff1a;CentOS7.9 1.安装docker yum install -y docker 2. 设置镜像加速 修改 /etc/docker/daemon.json 文件并添加上 registry-mirrors 键值 阿里云的docker镜像需要自己注册账号&#xff0c;也可以不注册账号&#xff0c;直接使用下面的连接。 也可以写入多…

深度学习中的多GPU训练(Pytorch 20)

一 多GPU训练 下面详细介绍如何从零开始并行地训练网络&#xff0c;这里需要运用小批量随机梯度下降算法。后面我还讲介绍如何使用高级API并行训练网络。 我们从一个简单的计算机视觉问题和一个稍稍过时的网络开始。这个网络有多个卷积层和汇聚层&#xff0c;最后可能 有几个…

AGI系列(1):掌握AI大模型提示词优化术,提问准确率飙升秘籍

当我们向AI大模型提问时&#xff0c;通常人们的做法是有什么问题&#xff0c;就直接去问&#xff0c;得到大模型的回复结果&#xff0c;时好时坏&#xff0c;完全没有可控性。 那么有没有一种方式或是一套方法&#xff0c;可以让我们向大模型提问时&#xff0c;得到的结果更准确…

怎么在网上赚点零花钱?分享十个正规的赚钱兼职平台

亲爱的朋友们&#xff0c;大家好&#xff01;今天要和大家聊聊一个让人兴奋的话题——网上赚钱。在这个互联网飞速发展的时代&#xff0c;网上赚钱已经不再是遥不可及的梦想。如果你正想在网上赚点零花钱&#xff0c;那么这篇文章你可不能错过&#xff01; 在这个信息爆炸的时代…

Linux系统命令traceroute详解(语法、选项、原理和实例)

目录 一、traceroute概述 二、语法 1、基本语法 2、命令选项 三、帮助信息 四、示例 1. 使用默认模式&#xff08;ICMP Echo&#xff09;追踪到目标主机 2. 使用UDP模式&#xff08;需要root权限&#xff09;追踪到目标主机 3. 不解析IP地址为主机名&#xff0c;直接显…