【ESP 保姆级教程】玩转emqx篇③ ——认证安全之使用内置数据库(Mnesia)的密码认证

news2024/12/22 20:59:00

忘记过去,超越自己

  • ❤️ 博客主页 单片机菜鸟哥,一个野生非专业硬件IOT爱好者 ❤️
  • ❤️ 本篇创建记录 2023-01-15 ❤️
  • ❤️ 本篇更新记录 2022-01-15 ❤️
  • 🎉 欢迎关注 🔎点赞 👍收藏 ⭐️留言📝
  • 🙏 此博客均由博主单独编写,不存在任何商业团队运营,如发现错误,请留言轰炸哦!及时修正!感谢支持!
  • 🔥 Arduino ESP8266教程累计帮助过超过1W+同学入门学习硬件网络编程,入选过选修课程,刊登过无线电杂志 🔥
  • 🔥 菜鸟项目合集 🔥

快速导读

    • 1. 前言
      • 1.1 认证原理
      • 1.2 避免存储明文密码
    • 2. Mnesia数据库(了解即可)
    • 3. 使用内置数据库(Mnesia)的密码认证
      • 3.1 操作步骤1:选择认证方式为 Password-Based
      • 3.2 操作步骤2:选择数据源为 Built-in Database
      • 3.3 操作步骤3:配置参数
        • 3.3.1 配置为 md5、sha 等散列算法
        • 3.3.2 配置为 bcrypt 算法
        • 3.3.3 配置为 pkbdf2 算法
      • 3.4 操作步骤4:用户管理
    • 4. MQTTX 测试
      • 4.1 测试随机账号
      • 4.2 测试商场1号
      • 4.3 测试商场2号
      • 4.4 超级用户
    • 5. ESP8266 测试
      • 5.1 测试随机账号
      • 5.2 测试商场1号
      • 5.3 测试商场2号
      • 5.4 测试超级用户
    • 6. 总结

手把手代码注释,完整案例讲解开发过程以及细节,一键式运行代码。
ESP保姆级付费专栏群 707958244,不喜勿加,凭借付费专栏订单号加入

  • ESP 保姆级教程150篇 系列导读(此专栏非常适合刚刚入门ESP开发的初学者)

1. 前言

在前面一章 玩转emqx篇② ——控制客户端连接,认证安全 中 ,我们介绍到认证安全有非常多的方式。
在这里插入图片描述
那么,接下来我们针对一些常用的进行详细讲解细节。

认证安全最重要的目的就是管理谁能连上服务器。首次安装emqx,如果没有配置任何认证安全策略,所有人都可以连接上你的emqx服务器。

本章主要讲解 使用内置数据库(Mnesia)的密码认证

1.1 认证原理

密码认证通常需要由用户提供身份 ID对应的密码,身份 ID 用于标识用户的身份,可以是用户名客户端标识符或者证书通用名称等。身份 ID 与密码的正确组合,只在用户和认证系统之间共享,因此认证系统可以通过比较用户提供的密码和存储在自己数据库中的密码来验证用户所声明身份的真实性。

1.2 避免存储明文密码

为了完成身份验证,用户与认证系统之间需要共享一些信息,例如密码。但这意味着原本应该保密的密码现在被多方持有,这会显著增加密码泄漏的概率,因为攻击者攻击任意一方都有可能窃取到密码

因此,我们不建议在认证系统的数据库中以明文的形式存储密码。因为一旦遭遇拖库,这些密码将完全暴露在攻击者面前。我们更建议生成一个随机的盐,然后在数据库中存储这个盐和对密码加盐后散列得到的值。这样即便攻击者窃取到了数据库中的数据,他既不能拿着这个散列值来进行登录,也很难根据散列值反推出真正的密码。

Hash(密码+随机盐) 得到一个散列值(HashCode)。一般这个值看起来是乱码且无法反推出原始数据。

2. Mnesia数据库(了解即可)

Mnesia是一个分布式数据库管理系统,适合于电信和其它需要持续运行和具备软实时特性的Erlang应用,越来越受关注和使用,但是目前Mnesia资料却不多,很多都只有官方的用户指南。

目前找到一些资料:

【Mnesia文档】1、引言
【Mnesia文档】2、概述
【Mnesia文档】3、快速开始

3. 使用内置数据库(Mnesia)的密码认证

EMQX 支持使用内置数据库(Mnesia)作为客户端身份凭据的存储介质,无需用户额外部署其他数据库,能够做到开箱即用使用内置数据库也是 EMQX 的默认推荐方案,因为它为身份验证提供了最佳性能(开机加载在内存中)。

对于初学者来说,这种方式搭建最简单高效。

使用 EMQX Dashboard 来创建使用内置数据库的密码认证。

在 Dashboard > 访问控制 > 认证 (opens new window)页面单击创建

3.1 操作步骤1:选择认证方式为 Password-Based

在这里插入图片描述

3.2 操作步骤2:选择数据源为 Built-in Database

在这里插入图片描述

3.3 操作步骤3:配置参数

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 账号类型
    用于指定 EMQX 应当使用哪个字段作为客户端的身份 ID 进行认证,可选值有 usernameclientid。对于 MQTT 客户端来说,分别对应 CONNECT 报文中的 UsernameClient Identifier 字段。
  • 密码加密方式
    用于指定存储密码时使用的散列算法,支持plain(明文方式,不建议)、 md5sha(正常情况下sha256够用)、bcryptpbkdf2 等。对于不同的散列算法,内置数据库密码认证器会有不同的配置要求,这个是本章重点内容。

注意:

plain 不在考虑范围。明文存储密码。

3.3.1 配置为 md5、sha 等散列算法

加盐方式,用于指定盐和密码的组合方式:在密码尾部加盐(password+suffix)还是在密码头部加盐(prefix+password),也可以不加盐(disable)。

上面说到了一个叫做加盐(salt)的词汇,对于初学者怎么去理解它呢?

一般我们会把密码经过hash编码之后得到一个散列密码值(一般看起来像是乱码)。
Hash(密码) = hashCode,这种方式是比较固定的,如果我们在密码基础上加一个随机值salt,就变成了 Hash(密码+salt) = hashCode,这样只要salt稍微变化一点点,整个hashcode就完全不一样。为什么叫做加盐呢?可以简单理解为同样一道菜,放盐多一点或者放盐少一点,整个菜的味道还是完全不一样的。
头部加盐或者尾部加盐其实就是:
Hash(salt+密码)以及Hash(密码+salt)。就像炒菜,前面一开始就放盐还是后面才放盐,味道天差地别。
在这里插入图片描述

对应配置文件 /var/lib/emqx/configs/cluster-override.conf 内容:

authentication = [
  {
    backend = "built_in_database"
    enable = false
    mechanism = "password_based"
    password_hash_algorithm {name = "sha256", salt_position = "suffix"}
    user_id_type = "username"
  }
]

关注password_hash_algorithm

password_hash_algorithm {
  name = sha256             # plain, md5, sha, sha512
  salt_position = suffix    # prefix, disable
}

3.3.2 配置为 bcrypt 算法

Salt Rounds,又称成本因子,用于指定散列需要的计算次数(2^Salt Rounds)。每加一,散列需要的时间就会翻倍,需要的时间越长,暴力破解的难度就越高,但相应的验证用户需要花费的时间也就越长,因此需要按照您的实际情况进行取舍。

算法高级(22)-BCrypt加密算法,号称目前最安全的算法之一

内部自己实现了随机加盐处理。使用Bcrypt,每次加密后的密文是不一样的。
对一个密码,Bcrypt每次生成的hash都不一样,那么它是如何进行校验的?

  • 虽然对同一个密码,每次生成的hash不一样,但是hash中包含了salt(hash产生过程:先随机生成salt,salt跟password进行hash);
  • 在下次校验时,从hash中取出salt,salt跟password进行hash;得到的结果跟保存在DB中的hash进行比对。

举个例子:

加密后的格式一般为:$2a 10 10 10/bTVvqqlH9UiE0ZJZ7N2Me3RIgUCdgMheyTgV0B4cMCSokPa.6oCa
其中:$是分割符,无意义;2a是bcrypt加密版本号;10是cost的值;而后的前22位是salt值;再然后的字符串就是密码的密文了。

对应配置文件 /var/lib/emqx/configs/cluster-override.conf 内容:

authentication {
  backend = "built_in_database"
  mechanism = "password_based"
  password_hash_algorithm {name = "bcrypt", salt_rounds = "10"}
  user_id_type = "username"
}

关注password_hash_algorithm

password_hash_algorithm {name = "bcrypt", salt_rounds = "10"}

3.3.3 配置为 pkbdf2 算法

伪随机函数,用于指定生成密钥使用的散列函数。
迭代次数,用于指定散列次数。
密钥长度,指定希望得到的密钥长度。如果未指定,则表示由 伪随机函数 决定输出的密钥长度。
PBKDF2 算法概述

对应配置文件 /var/lib/emqx/configs/cluster-override.conf 内容:

authentication {
  backend = "built_in_database"
  mechanism = "password_based"
  password_hash_algorithm {
    iterations = 4096
    mac_fun = "sha256"
    name = "pbkdf2"
  }
  user_id_type = "username"
}

关注password_hash_algorithm

password_hash_algorithm {
    iterations = 4096
    mac_fun = "sha256"
    name = "pbkdf2"
}

3.4 操作步骤4:用户管理

我们以密码加密方式 sha256 为例子。
在这里插入图片描述

当我们构建好数据库之后,就可以添加管理用户。
在这里插入图片描述

这里我们加三个测试用户。

  • 商场1号,密码123456
    在这里插入图片描述
  • 商场2号,密码123456
    在这里插入图片描述
  • 超级用户,密码也是123456
    在这里插入图片描述

最终结果:
在这里插入图片描述
到这里整个配置就完成了,接下来我们测试一下效果。

4. MQTTX 测试

在这里插入图片描述
需要创建4个连接,分别是随机账号、商场1号、商场2号、超级用户。

4.1 测试随机账号

主要是填上自己的ip地址以及username。
这里为:

  • username 随机账号

在这里插入图片描述
点击连接,直接提示错误。

原因:它就没有在我们的用户管理名单中
在这里插入图片描述

4.2 测试商场1号

主要是填上自己的ip地址以及username、正确密码。
这里为:

  • username 商场1号

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.3 测试商场2号

主要是填上自己的ip地址以及username、错误密码。
这里为:

  • username 商场2号
  • 密码随机填

在这里插入图片描述
在这里插入图片描述

4.4 超级用户

主要是填上自己的ip地址以及username、正确密码。
这里为:

  • username dpjcn
  • 密码正确:123456

在这里插入图片描述
在这里插入图片描述
注意:

EMQX 也允许用户为某些特殊的客户端设置超级用户权限,从而跳过后续所有的权限检查。(后续会讲授权管理)

5. ESP8266 测试

以下代码是emqx 认证安全测试,更改一些参数之后直接烧录到nodemcu中。

/**
 * 功能: emqx 认证安全测试
 *
 * 1、运行前提:
 *  这里尽量把第三方库集成在工程目录下,如出现xxxx库找不到,请按照下面方式进行安装。
 *  - 缺少 PubSubClient。 工具 -> 管理库 -> 搜索 PubSubClient -> 安装最新版本
 *
 * 2、逻辑描述:
 *   - 连接上emqx服务器,然后测试测试认证功能,包括商品1号、商品2号、超级用户等等
 *
 * 3、硬件材料:
 *   - 1*ESP8266-12 NodeMcu板子
 */

// 导入必要的库
#include <ESP8266WiFi.h>  // 引入WiFi核心库
#include "PubSubClient.h" // 引入MQTT处理库


/******************* 常量声明 **********************/
#define SSID "xxxxxxx"            // 填入自己的WiFi账号
#define PASSWORD "xxxxx"          // 填入自己的WiFi密码

//---------------- emqx相关配置信息 ------------------//
#define MQTT_SERVER "192.168.4.1"   // mqtt服务器IP地址,替换为自己的
#define MQTT_PORT   1883            // mqtt服务器端口号,默认是1883,除非你修改过配置
#define MQTT_USERNAME    "商场1号"  // 用户名字
#define MQTT_PASSWORD    "123456"   // 用户密码
//---------------- emqx相关配置信息 ------------------//

WiFiClient espClient;
PubSubClient client(espClient);
long lastMsg = 0;
char msg[50];
int value = 0;

void setup_wifi() {

  delay(10);
  // We start by connecting to a WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(SSID);

  WiFi.begin(SSID, PASSWORD);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  randomSeed(micros());

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

/**
 * 消息回调
 */
void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();

  // 根据第一个字符来控制led灯
  if ((char)payload[0] == '1') {
    digitalWrite(BUILTIN_LED, LOW);
  } else {
    digitalWrite(BUILTIN_LED, HIGH);
  }
}

/**
 * 断开重连
 */
void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // 创建一个随机ClientID
    String clientId = "ESP8266Client-";
    clientId += String(random(0xffff), HEX);
    // 尝试连接emqx mqtt服务器,参数:clientid、username、password
    if (client.connect(clientId.c_str(),MQTT_USERNAME,MQTT_PASSWORD)) {
      Serial.println("connected");
      // 发布一个消息,主题是 outTopic
      client.publish("outTopic", "hello world");
      // 订阅 inTopic 主题
      client.subscribe("inTopic");
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // 等待5s重连
      delay(5000);
    }
  }
}

void setup() {
  delay(2000);                   // 延时2秒,用于等待系统上电稳定
  pinMode(BUILTIN_LED, OUTPUT);  // 初始化LED引脚为输出引脚    
  Serial.begin(115200);          // 初始化串口,波特率 115200
  Serial.println("");   // 串口默认先换行显示
  Serial.println("esp_emqx run~");// 串口打印信息表示项目启动~
  setup_wifi();                   // 初始化Wifi连接
  client.setServer(MQTT_SERVER, MQTT_PORT); //配置mqtt服务器地址和端口
  client.setCallback(callback);   //设置订阅消息回调
}

void loop() {
  //重连机制
  if (!client.connected()) {
    reconnect();
  }
  //不断监听信息
  client.loop();

  long now = millis();
  if (now - lastMsg > 2000) {
    //每2s发布一次信息
    lastMsg = now;
    ++value;
    snprintf (msg, 50, "hello world #%ld", value);
    Serial.print("Publish message: ");
    Serial.println(msg);
    client.publish("outTopic", msg);
  }
}

更改参数内容:

/******************* 常量声明 **********************/
#define SSID "xxxxxxx"            // 填入自己的WiFi账号
#define PASSWORD "xxxxx"          // 填入自己的WiFi密码

//---------------- emqx相关配置信息 ------------------//
#define MQTT_SERVER "192.168.4.1"   // mqtt服务器IP地址,替换为自己的
#define MQTT_PORT   1883            // mqtt服务器端口号,默认是1883,除非你修改过配置
#define MQTT_USERNAME    "商场1号"  // 用户名字
#define MQTT_PASSWORD    "123456"   // 用户密码
//---------------- emqx相关配置信息 ------------------//
/******************* 常量声明 **********************/

根据提示更改即可。

接下来我们分别测试几个场景。

5.1 测试随机账号

配置改为:

#define MQTT_USERNAME    "随机账号"  // 用户名字
#define MQTT_PASSWORD    "123456"   // 用户密码

测试结果:
在这里插入图片描述
连接失败,毕竟压根不存在这个账号。我们看到这里的状态是5(MQTT_CONNECT_UNAUTHORIZED ,认证失败).

//状态定义
// Possible values for client.state()
#define MQTT_CONNECTION_TIMEOUT     -4
#define MQTT_CONNECTION_LOST        -3
#define MQTT_CONNECT_FAILED         -2
#define MQTT_DISCONNECTED           -1
#define MQTT_CONNECTED               0
#define MQTT_CONNECT_BAD_PROTOCOL    1
#define MQTT_CONNECT_BAD_CLIENT_ID   2
#define MQTT_CONNECT_UNAVAILABLE     3
#define MQTT_CONNECT_BAD_CREDENTIALS 4
#define MQTT_CONNECT_UNAUTHORIZED    5

/**
 * 获取Mqtt客户端当前状态
 */
int PubSubClient::state() {
    return this->_state;
}

5.2 测试商场1号

配置改为:

#define MQTT_USERNAME    "商场1号"  // 用户名字
#define MQTT_PASSWORD    "123456"   // 用户密码

测试结果:
在这里插入图片描述
在这里插入图片描述
正确连接成功。

5.3 测试商场2号

配置改为:

#define MQTT_USERNAME    "商场2号"  // 用户名字
#define MQTT_PASSWORD    "1234567"   // 填写一个错误的用户密码

测试结果:
在这里插入图片描述
连接失败,毕竟压根不存在这个账号。我们看到这里的状态是4(MQTT_CONNECT_BAD_CREDENTIALS ,错误的认证信息,一般就是某一个信息不对).

//状态定义
// Possible values for client.state()
#define MQTT_CONNECTION_TIMEOUT     -4
#define MQTT_CONNECTION_LOST        -3
#define MQTT_CONNECT_FAILED         -2
#define MQTT_DISCONNECTED           -1
#define MQTT_CONNECTED               0
#define MQTT_CONNECT_BAD_PROTOCOL    1
#define MQTT_CONNECT_BAD_CLIENT_ID   2
#define MQTT_CONNECT_UNAVAILABLE     3
#define MQTT_CONNECT_BAD_CREDENTIALS 4
#define MQTT_CONNECT_UNAUTHORIZED    5

/**
 * 获取Mqtt客户端当前状态
 */
int PubSubClient::state() {
    return this->_state;
}

5.4 测试超级用户

配置改为:

#define MQTT_USERNAME    "dpjcn"  // 用户名字
#define MQTT_PASSWORD    "123456"   // 用户密码

测试结果:
在这里插入图片描述
在这里插入图片描述
正确连接成功。

6. 总结

使用内置数据库也是 EMQX 的默认推荐方案,因为它为身份验证提供了最佳性能(开机加载在内存中)。建议初学者可以先用这种方式去操作。

思考题:

上面的方式是以username为维度,其实在选择字段的时候也可以以clientid为维度。
在这里插入图片描述
各位同学也可以试着玩玩,做到举一反三的效果。

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

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

相关文章

Transformer模型详解相关了解

文章目录Transformer模型详解1.前言1.1 Transformer 整体结构1.2 Transformer 的工作流程2. Transformer 的输入2.1 单词 Embedding2.2 位置 Embedding3. Self-Attention&#xff08;自注意力机制&#xff09;3.1 Self-Attention 结构3.2 Q, K, V 的计算3.3 Self-Attention 的输…

《神经网络与深度学习》 邱希鹏 学习笔记(一)

一、机器学习的基本要素 机器学习的基本要素: 模型 学习准则 优化算法 其中模型分为线性和非线性。学习准则有用损失函数来评价模型的好坏&#xff0c;还有经验风险最小化准则&#xff0c;大概意思就是在平均损失函数中获得最小的损失函数&#xff0c;但是因为样本可能很小&…

Goodbye 2022,Welcome 2023 | 锁定 2023

引言又是一年春来到&#xff0c;新年应比旧年好。旧岁已辞&#xff0c;新年已到&#xff0c;新旧更迭之际&#xff0c;真想剪个头发换身行头&#xff0c;就能重新出发。但终究是要回头看看啊&#xff0c;那一路而来的荆棘与芬芳&#xff0c;才是成长的印记啊。那就回拨记忆&…

和涤生大数据的故事

1自我介绍 大家好&#xff0c;我是泰罗奥特曼&#xff0c;毕业于东北的一所不知名一本大学&#xff0c;学校在一个小城市里面&#xff0c;最热闹的地方是一个四层楼的商城&#xff0c;专业是信息管理与信息系统&#xff0c;由于是调剂的&#xff0c;所以我也不知道这个专业是干…

一篇文章带你学完JavaScript基础知识,超全的JavaScript知识点总结

目录 内置函数 alert警告框 promopt提示框 console控制台 字面量 数字型 字符串型 变量 声明与赋值 类型检测 类型转换 比较运算符 逻辑运算符 条件句 if else switch break,continue while 赋值运算符 函数 关键字形式函数 变量认知 作用域 表达式…

什么样的故障让阿里云换了总裁?

&#x1f4e3;&#x1f4e3;&#x1f4e3;&#x1f4e3;&#x1f4e3;&#x1f4e3;&#x1f4e3; &#x1f38d;大家好&#xff0c;我是慕枫 &#x1f38d;前阿里巴巴高级工程师&#xff0c;InfoQ签约作者、阿里云专家博主&#xff0c;一直致力于用大白话讲解技术知识 &#x…

SpringBoot数据访问Redis

目录 前言 1、Redis自动配置 2、RedisTemplate与Lettuce 3、切换至jedis 前言 Redis 是一个开源&#xff08;BSD许可&#xff09;的&#xff0c;内存中的数据结构存储系统&#xff0c;它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构&#xff0c;如 字符串…

基于贝叶斯算法的邮件过滤管理系统的设计和实现(Vue+SpringBoot)

作者主页&#xff1a;Designer 小郑 作者简介&#xff1a;Java全栈软件工程师一枚&#xff0c;来自浙江宁波&#xff0c;负责开发管理公司OA项目&#xff0c;专注软件前后端开发&#xff08;Vue、SpringBoot和微信小程序&#xff09;、系统定制、远程技术指导。CSDN学院、蓝桥云…

Java对象引用级别

为了使程序能更灵活地控制对象生命周期&#xff0c;从 JDK1.2 版本开始&#xff0c;JDK把对象的引用级别由高到低分为强引用、软引用、弱引用、虚引用四种级别。 强引用 StrongReference 强引用是我们最常见的对象&#xff0c;它属于不可回收资源&#xff0c;垃圾回收器&…

区块链技术3--BTC协议

1数字货币中经常出现的问题&#xff1a;双花攻击 数字货币本身为带有签名的数据文件&#xff0c;可以进行复制。即&#xff1a;对用户来说&#xff0c;可以将同一货币花费两次。对货币添加唯一编号&#xff08;不可篡改&#xff09;&#xff0c;每次支付向货币发行单位查询真伪…

数据标注平台(CVAT)安装及踩坑记录

目录 一、CVAT安装 step1 安装docker step2 获取权限 step3 获取权限 step4 克隆cvat源代码 step5 构建docker镜像 step6 运行Docker容器这一步要下载公共docker映像&#xff0c;耗时看网速&#xff0c;但是不会太久。 step6 创建管理员用户 step7 关闭cvat服务 二、…

算法第十二期——BFS-判重

目录 BFS判重 Python判重方法: set、字典 set()判重 字典判重 例题&#xff1a;跳蚱蜢 思路 【建模】 去重 代码一&#xff1a;字典去重&#xff08;用list实现队列&#xff09; 代码二&#xff1a;set()去重&#xff08;用list实现队列&#xff09; 代码二&#xff…

CRMEB开源商城部署在腾讯云2

目录PHP在安装过程中会监测Redish5跨域PHP在安装过程中会监测Redis public\install\index.php if (extension_loaded(redis)) {$redis <span class"correct_span">&radic;</span> 已安装;} else {$redis <a href"https://doc.crmeb.com/w…

2个大厂 100亿级 超大流量 红包 架构方案

2个大厂 100亿级 超大流量 红包 架构方案 文章目录2个大厂 100亿级 超大流量 红包 架构方案100亿级 红包 应用 场景概述百亿级 微信红包技术架构架构**南北分布****拆红包入账异步化****发拆落地&#xff0c;其他操作双层cache**高并发**红包算法****柔性降级方案**360w QPS 10…

Nginx与LUA(3)

您好&#xff0c;我是湘王&#xff0c;这是我的CSDN博客&#xff0c;欢迎您来&#xff0c;欢迎您再来&#xff5e;在互联网应用中&#xff0c;很多场景都会涉及到高并发请求&#xff0c;如果不对这些请求做限制&#xff0c;那么服务器很快就会被挤垮。就像在12306买票一样&…

计算机图形学实习教程之基本图形的生成(扫描线填充算法+图形缩放算法+对称变换算法+消隐算法+金刚石图案算法),利用C#实现,附源码

环境&#xff1a;Win10Visual Studio 2022 Community 在本次实验中需要用到第一篇文章实验内容的代码及环境&#xff0c;详情请见&#xff1a;传送门 目录 一、实验目的 二、实验步骤 1.扫描线填充算法 2.图形的缩放算法 3.对称变换算法 4.消隐算法 5.金刚石图形算法 一…

Unity 3D 人形角色动画(Avatar)||Unity 3D 导航系统||Unity 3D 障碍物

Unity 3D 人形角色动画&#xff08;Avatar&#xff09; Mecanim 动画系统适合人形角色动画的制作&#xff0c;人形骨架是在游戏中普遍采用的一种骨架结构。。 由于人形骨架在骨骼结构上的相似性&#xff0c;用户可以将动画效果从一个人形骨架映射到另一个人形骨架&#xff0c…

基于Java+SpringBoot+Vue求职招聘系统设计与实现

博主介绍&#xff1a;✌全网粉丝3W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供毕业项目实战✌ 博主作品&#xff1a;《微服务实战》专栏是本人的实战经验总结&#xff0c;《Spring家族及微服务系列》专注…

《Buildozer打包实战指南》第一节 在虚拟机中安装Ubuntu系统

目录 1.1 下载并安装Virtual Box虚拟机 1.2 下载并安装Ubuntu系统 由于Buildozer不能在Windows系统上打包&#xff0c;只能运行于Linux&#xff0c;所以我们可以在Windows系统上安装一个虚拟机&#xff0c;并在虚拟机中安装Linux。在本教程中笔者将会一直使用Ubuntu系统&…

大数据分案例-基于随机森林算法构建返乡人群预测模型

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…