MQTT(EMQX) - SpringBoot 整合MQTT 连接池 Demo - 附源代码 + 在线客服聊天架构图

MQTT 概述

MQTT (Message Queue Telemetry Transport) 是一个轻量级传输协议,它被设计用于轻量级的发布/订阅式消息传输,MQTT协议针对低带宽网络,低计算能力的设备,做了特殊的优化。是一种简单、稳定、开放、轻量级易于实现的消息协议,在物联网的应用下的信息采集,工业控制,智能家居等方面具有广泛的适用性。

image

  1. MQTT更加简单:MQTT是一种消息队列协议,使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合,相对于其他协议,开发更简单;
  2. MQTT网络更加稳定:工作在TCP/IP协议上;由TCP/IP协议提供稳定的网络连接;
  3. 轻量级:小型传输,开销很小(固定长度的头部是 2 字节),协议交换最小化,以降低网络流量;适合低带宽,数据量较小的应用;

MQTT支持三种消息发布服务质量(QoS):

  • “至多一次”(QoS==0):消息发布完全依赖底层 TCP/IP 网络。会发生消息丢失或重复。这一级别可用于如下情况,环境传感器数据,丢失一次读记录无所谓,因为不久后还会有第二次发送。
  • “至少一次”(QoS==1):确保消息到达,但消息重复可能会发生。
  • “只有一次”(QoS==2):确保消息到达一次。这一级别可用于如下情况,在计费系统中,消息重复或丢失会导致不正确的结果。小型传输,开销很小(固定长度的头部是 2 字节),协议交换最小化,以降低网络流量。

MQTT 三种身份

  • 发布者、代理、订阅者,发布者和订阅者都为客户端,代理为服务器,同时消息的发布者也可以是订阅者(为了节约内存和流量发布者和订阅者一般都会定义在一起)。
  • MQTT传输的消息分为主题(Topic,可理解为消息的类型,订阅者订阅后,就会收到该主题的消息内容(payload))和负载(payload,可以理解为消息的内容)两部分。

    image

image

MQTT和Websocket的区别是什么?

MQTT是为了物联网场景设计的基于TCP的Pub/Sub协议,有许多为物联网优化的特性,比如适应不同网络的QoS、层级主题、遗言等等。

WebSocket是为了HTML5应用方便与服务器双向通讯而设计的协议,HTTP握手然后转TCP协议,用于取代之前的Server Push、Comet、长轮询等老旧实现。
两者之所有有交集,是因为一个应用场景:如何通过HTML5应用来作为MQTT的客户端,以便接受设备消息或者向设备发送信息,那么MQTT over WebSocket自然成了最合理的途径了。

语言支持

Java、C#、Python、C/C++、Objective-C、Node.js、Javascript、Ruby、Golang、PHP

应用场景

遥感数据、汽车、智能家居、智慧城市、医疗医护
即时通讯:MQ 可以通过订阅主题,轻松实现 1对1、1对多的通讯。

image

连接

  1. 登录IM服务
  2. 获取MQTT 服务器地址
  3. 建立MQTT连接

通讯

1、4. 发送者 向IM 服务发送消息
2、5. IM 服务,将消息持久化,并发给 MQTT
3、6. 消费者 从MQTT订阅到消息

本文 Demo 介绍

主要用到 InitializingBean、BasePooledObjectFactory、GenericObjectPool、GenericObjectPoolConfig
InitializingBean:实例化工厂、连接池,参考:Java SpringBoot Bean InitializingBean
GenericObjectPool:获取连接对象,如果池中没有,通过工厂创建 参考:Java GenericObjectPool 对象池化技术--SpringBoot sftp 连接池工具类
BasePooledObjectFactory::创建 MqttClient 连接 参考:Java BasePooledObjectFactory 对象池化技术
GenericObjectPoolConfig:GenericObjectPoolConfig是封装GenericObject池配置的简单“结构”,此类不是线程安全的;它仅用于提供创建池时使用的属性。大多数情况,可以使用GenericObjectPoolConfig提供的默认参数就可以满足日常的需求。
 

image

image

对象获取流程图

image

username(用户名)和password(密码)。这里的用户名和密码是用于客户端连接服务端时进行认证需要的。

有些MQTT服务端需要客户端在连接时提供用户名和密码。只有客户端正确提供了用户名和密码后,才能连接服务端。否则服务端将会拒绝客户端连接,那么客户端也就无法发布和订阅消息了。 当然,那些没有开启用户密码认证的服务端无需客户端提供用户名和密码认证信息。

Deom代码

image

POM

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>vipsoft-parent</artifactId>
        <groupId>com.vipsoft.boot</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>vipsoft-mqtt</artifactId>
    <version>1.0-SNAPSHOT</version>


    <dependencies>


        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.7.0</version>
        </dependency>

        <dependency>
            <groupId>org.eclipse.paho</groupId>
            <artifactId>org.eclipse.paho.client.mqttv3</artifactId>
            <version>1.2.5</version>
        </dependency>
 
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.3.6</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

Resource

application.yml

server:
  port: 8088
  application:
    name: MQTT Demo

mqtt:
  host: tcp://172.16.0.88:1883
  clientId: VipSoft_MQTT
  poolConfig:
    customSet: false
    minIdle: 8
    maxIdle: 20
    maxTotal: 20
    lifo: false

Config

MqttConfig
用户名和密码除了有以上功能外,有些公用MQTT服务端也利用此信息来识别客户端属于哪一个用户,从而对客户端进行管理。比如用户可以拥有私人主题,这些主题只有该用户可以发布和订阅。对于私人主题,服务端就可以利用客户端连接时的用户名和密码来判断该客户端是否有发布订阅该用户私人主题的权限。

package com.vipsoft.mqtt.config;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "mqtt")
public class MqttConfig {
    /**
     * MQTT host 地址
     */
    private String host;

    /**
     * 客户端Id
     */
    private String clientId;

    /**
     * 登录用户(可选)
     */
    private String userName;

    /**
     * 登录密码(可选)
     */
    private String password;
 
    /**
     * Mqtt Pool Config
     */
    private MqttPoolConfig poolConfig;

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public String getClientId() {
        return clientId;
    }

    public void setClientId(String clientId) {
        this.clientId = clientId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public MqttPoolConfig getPoolConfig() {
        return poolConfig;
    }

    public void setPoolConfig(MqttPoolConfig poolConfig) {
        this.poolConfig = poolConfig;
    }

}

MqttPoolConfig

package com.vipsoft.mqtt.config;

public class MqttPoolConfig {

    /**
     * 是否启用自定义配置
     */
    private boolean customSet;
    /**
     * 最小的空闲连接数
     */
    private int minIdle;
    /**
     * 最大的空闲连接数
     */
    private int maxIdle;
    /**
     * 最大连接数
     */
    private int maxTotal;

    public boolean isCustomSet() {
        return customSet;
    }

    public void setCustomSet(boolean customSet) {
        this.customSet = customSet;
    }

    public int getMinIdle() {
        return minIdle;
    }

    public void setMinIdle(int minIdle) {
        this.minIdle = minIdle;
    }

    public int getMaxIdle() {
        return maxIdle;
    }

    public void setMaxIdle(int maxIdle) {
        this.maxIdle = maxIdle;
    }

    public int getMaxTotal() {
        return maxTotal;
    }

    public void setMaxTotal(int maxTotal) {
        this.maxTotal = maxTotal;
    }
}

Pool

MqttClientManager

package com.vipsoft.mqtt.pool;

import cn.hutool.core.util.StrUtil;
import com.vipsoft.mqtt.config.MqttConfig;
import com.vipsoft.mqtt.config.MqttPoolConfig;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Service;


/**
 * 对类的创建之前进行初始化的操作,在afterPropertiesSet()中完成。
 */
@Service
public class MqttClientManager implements InitializingBean {
    private static Logger logger = LoggerFactory.getLogger(MqttClientManager.class);

    /**
     * mqtt连接配置
     */
    private final MqttConfig mqttConfig;

    private MqttConnectionPool<MqttConnection> mqttPool;

    public MqttClientManager(MqttConfig mqttConfig) {
        this.mqttConfig = mqttConfig;
    }

    /**
     * 创建连接池
     */
    @Override
    public void afterPropertiesSet() {
        try {
            // 连接池配置
            GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
            this.initPoolConfig(poolConfig);

            // mqtt连接配置
            MqttConnectOptions connOpts = new MqttConnectOptions();
            connOpts.setUserName(this.mqttConfig.getUserName());
            if (StrUtil.isNotEmpty(mqttConfig.getPassword())) {
                connOpts.setPassword(this.mqttConfig.getPassword().toCharArray());
            }

            // 创建工厂对象
            MqttConnectionFactory connectionFactory = new MqttConnectionFactory(mqttConfig.getHost(), connOpts);

            // 创建连接池
            mqttPool = new MqttConnectionPool<>(connectionFactory, poolConfig);

        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
    }

    private void initPoolConfig(GenericObjectPoolConfig poolConfig) {

        MqttPoolConfig mqttConnectionPoolConfig = this.mqttConfig.getPoolConfig();

        if (mqttConnectionPoolConfig.isCustomSet()) {

            // 设置连接池配置信息
            poolConfig.setMinIdle(mqttConnectionPoolConfig.getMinIdle());
            poolConfig.setMaxIdle(mqttConnectionPoolConfig.getMaxIdle());
            poolConfig.setMaxTotal(mqttConnectionPoolConfig.getMaxTotal());
            // TODO 补全

        }
    }

    /**
     * 根据key找到对应连接
     */
    public MqttConnection getConnection() throws Exception {
        return this.mqttPool.borrowObject();
    }

}

MqttConnection

package com.vipsoft.mqtt.pool;

import org.apache.commons.pool2.impl.GenericObjectPool;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MqttConnection {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    private MqttClient mqttClient;

    public MqttConnection(MqttClient mqttClient) {
        this.mqttClient = mqttClient;
    }

    /**
     * 隶属于的连接池
     */
    private GenericObjectPool<MqttConnection> belongedPool;


    /**
     * 推送方法消息
     */
    public void publish(String topic, String message) throws Exception {
        MqttMessage mqttMessage = new MqttMessage();
        mqttMessage.setPayload(message.getBytes());
        mqttClient.publish(topic, mqttMessage);
        System.out.println("对象:" + mqttClient + " " + "发送消息:" + message);
    }


    /**
     * 销毁连接
     */
    public void destroy() {
        try {
            if (this.mqttClient.isConnected()) {
                this.mqttClient.disconnect();
            }
            this.mqttClient.close();
        } catch (Exception e) {
            logger.error("MqttConnection destroy ERROR ; errorMsg={}", e.getMessage(), e, e);
        }
    }

    /**
     * 换回连接池
     */
    public void close() {
        if (belongedPool != null) {
            this.belongedPool.returnObject(this);
        }
    }


    public MqttClient getMqttClient() {
        return mqttClient;
    }

    public void setMqttClient(MqttClient mqttClient) {
        this.mqttClient = mqttClient;
    }

    public GenericObjectPool<MqttConnection> getBelongedPool() {
        return belongedPool;
    }

    public void setBelongedPool(GenericObjectPool<MqttConnection> belongedPool) {
        this.belongedPool = belongedPool;
    }
}

MqttConnectionFactory

package com.vipsoft.mqtt.pool;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.system.HostInfo;
import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.atomic.AtomicInteger;

public class MqttConnectionFactory extends BasePooledObjectFactory<MqttConnection> {

    private static final Logger logger = LoggerFactory.getLogger(MqttConnectionFactory.class);


    // AtomicInteger是一个提供原子操作的Integer类,通过线程安全的方式操作加减
    private AtomicInteger counter = new AtomicInteger();

    /**
     * 连接地址
     */
    private String serverURI;
    /**
     * 当前服务IP
     */
    private String localHostIP;


    /**
     * mqtt连接配置
     */
    private MqttConnectOptions mqttConnectConfig;


    /**
     * 根据mqtt连接 配置创建工厂
     */
    public MqttConnectionFactory(String serverURI, MqttConnectOptions mqttConnectConfig) {
        this.serverURI = serverURI;
        this.mqttConnectConfig = mqttConnectConfig;
    }

    /**
     * 在对象池中创建对象
     *
     * @return
     * @throws Exception
     */
    @Override
    public MqttConnection create() throws Exception {
        // 实现线程安全避免在高并发的场景下出现clientId重复导致无法创建连接的情况
        int count = this.counter.addAndGet(1);

        // 根据ip+编号,生成唯一clientId
        String clientId = this.getLosthostIp() + "_" + DateUtil.thisMillsecond();

        // 创建MQTT连接对象
        MqttClient mqttClient = new MqttClient(serverURI, clientId);

        // 建立连接
        mqttClient.connect(mqttConnectConfig);

        // 构建mqttConnection对象
        MqttConnection mqttConnection = new MqttConnection(mqttClient);
        logger.info("在对象池中创建对象 {}", clientId);
        return mqttConnection;
    }

    /**
     * common-pool2 中创建了 DefaultPooledObject 对象对对象池中对象进行的包装。
     * 将我们自定义的对象放置到这个包装中,工具会统计对象的状态、创建时间、更新时间、返回时间、出借时间、使用时间等等信息进行统计
     *
     * @param mqttConnection
     * @return
     */
    @Override
    public PooledObject<MqttConnection> wrap(MqttConnection mqttConnection) {
        logger.info("封装默认返回类型 {}", mqttConnection.toString());
        return new DefaultPooledObject<>(mqttConnection);
    }

    /**
     * 销毁对象
     *
     * @param p 对象池
     * @throws Exception 异常
     */
    @Override
    public void destroyObject(PooledObject<MqttConnection> p) throws Exception {
        if (p == null) {
            return;
        }
        MqttConnection mqttConnection = p.getObject();
        logger.info("销毁对象 {}", p.getObject().getMqttClient());
        mqttConnection.destroy();
    }

    /**
     * 校验对象是否可用
     *
     * @param p 对象池
     * @return 对象是否可用结果,boolean
     */
    @Override
    public boolean validateObject(PooledObject<MqttConnection> p) {
        MqttConnection mqttConnection = p.getObject();
        boolean result = mqttConnection.getMqttClient().isConnected();
        logger.debug("validateObject serverURI {},client_id {},result {}", mqttConnection.getMqttClient().getServerURI(),
                mqttConnection.getMqttClient().getClientId(), result);
        return result;
    }

    /**
     * 激活钝化的对象系列操作
     *
     * @param p 对象池
     * @throws Exception 异常信息
     */
    @Override
    public void activateObject(PooledObject<MqttConnection> p) throws Exception {
        logger.info("激活钝化的对象 {}", p.getObject().getMqttClient());
    }

    /**
     * 钝化未使用的对象
     *
     * @param p 对象池
     * @throws Exception 异常信息
     */
    @Override
    public void passivateObject(PooledObject<MqttConnection> p) throws Exception {
        logger.info("钝化未使用的对象 {}", p.getObject().getMqttClient());
    }


    /**
     * 获取当前服务真实IP
     */
    private String getLosthostIp() {
        if (StrUtil.isNotBlank(this.localHostIP)) {
            return this.localHostIP;
        }
        HostInfo hostInfo = new HostInfo();
        this.localHostIP = hostInfo.getAddress();
        return this.localHostIP;
    }

}

MqttConnectionPool

package com.vipsoft.mqtt.pool;

import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;

public class MqttConnectionPool<T> extends GenericObjectPool<MqttConnection> {

    public MqttConnectionPool(MqttConnectionFactory factory, GenericObjectPoolConfig config) {
        super(factory, config);
    }

    /**
     * 从对象池获得一个对象
     */
    @Override
    public MqttConnection borrowObject() throws Exception {
        MqttConnection conn = super.borrowObject();
        // 设置所属连接池
        if (conn.getBelongedPool() == null) {
            conn.setBelongedPool(this);
        }
        return conn;
    }

    /**
     * 归还一个连接对象
     * @param conn
     */
    @Override
    public void returnObject(MqttConnection conn) {
        if (conn!=null) {
            super.returnObject(conn);
        }
    }
} 

utils

MqttClientManager

package com.vipsoft.mqtt.utils;

import com.vipsoft.mqtt.pool.MqttClientManager;
import com.vipsoft.mqtt.pool.MqttConnection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


@Service
public class MqttUtil {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    MqttClientManager mqttManager;

    public void publish(String clientId, String message) {
        logger.info("publish INFO ; clientId={}, message={}", clientId, message);
        MqttConnection connection = null;
        try {
            connection = mqttManager.getConnection();
            logger.info("publish INFO ; clientId={},targetUrl={}", clientId, connection.getMqttClient().getServerURI());
            connection.publish(clientId, message);
        } catch (Exception e) {
            logger.error("publish ERROR ; clientId={},message={}", clientId, message, e, e);
        } finally {
            if (null != connection) {
                connection.close();
            }
        }
    }


}

test

PushCallback

package com.vipsoft.mqtt;

import cn.hutool.core.date.DateUtil;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class PushCallback implements MqttCallback {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public void connectionLost(Throwable cause) {
        // 连接丢失后进行重连
        System.out.println("连接断开,可以做重连");
        logger.info("掉线时间:{}", DateUtil.now());
    }

    @Override
    public void deliveryComplete(IMqttDeliveryToken token) {
        System.out.println("deliveryComplete---------" + token.isComplete());
    }

    @Override
    public void messageArrived(String topic, MqttMessage message) throws Exception {
        System.out.println("接收消息主题 : " + topic);
        System.out.println("接收消息Qos : " + message.getQos());
        System.out.println("接收消息内容 : " + new String(message.getPayload()));
    }
}

MqttProducerTest

package com.vipsoft.mqtt;

import cn.hutool.core.date.DateUtil;
import com.vipsoft.mqtt.utils.MqttUtil;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.concurrent.CountDownLatch;

@SpringBootTest
public class MqttProducerTest {

    @Autowired
    MqttUtil mqttUtil;

    @Test
    void pushMessateTest() throws Exception {
        for (int i = 0; i < 50; i++) {
            String topic = "VipSoft_MQTT";
            mqttUtil.publish(topic, "发送消息:" + DateUtil.now());
            Thread.sleep(3000);
        }
        new CountDownLatch(1).await();
    }
}

MqttConsumerTest

package com.vipsoft.mqtt;
 
import com.vipsoft.mqtt.pool.MqttClientManager;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;


@SpringBootTest
public class MqttConsumerTest {


    @Autowired
    MqttClientManager mqttManager;

    @Test
    void subscribeTest() throws Exception {
        String topic = "VipSoft_MQTT";
        MqttClient mqttClient = mqttManager.getConnection().getMqttClient();
        //这里的setCallback需要新建一个Callback类并实现 MqttCallback 这个类
        mqttClient.setCallback(new PushCallback());
        while (true) {
            mqttClient.subscribe(topic);
            Thread.sleep(1000);
        }
    }
}

运行方式
  1. MqttConsumerTest.subscribeTest()
  2. MqttProducerTest.pushMessateTest() 

image

 

image

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

APP开发中的性能优化:提升用户满意度的关键

APP开发中的性能优化是需要持续进行的&#xff0c;它不仅能够让用户体验到 APP的使用感受&#xff0c;还能在一定程度上提升用户的满意度&#xff0c;从而提升 APP的粘性和转化率。不过在实际开发中&#xff0c;很多 APP开发公司会存在性能优化上的问题&#xff0c;这就需要了解…

[C++项目] Boost文档 站内搜索引擎(3): 建立文档及其关键字的正排 倒排索引、jieba库的安装与使用...

之前的两篇文章: 第一篇文章介绍了本项目的背景, 获取了Boost库文档 &#x1fae6;[C项目] Boost文档 站内搜索引擎(1): 项目背景介绍、相关技术栈、相关概念介绍…第二篇文章 分析实现了parser模块. 此模块的作用是 对所有文档html文件, 进行清理并汇总 &#x1fae6;[C项目] …

【力扣每日一题】2023.8.4 不同路径3

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 在二维网格之上&#xff0c;让我们模拟从开头走到末尾&#xff0c;并且要经过所有能经过的点&#xff0c;问我们有多少种走法。 看到这道…

c++学习(异常)[28]

c语言处理错误机制 c异常概念 try {//保护的标识代码 }catch(ExceptionName e1) {//catch块 }catch(ExceptionName e2) {//catch块 }catch(ExceptionName eN) {//catch块 }匹配 优先调用链中最近的捕获 异常若不被捕获则报错终止程序 try { }catch ( ... ) //可以捕获任意类…

TCP的三次握手和四次挥手······详解

1、三次握手 三次握手是建立连接的过程 如图大致为三次握手的流程图&#xff1a; 当客户端对服务端发起连接时&#xff0c;会先发一个包连接请求数据&#xff0c;去询问能否建立连接&#xff0c;该数据包称为 “SYN”包 然后&#xff0c;如果对方同意连接&#xff0c;那么…

RabbitMQ:概念和安装,简单模式,工作,发布确认,交换机,死信队列,延迟队列,发布确认高级,其它知识,集群

1. 消息队列 1.0 课程介绍 1.1.MQ 的相关概念 1.1.1.什么是MQ MQ(message queue&#xff1a;消息队列)&#xff0c;从字面意思上看&#xff0c;本质是个队列&#xff0c;FIFO 先入先出&#xff0c;只不过队列中存放的内容是message 而已&#xff0c;还是一种跨进程的通信机制…

2024年杭州电子科技大学MEM项目招生信息全面了解

2024年全国管理类硕士联考备考已经到了最火热的阶段&#xff0c;不少考生开始持续将注意力集中在备考的规划中&#xff01;杭州达立易考教育整合浙江省内的MEM目信息&#xff0c;为大家详细梳理了相关报考参考内容&#xff0c;方便大家更好完成择校以及针对性的备考工作。本期为…

史上最全docker启动命令

docker Docker 启动镜像 一、查看当前docker中下载的镜像&#xff0c;如下图&#xff0c;当前我的Docker容器中存在两个镜像 &#xff0c;tomcat、mysql 二、启动镜像 (因启动命令参数过多&#xff0c;同时各种镜像启动时可以增加额外的参数&#xff0c;本次以启动mysql5.6为例…

(12)理解委托,反射,Type,EvenInfo,插件, 组合枚举,BindingFlags,扩展方法及重载,XML认识

一、复习委托事件 1、委托复习。 private delegate int MyDelegate(int a, int b); //1.定义委托类型private static void Main(string[] args){MyDelegate md new MyDelegate(AddDelegate);//2.声明委托变量int result md(1, 2);//3.调用委托Console.WriteLine(result);Cons…

Vue中默认插槽,具名插槽,作用域插槽区别详解

默认插槽&#xff1a; App.vue : 在app.vue中使用MyCategory&#xff0c;里面包裹的结构是不显示的&#xff0c;要想在页面中显示&#xff0c;就需要用到插槽。在子组件MyCategory中定义 <template><div class"container"><MyCategory title"美…

【Opencv入门到项目实战】(二):图像阈值与平滑处理

文章目录 1.图像阈值处理1.1简单阈值处理&#xff08;Simple Thresholding&#xff09;1.2自适应阈值处理&#xff08;Adaptive Thresholding&#xff09;1.3Otsus阈值处理 2.平滑处理1.1均值滤波&#xff08;Mean Filter&#xff09;1.2高斯滤波&#xff08;Gaussian Filter&a…

程序员自由创业周记#5:加一上线

程序员自由创业周记#5&#xff1a;加一上线 这是一位程序员进行独立开发创业的记录&#xff0c;将分享创业过程中的所思所想以及收支明细。 充实 如果说程序员独立创业的成功率只有5%&#xff0c;那如果家里有一位3岁多还没上幼儿园的小朋友要照顾&#xff0c;成功的概率至少还…

rv1109/1126 rknn 模型部署过程

rv1109/1126是瑞芯微出的嵌入式AI芯片&#xff0c;带有npu, 可以用于嵌入式人工智能应用。算法工程师训练出的算法要部署到芯片上&#xff0c;需要经过模型转换和量化&#xff0c;下面记录一下整个过程。 量化环境 模型量化需要安装rk的工具包&#xff1a; rockchip-linux/rk…

【Spring】(一)Spring设计核心思想

文章目录 一、初识 Spring1.1 什么是 Spring1.2 什么是 容器1.3 什么是 IoC 二、对 IoC 的深入理解2.1 传统程序开发方式存在的问题2.2 控制反转式程序的开发2.3 对比总结 三、对 Spring IoC 的理解四、DI 的概念4.1 什么是 DI4.2 DI 与 IoC的关系 一、初识 Spring 1.1 什么是…

flutter:Future、Stream、RxDart

Future 在Flutter中&#xff0c;Future是Dart语言中的一个类&#xff0c;用于表示异步操作的结果。与Future相关的的重要关键字包括async和await。 async&#xff1a;这个关键字用于在方法或函数声明前添加&#xff0c;以指示该方法为异步方法。在异步方法中&#xff0c;执行…

c++画出分割图像,水平线和垂直线

1、pca 找到图像某个区域的垂直线&#xff0c;并画出来 // 1、 斑块的框 血管二值化图&#xff0c;pca 找到垂直血管壁的直线, 还是根据斑块找主轴方向吧// Step 1: 提取斑块左右范围内的血管像素点坐标&#xff0c;std::vector<cv::Point> points;for (int y 0; y <…

用Apache Echarts展示数据

目录 1.后端代码 1.1 实体类&#xff1a; 1.2 SQL语句&#xff1a; 2.前端代码 2.1 安装 Apach Echarts安装包&#xff1a; 2.2 查找数据并赋值给Echarts 思路&#xff1a;后端查到数据&#xff0c;包装为map&#xff0c;map里有日期和每日就诊人数&#xff0c;返回给前端…

异或运算详解

异或运算详解 定义特性用途总结 定义 参与运算的两个数据,按二进制位进行 ^ 运算,如果两个相对应为值相同结果为0,否则为1 1 ^ 0 1 0 ^ 1 1 0 ^ 0 0 1 ^ 1 0特性 异或^运算只能用于数值(整数) x ^ 0 x x ^ x 0用途 两个值交换,而不用使用临时变量 a a ^ b; b b ^…

css在线代码生成器

这里收集了许多有意思的css效果在线代码生成器适合每一位前端开发者 布局&#xff0c;效果类&#xff1a; 网格生成器https://cssgrid-generator.netlify.app/ CSS Grid Generator可帮助开发人员使用CSS Grid创建复杂的网格布局。网格布局是创建Web页面的灵活和响应式设计的强…

【Linux】在服务器上创建Crontab(定时任务),自动执行shell脚本

业务场景&#xff1a;该文即为上次编写shell脚本的姊妹篇,在上文基础上,将可执行的脚本通过linux的定时任务自动执行,节省人力物力,话不多说,开始操作! 一、打开我们的服务器连接工具 连上服务器后,在任意位置都可以执行:crontab -e 如果没有进入编辑cron任务模式 根据提示查看…