EMQ基础功能-程序员宅基地

技术标签: 消息队列  分布式消息队列  EMQX  

认证

认证简介

身份认证是大多数应用的重要组成部分,MQTT 协议支持用户名密码认证,启用身份认证能有效阻止非法客户端的连接。
EMQ X 中的认证指的是当一个客户端连接到 EMQ X 的时候,通过服务器端的配置来控制客户端连接服务器的权限。
EMQ X 的认证支持包括两个层面

  • MQTT 协议本身在 CONNECT 报文中指定用户名和密码,EMQ X 以插件形式支持基于 Username、ClientID、HTTP、JWT、LDAP 及各类数据库如 MongoDB、MySQL、PostgreSQL、Redis 等多种形式的认证。
  • 在传输层上,TLS 可以保证使用客户端证书的客户端到服务器的身份验证,并确保服务器向客户端验证服务器证书。也支持基于 PSK 的 TLS/DTLS 认证。

认证方式

EMQ X 支持使用内置数据源(文件、内置数据库)、JWT、外部主流数据库和自定义 HTTP API 作为身份认证数据源。
连接数据源、进行认证逻辑通过插件实现的,每个插件对应一种认证方式,使用前需要启用相应的插件
客户端连接时插件通过检查其 username/clientid 和 password 是否与指定数据源的信息一致来实现对客户端的身份认证。

EMQ X 支持的认证方式:

内置数据源

Username 认证
Cliend ID 认证
使用配置文件与 EMQ X 内置数据库提供认证数据源,通过 HTTP API 进行管理,足够简单轻量。

外部数据库

LDAP 认证
MySQL 认证
PostgreSQL 认证
Redis 认证
MongoDB 认证
外部数据库可以存储大量数据,同时方便与外部设备管理系统集成。

其他

HTTP 认证
JWT 认证
JWT 认证可以批量签发认证信息,HTTP 认证能够实现复杂的认证鉴权逻辑。
更改插件配置后需要重启插件才能生效,部分认证鉴权插件包含 ACL 功能

认证结果

任何一种认证方式最终都会返回一个结果:

  • 认证成功:经过比对客户端认证成功
  • 认证失败:经过比对客户端认证失败,数据源中密码与当前密码不一致
  • 忽略认证(ignore):当前认证方式中未查找到认证数据,无法显式判断结果是成功还是失败,交由认证链下一认证方式或匿名认证来判断

匿名认证

EMQ X 默认配置中启用了匿名认证,任何客户端都能接入 EMQ X。没有启用认证插件或认证插件没有显式允许/拒绝(ignore)连接请求时,EMQ X 将根据匿名认证启用情况决定是否允许客户端连接。
配置匿名认证开关:

# etc/emqx.conf 
## Value: true | false 
allow_anonymous = true

生产环境中请禁用匿名认证。
注意:我们需要进入到容器内部修改该配置,然后重启EMQ X服务

密码加盐规则与哈希方法

EMQ X 多数认证插件中可以启用哈希方法,数据源中仅保存密码密文,保证数据安全。
启用哈希方法时,用户可以为每个客户端都指定一个 salt(盐)并配置加盐规则,数据库中存储的密码是按照加盐规则与哈希方法处理后的密文

以 MySQL 认证为例:
加盐规则与哈希方法配置:

# etc/plugins/emqx_auth_mysql.conf 
## 不加盐,仅做哈希处理 
auth.mysql.password_hash = sha256 

## salt 前缀:使用 sha256 加密 salt + 密码 拼接的字符串 auth.mysql.password_hash = salt,sha256 

## salt 后缀:使用 sha256 加密 密码 + salt 拼接的字符串 auth.mysql.password_hash = sha256,salt 

## pbkdf2 with macfun iterations dklen 
## macfun: md4, md5, ripemd160, sha, sha224, sha256, sha384, sha512 
## auth.mysql.password_hash = pbkdf2,sha256,1000,20

如何生成认证信息

1.为每个客户端分配用户名、Client ID、密码以及 salt(盐)等信息
2. 使用与 MySQL 认证相同加盐规则与哈希方法处理客户端信息得到密文
3. 将客户端信息写入数据库,客户端的密码应当为密文信息

EMQ X 身份认证流程

  1. 根据配置的认证 SQL 结合客户端传入的信息,查询出密码(密文)和 salt(盐)等认证数据没有查询结果时,认证将终止并返回 ignore 结果
  2. 根据配置的加盐规则与哈希方法计算得到密文,没有启用哈希方法则跳过此步
  3. 将数据库中存储的密文与当前客户端计算的到的密文进行比对,比对成功则认证通过,否则认证失败

PostgreSQL 认证功能逻辑图:
在这里插入图片描述
写入数据的加盐规则、哈希方法与对应插件的配置一致时认证才能正常进行。更改哈希方法会造成现有认证数
据失效。

认证链

当同时启用多个认证方式时,EMQ X 将按照插件开启先后顺序进行链式认证:

  • 一旦认证成功,终止认证链并允许客户端接入
  • 一旦认证失败,终止认证链并禁止客户端接入
  • 直到最后一个认证方式仍未通过,根据匿名认证配置判定
    • 匿名认证开启时,允许客户端接入
    • 匿名认证关闭时,禁止客户端接入

在这里插入图片描述

Username认证

Username 认证使用配置文件预设客户端用户名与密码,支持通过 HTTP API 管理认证数据。
Username 认证不依赖外部数据源,使用上足够简单轻量。使用这种认证方式前需要开启插件,我们可以在Dashboard里找到这个插件并开启。

插件:emqx_auth_username

哈希方法

Username 认证默认使用 sha256 进行密码哈希加密,可在 etc/plugins/emqx_auth_username.conf 中更改:

# etc/plugins/emqx_auth_username.conf 
## Value: plain | md5 | sha | sha256 
auth.user.password_hash = sha256

配置哈希方法后,新增的预设认证数据与通过 HTTP API 添加的认证数据将以哈希密文存储在 EMQ X 内置数据库中。

预设认证数据

可以通过配置文件预设认证数据,编辑配置文件: etc/plugins/emqx_auth_username.conf
在这里插入图片描述
插件启动时将读取预设认证数据并加载到 EMQ X 内置数据库中,节点上的认证数据会在此阶段同步至集群中。
预设认证数据在配置文件中使用了明文密码,出于安全性与可维护性考虑应当避免使用该功能。

HTTP API 管理认证数据

EMQ X提供了对应的HTTP API用以维护内置数据源中的认证信息,我们可以添加/查看/取消/更改认证数据
1:查看已有认证用户数据: GET api/v4/auth_username
在这里插入图片描述

2:添加认证数据API 定义: POST api/v4/auth_username{ “username”: “emqx_u”, “password”: “emqx_p”}
在这里插入图片描述
3:更改指定用户名的密码API 定义: PUT api/v4/auth_username/${username}{ “password”: “emqx_new_p”}
指定用户名,传递新密码进行更改,再次连接时需要使用新密码进行连接
在这里插入图片描述

4:查看指定用户名信息API 定义: GET api/v4/auth_username/${username}
指定用户名,查看相关用户名、密码信息,注意此处返回的密码是使用配置文件指定哈希方式加密后的密码:
在这里插入图片描述

5:删除认证数据API 定义: DELETE api/v4/auth_username/${username}用以删除指定认证数据
在这里插入图片描述

MQTTX客户端验证

  • 发布者向某个主题发布消息在这里插入图片描述
  • 订阅者订阅该主题
    在这里插入图片描述
  • 订阅者收到消息
    在这里插入图片描述

Client ID认证

Client ID 认证使用配置文件预设客户端Client ID 与密码,支持通过 HTTP API 管理认证数据。
Client ID 认证不依赖外部数据源,使用上足够简单轻量,使用该种认证方式时需要开启 emqx_auth_clientid插件,直接在DashBoard中开启即可,
哈希方法
Client ID 认证默认使用 sha256 进行密码哈希加密,可在 etc/plugins/emqx_auth_clientid.conf 中更改:

auth.client.password_hash = sha256

配置哈希方法后,新增的预设认证数据与通过 HTTP API 添加的认证数据将以哈希密文存储在 EMQ X 内置数据库中。

预设认证数据

可以通过配置文件预设认证数据,编辑配置文件: etc/plugins/emqx_auth_clientid.conf
插件启动时将读取预设认证数据并加载到 EMQ X 内置数据库中,节点上的认证数据会在此阶段同步至集群
中。
预设认证数据在配置文件中使用了明文密码,出于安全性与可维护性考虑应当避免使用该功能

HTTP API 管理认证数据

1:添加认证数据API 定义: POST api/v4/auth_clientid{ “clientid”: “emqx_c”, “password”: “emqx_p”}
在这里插入图片描述
2:查看已经添加的认证数据API 定义: GET api/v4/auth_clientid
在这里插入图片描述
3:更改指定 Client ID 的密码API 定义: PUT api/v4/auth_clientid/${clientid}{ “password”: “emqx_new_p”}
指定 Client ID,传递新密码进行更改,再次连接时需要使用新密码进行连接:
在这里插入图片描述

4:查看指定 Client ID 信息API 定义: GET api/v4/auth_clientid/${clientid}
指定 Client ID,查看相关 Client ID、密码信息,注意此处返回的密码是使用配置文件指定哈希方式加密后的
密码:
在这里插入图片描述

5:删除认证数据API 定义: DELETE api/v4/auth_clientid/${clientid}
删除指定 Client ID:
在这里插入图片描述

HTTP认证

HTTP 认证使用外部自建 HTTP 应用认证数据源,根据 HTTP API 返回的数据判定认证结果,能够实现复杂的认证鉴权逻辑。启用该功能需要将 emqx_auth_http 插件启用,并且修改该插件的配置文件,在里面指定HTTP认证接口的url。 emqx_auth_http 插件同时还包含了ACL的功能,我们暂时还用不上,通过注释将其禁用。
1:在Dashboard中中开启 emqx_auth_http 插件,同时为了避免误判我们可以停止通过username,clientID
进行认证的插件 emqx_auth_clientid , emqx_auth_username

认证原理

EMQ X 在设备连接事件中使用当前客户端相关信息作为参数,向用户自定义的认证服务发起请求查询权限,通过返回的 HTTP 响应状态码 (HTTP statusCode) 来处理认证请求。

  • 认证失败:API 返回 4xx 状态码
  • 认证成功:API 返回 200 状态码
  • 忽略认证:API 返回 200 状态码且消息体 ignore

HTTP 请求信息

HTTP API 基础请求信息,配置证书、请求头与重试规则。

# etc/plugins/emqx_auth_http.conf 
## 启用 HTTPS 所需证书信息 
## auth.http.ssl.cacertfile = etc/certs/ca.pem 
## auth.http.ssl.certfile = etc/certs/client-cert.pem

## auth.http.ssl.keyfile = etc/certs/client-key.pem 
## 请求头设置 
## auth.http.header.Accept = */* 

## 重试设置 
auth.http.request.retry_times = 3 
auth.http.request.retry_interval = 1s 
auth.http.request.retry_backoff = 2.0

加盐规则与哈希方法
HTTP 在请求中传递明文密码,加盐规则与哈希方法取决于 HTTP 应用。

认证请求

进行身份认证时,EMQ X 将使用当前客户端信息填充并发起用户配置的认证查询请求,查询出该客户端在HTTP 服务器端的认证数据。
打开etc/plugins/emqx_auth_http.conf配置文件,通过修改如下内容:修改完成后需要重启EMQX服务

# etc/plugins/emqx_auth_http.conf 
## 请求地址 
auth.http.auth_req = http://192.168.200.10:8991/mqtt/auth 
## HTTP 请求方法 
## Value: post | get | put 
auth.http.auth_req.method = post 
## 请求参数 
auth.http.auth_req.params = clientid=%c,username=%u,password=%P

HTTP 请求方法为 GET 时,请求参数将以 URL 查询字符串的形式传递;POST、PUT 请求则将请求参数以普通表单形式提交(content-type 为 x-www-form-urlencoded)。
你可以在认证请求中使用以下占位符,请求时 EMQ X 将自动填充为客户端信息:

%u:用户名
%c:Client ID
%a:客户端 IP 地址
%r:客户端接入协议
%P:明文密码
%p:客户端端口
%C:TLS 证书公用名(证书的域名或子域名),仅当 TLS 连接时有效
%d:TLS 证书 subject,仅当 TLS 连接时有效

推荐使用 POST 与 PUT 方法,使用 GET 方法时明文密码可能会随 URL 被记录到传输过程中的服务器日志中。

认证服务开发

在这里插入图片描述

在这里插入图片描述
这个地方的Client-ID随便输入,因为在验证的代码里没有对该字段做校验,之后点连接,发现会连接成功,然后可以去自定义的认证服务中查看控制台输出,证明基于外部的http验证接口生效了。在实际项目开发过程中,HTTP接口校验的代码不会这么简单,账号和密码之类的数据肯定会存在后端数据库中,代码会通过传入的数据和数据库中的数据做校验,如果成功才会校验成功,否则校验失败
当然EMQ X除了支持我们之前讲过的几种认证方式外,还支持其他的认证方式,比如:MySQL认证、PostgreSQL认证、Redis认证、MongoDB认证,对于其他这些认证方式只需要开启对应的EMQ X插件并且配置对应的配置文件,将对应的数据保存到相应的数据源即可。

客户端SDK

Paho Java客户端是用Java编写的MQTT客户端库,用于开发在JVM或其他Java兼容平台(例如Android)上运行的应用程序。
Paho不仅可以对接EMQ X Broker,还可以对接满足符合MQTT协议规范的消息代理服务端,目前Paho可以支持到MQTT5.0以下版本。MQTT3.3.1协议版本基本能满足百分之九十多的接入场景。

Paho Java客户端提供了两个API:

  • 1:MqttAsyncClient提供了一个完全异步的API,其中活动的完成是通过注册的回调通知的。
  • 2:MqttClient是MqttAsyncClient周围的同步包装器,在这里,功能似乎与应用程序同步。

Paho实现消息收发

(1)找到项目:emq-demo,添加坐标依赖

<dependency>
	<groupId>org.eclipse.paho</groupId>
	<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
	<version>1.2.2</version>
</dependency>

(2)编写客户端封装类的代码:com.itheima.mqtt.client.EmqClient

package com.itheima.mqtt.client;

import com.itheima.mqtt.enums.QosEnum;
import com.itheima.mqtt.properties.MqttProperties;
import org.eclipse.paho.client.mqttv3.*;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

/**
 *
 */
@Component
public class EmqClient {
    
    
    private static final Logger log = LoggerFactory.getLogger(EmqClient.class);
    
    
    private IMqttClient mqttClient;
    
    @Autowired
    private MqttProperties mqttProperties;
    
    @Autowired
    private MqttCallback mqttCallback;
    
    
    @PostConstruct
    public void init(){
    
    	//MqttClientPersistence是接口 实现类有:MqttDefaultFilePersistence;MemoryPersistence
        MqttClientPersistence mempersitence = new MemoryPersistence();
        try {
    
            mqttClient = new MqttClient(mqttProperties.getBrokerUrl(),mqttProperties.getClientId(),mempersitence);
        } catch (MqttException e) {
    
            log.error("初始化客户端mqttClient对象失败,errormsg={},brokerUrl={},clientId={}",e.getMessage(),mqttProperties.getBrokerUrl(),mqttProperties.getClientId());
        }

    }

    /**
     * 连接broker
     * @param username
     * @param password
     */
    public void connect(String username,String password){
    
    	//创建MQTT连接选项对象--可配置mqtt连接相关选项
        MqttConnectOptions options = new MqttConnectOptions();
        //自动重连
        options.setAutomaticReconnect(true);
        options.setUserName(username);
        options.setPassword(password.toCharArray());
        /** 设置为true后意味着:
        * * 客户端断开连接后emq不保留会话保留会话,否则会产生订阅共享队列的存活 客户端收不到消息的情况 
        * * 因为断开的连接还被保留的话,emq会将队列中的消息负载到断开但还保留的客户端,导致存活的客户 端收不到消息
        * * 解决该问题有两种方案:1.连接断开后不要保持;2.保证每个客户端有固定的clientId 
        * */
        options.setCleanSession(true);
        //设置mqtt消息回调
        mqttClient.setCallback(mqttCallback);
		//连接broker
        try {
    
            mqttClient.connect(options);
        } catch (MqttException e) {
    
            log.error("mqtt客户端连接服务端失败,失败原因{}",e.getMessage());
        }
    }

    /**
     * 断开连接
     */
    @PreDestroy
    public void disConnect(){
    
        try {
    
            mqttClient.disconnect();
        } catch (MqttException e) {
    
            log.error("断开连接产生异常,异常信息{}",e.getMessage());
        }
    }

    /**
     * 重连
     */
    public void reConnect(){
    
        try {
    
            mqttClient.reconnect();
        } catch (MqttException e) {
    
            log.error("重连失败,失败原因{}",e.getMessage());
        }
    }

    /**
     * 发布消息
     * @param topic
     * @param msg
     * @param qos
     * @param retain
     */
    public void publish(String topic, String msg, QosEnum qos,boolean retain){
    

        MqttMessage mqttMessage = new MqttMessage();
        mqttMessage.setPayload(msg.getBytes());
        mqttMessage.setQos(qos.value());
        mqttMessage.setRetained(retain);
        try {
    
            mqttClient.publish(topic,mqttMessage);
        } catch (MqttException e) {
    
            log.error("发布消息失败,errormsg={},topic={},msg={},qos={},retain={}",e.getMessage(),topic,msg,qos.value(),retain);
        }

    }

    /**
     * 订阅
     * @param topicFilter
     * @param qos
     */
    public void subscribe(String topicFilter,QosEnum qos){
    
        try {
    
            mqttClient.subscribe(topicFilter,qos.value());
        } catch (MqttException e) {
    
            log.error("订阅主题失败,errormsg={},topicFilter={},qos={}",e.getMessage(),topicFilter,qos.value());
        }

    }

    /**
     * 取消订阅
     * @param topicFilter
     */
    public void unSubscribe(String topicFilter){
    
        try {
    
            mqttClient.unsubscribe(topicFilter);
        } catch (MqttException e) {
    
            log.error("取消订阅失败,errormsg={},topicfiler={}",e.getMessage(),topicFilter);
        }
    }
    
}

需要在application.yml中添加自定义的配置:

mqtt:  
  broker-url: tcp://127.0.0.1:1883
  client-id: emq-client
  username: user
  password: 123456

同时需要创建属性配置类来加载该配置数据,创建:com.itheima.mqtt.properties.MqttProperties

package com.itheima.mqtt.properties;

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

/**
 * 
 */
@Configuration
@ConfigurationProperties(prefix = "mqtt")
public class MqttProperties {
    
    
    private String brokerUrl;
    
    private String clientId;
    
    private String username;
    
    private String password;


    public String getBrokerUrl() {
    
        return brokerUrl;
    }

    public void setBrokerUrl(String brokerUrl) {
    
        this.brokerUrl = brokerUrl;
    }

    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;
    }

    @Override
    public String toString() {
    
        return "MqttProperties{" +
                "brokerUrl='" + brokerUrl + '\'' +
                ", clientId='" + clientId + '\'' +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

还需创建QoS服务之类枚举:com.itheima.mqtt.enums.QosEnum

package com.itheima.mqtt.enums;

/**

 */
public enum QosEnum {
    
    QoS0(0),QoS1(1),QoS2(2);


    private final int value;

    QosEnum(int value) {
    
        this.value = value;
    }
    
    public int value(){
    
        return this.value;
    }
}

(3)在连接接收到消息之后,我们需要将消息传入消息回调:com.itheima.mqtt.client.MessageCallback

package com.itheima.mqtt.client;

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;
import org.springframework.stereotype.Component;

/**
 */
@Component
public class MessageCallback implements MqttCallback {
    
    
    private static final Logger log = LoggerFactory.getLogger(MessageCallback.class);


    /**
     * 丢失了对服务端的连接后触发的回调
     * @param cause
     */
    @Override
    public void connectionLost(Throwable cause) {
    
	    //丢失对服务端的连接后触发该方法回调,此处可以做一些特殊处理,比如重连
        // 资源的清理  重连
        log.info("丢失了对服务端的连接");
    }

    /**
     * 订阅到消息后的回调 
     * 该方法由mqtt客户端同步调用,在此方法未正确返回之前,不会发送ack确认消息到broker 
     * 一旦该方法向外抛出了异常客户端将异常关闭,当再次连接时;所有QoS1,QoS2且客户端未进行ack确认的 消息都将由 
     * broker服务器再次发送到客户端
     * 应用收到消息后触发的回调
     * @param topic
     * @param message
     * @throws Exception
     */
    @Override
    public void messageArrived(String topic, MqttMessage message) throws Exception {
    
        log.info("订阅者订阅到了消息,topic={},messageid={},qos={},payload={}",
                topic,
                message.getId(),
                message.getQos(),
                new String(message.getPayload()));
    }

    /**
     * 消息发布者消息发布完成产生的回调
     * 消息发布完成且收到ack确认后的回调 
     * * QoS0:消息被网络发出后触发一次 
     * * QoS1:当收到broker的PUBACK消息后触发 
     * * QoS2:当收到broer的PUBCOMP消息后触发
     * @param token
     */
    @Override
    public void deliveryComplete(IMqttDeliveryToken token) {
    
        int messageId = token.getMessageId();
        String[] topics = token.getTopics();
        log.info("消息发布完成,messageid={},topics={}",messageId,topics);
    }
}

(4)编写消息发布和订阅的测试,在启动类中添加如下代码

package com.itheima;

import com.itheima.mqtt.client.EmqClient;
import com.itheima.mqtt.enums.QosEnum;
import com.itheima.mqtt.properties.MqttProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import javax.annotation.PostConstruct;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.TimeUnit;

@SpringBootApplication
public class EmqDemoApplication {
    

	public static void main(String[] args) {
    
		SpringApplication.run(EmqDemoApplication.class, args);
	}
	
	
	@Autowired
	private EmqClient emqClient;
	
	@Autowired
	private MqttProperties properties;
	
	@PostConstruct
	public void init(){
    
		//连接服务端
		emqClient.connect(properties.getUsername(),properties.getPassword());
		//订阅一个主题
		emqClient.subscribe("testtopic/#", QosEnum.QoS2);
		//开启一个新的线程 每隔5秒去向 testtopic/123
		new Thread(()->{
    
			while (true){
    
				emqClient.publish("testtopic/123"," publish msg :"+ LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME),
						QosEnum.QoS2,false);
				try {
    
					TimeUnit.SECONDS.sleep(5);
				} catch (InterruptedException e) {
    
					e.printStackTrace();
				}


			}
		}).start();
	}

}

在这里插入图片描述

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/yemuxiaweiliang/article/details/123312549

智能推荐

普里姆算法c语言(详细解读)_c语言普里姆算法-程序员宅基地

文章浏览阅读854次,点赞5次,收藏12次。找到与这个系统邻接的边(0,1),(5,4),比较两者的权值,容易发现权值最小的为25,因此加入边(5,4),同时加入结点4和边(5,4)。4.将0,5,4,3以及相关的边看成一个整体,与其邻接的边有(0,1)28,(4,6)24,(3,6)18,(3,2)12,四个边中权值最小的边是(3,2),所以加入结点2以及边(3,2)。5.与4中所构成的整体邻接的边有(0,1)28,(4,6)24,(3,6)18,(2,1)16,四者中权值最小的边为(2,1),所以加入结点1以及边(2,1)。_c语言普里姆算法

nohub 和 & 在linux上不间断后台运行程序-程序员宅基地

文章浏览阅读3.1k次,点赞2次,收藏15次。长时间在服务器上运行深度学习代码,使用nohub 命令行 & 可以让代码不间断在后台运行_nohub

Policy-based Reinforcement learning_policy函数-程序员宅基地

文章浏览阅读4k次,点赞18次,收藏69次。强化学习这一章会讲基于策略的强化学习Value-Based Reinforcement Learning-DQN强化学习前言一、policy函数二、DQN2.1 游戏中agent的目标是什么?2.2 agent如何做决策?2.3 如何理解Q* 函数呢?2.5 DQN打游戏?三、如何训练DQN?3.1 TD算法3.2 TD算法训练DQN四、训练步骤六、总结前言说明一下:这是我的一个学习笔记,课程链接如下:最易懂的强化学习课程公众号:AI那些事一、policy函数我们回顾一下Acti_policy函数

project2016调配资源冲突-程序员宅基地

文章浏览阅读5.4k次,点赞9次,收藏26次。(1) Project查看资源负荷情况的方法和结果在工时类资源会存在资源过度分配(在同一个时间段给工时类资源分配的资源超出了他的最大单位)的情况,而成本类、材料类资源则不会有、查看资源负荷的方法有:在视图栏------资源图表如下图在这里我们可以看到每个资源的分配状况,如下图滚动鼠标滑轮就会出现不同的资源分配状况此时选择“资源”—“下一个资源过度分配处”如下图总结:甘特图、..._project2016调配资源冲突

推荐算法知识图谱模型(二):KGCN-程序员宅基地

文章浏览阅读235次。常用的KGE方法侧重于建模严格的语义相关性(例如,TransE和TransR假设头+关系=尾),这更适合于KG补全和链接预测等图内应用,而不是推荐。更自然、更直观的方法是直接设计一个图算法来利用KG结构。_图谱模型

ajax跨域与cookie跨域_一级域名 的cookie ajax 请求二级域名时获取cookie-程序员宅基地

文章浏览阅读389次。ajax跨域ajax跨域取数据(利用可以跨域加载js的原理 functioncallback(){ }这是需要返回这样一个js函数)ajax数据类型使用jsonp :如 ajax{ url:..._一级域名 的cookie ajax 请求二级域名时获取cookie

随便推点

lego-loam阅读理解笔记 一_horizon_angle = atan2(p.x, p.y) * 180.0 / m_pi;-程序员宅基地

文章浏览阅读1.3k次。前言论文:https://ieeexplore.ieee.org/abstract/document/8594299ego-loam源码地址:https://github.com/RobustFieldAutonomyLab/LeGO-LOAM文章原理讲解除了看论文,看看这些博客:LeGO-LOAM:轻量级地面优化的建图其他博客代码理解推荐:https://blog.csdn.net/orange_littlegirl/article/details/95238586安装编译..._horizon_angle = atan2(p.x, p.y) * 180.0 / m_pi;

购物车功能测试用例测试点整理思维导图方式_购物车测试点思维导图-程序员宅基地

文章浏览阅读8.2k次,点赞12次,收藏71次。_购物车测试点思维导图

使用matplotlib绘图实现动态刷新(动画)效果_matplotlib 动态刷新-程序员宅基地

文章浏览阅读4.8k次,点赞7次,收藏36次。最近在做四足的运动学仿真,因为这一段时间用python比较多,所以想直接用python做运动仿真,通过画图来展示步态和运动效果。了解了一下matplotlib库之后又参考了一些网上的博客,成功实现了绘图动态刷新的效果,类似动画效果。_matplotlib 动态刷新

Apache Kafka 可视化工具调研_kafka-console-ui-程序员宅基地

文章浏览阅读3k次。Apache Kafka 可视化工具调研_kafka-console-ui

如何编译部署独立专用服务端(Standalone Dedicated Server)【UE4】_ue4 独立服务器搭建-程序员宅基地

文章浏览阅读1.4k次。一、下载源码及编译原文链接首先需要unrealengine官网上注册并加入github开发组才有权限进入下面地址。https://github.com/EpicGames/UnrealEngine/tags注意:编译专用服务器,只能用源码编译版本的引擎,安装版本的引擎无法编译Server。打开页面后下载一个最新的release版本,解压出来后先运行Setup.bat,会自动下载资源..._ue4 独立服务器搭建

Hadoop 序列化机制_hadoop final-程序员宅基地

文章浏览阅读493次。序列化是指将结构化对象转化为字节流以便在网络上传输或者写到磁盘上进行永久存储的过程,反序列化是指将字节流转回结构化对象的逆过程序列化用于分布式处理的两大领域,进程间通信和永久存储。在Hadoop中,系统中多个节点上进程间的通信是通过“远程过程调用”(remote procedure call, RPC)实现的。RPC将消息序列化成二进制流后发送到远程节点,远程节点接着将二进制流饭序列化为原始..._hadoop final