1.使用指南

这里主要介绍 mbedtls 程序的基本使用流程,并针对使用过程中经常涉及到的结构体和重要 API 进行简要说明。

mbedtls 的基本工作流程如下所示:

  • 初始化 SSL/TLS 上下文
  • 建立 SSL/TLS 握手
  • 发送、接收数据
  • 交互完成,关闭连接

menuconfig 配置说明

获取 mbedtls 软件包或者修改用户配置都需要使用 menuconfig。需要用户打开 ENV 工具,并将目录切换到您所用的 BSP 目录,使用 menuconfig 命令打开配置界面。

RT-Thread online packages → security packages 中选择 mbedtls 软件包,操作界面如下图所示:

详细的配置介绍如下所示:

RT-Thread online packages --->security packages  --->Select Root Certificate  --->      # 选择证书文件[*] mbedtls: An portable and flexible SSL/TLS library # 打开 mbedtls 软件包[*]   Store the AES tables in ROM      # 将 AES 表存储在 ROM 中(2)   Maximum window size used         # 用于点乘的最大“窗口”大小(2-7)(3584) Maxium fragment length in bytes # 配置数据帧大小[*]   Enable a mbedtls client example  # 开启 mbedtls 测试例程[ ]   Enable Debug log output          # 开启调试 log 输出version (latest)  --->           # 选择软件包版本,默认为最新版本
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • Using all default CA 配置选项会将 certs/default 目录下的所有预置证书加入编译,将占用很大的内存
  • Using user CA 配置选项允许用户将自己需要的证书文件加入编译,需要用户将证书文件拷贝到 certs 根目录

选择合适的配置项后,使用 pkgs --update 命令下载软件包并更新用户配置。

功能配置说明

mbedtls 功能模块的开启与关闭定义在 mbedtls/config.h 和 ports/inc/tls_config.h 文件中

mbedtls/config.h 是 mbedtls 源码里提供的配置文件,ports/inc/tls_config.h 是 RT-Thread 基于 mbedtls 源码中的配置文件进行的裁剪和适配。

最终,用户使用的是 RT-Thread 提供的配置文件 ports/inc/tls_config.h

用户可以通过文件中的宏来使能或失能部分不需要使用的功能模块,从而将 mbedtls 配置到合适的尺寸。

证书配置说明

  • 预置的 CA 证书文件存放在 certs/default 目录中
  • 用户增加的 CA 证书文件存放在 certs 根目录中

certs/default 目录中已经包含了大多数 CA 根证书,如果您使用的根证书不在该文件夹内,需要用户将自己的 PEM 格式的 CA 证书拷贝 certs 根目录下。(仅支持 PEM 格式证书,不支持 DER 格式证书)。

该证书文件中已经包含了大多数 CA 根证书,,参考后边的 添加新证书 章节。

初始化 TLS 会话

typedef struct MbedTLSSession
{char* host;char* port;
<span class="token keyword cye-lm-tag">unsigned</span> <span class="token keyword cye-lm-tag">char</span> <span class="token operator cye-lm-tag">*</span>buffer<span class="token punctuation cye-lm-tag">;</span>               <span class="token comment cye-lm-tag">// 公用数据缓冲区</span>
size_t buffer_len<span class="token punctuation cye-lm-tag">;</span>                   <span class="token comment cye-lm-tag">// 缓冲区大小</span>mbedtls_ssl_context ssl<span class="token punctuation cye-lm-tag">;</span>             <span class="token comment cye-lm-tag">// 保存 ssl 基本数据</span>
mbedtls_ssl_config conf<span class="token punctuation cye-lm-tag">;</span>             <span class="token comment cye-lm-tag">// 保存 ssl 配置信息</span>
mbedtls_entropy_context entropy<span class="token punctuation cye-lm-tag">;</span>     <span class="token comment cye-lm-tag">// 保存 ssl 熵配置</span>
mbedtls_ctr_drbg_context ctr_drbg<span class="token punctuation cye-lm-tag">;</span>   <span class="token comment cye-lm-tag">// 保存随机字节发生器配置</span>
mbedtls_net_context server_fd<span class="token punctuation cye-lm-tag">;</span>       <span class="token comment cye-lm-tag">// 保存文件描述符</span>
mbedtls_x509_crt cacert<span class="token punctuation cye-lm-tag">;</span>             <span class="token comment cye-lm-tag">// 保存认证信息</span>

} MbedTLSSession;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

MbedTLSSession 用于保存建立 TLS 会话连接时的配置信息,在 TLS 上下文中传递使用。用户在使用建立 TLS 会话前,必须定义一个存储会话内容的结构体,如下所示:

static MbedTLSSession *tls_session = RT_NULL;
tls_session = (MbedTLSSession *)malloc(sizeof(MbedTLSSession));

tls_session->host = strdup(MBEDTLS_WEB_SERVER);
tls_session->port = strdup(MBEDTLS_WEB_PORT);
tls_session->buffer_len = MBEDTLS_READ_BUFFER;
tls_session->buffer = malloc(tls_session->buffer_len);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

这里需要设置 SSL/TLS 服务器的 host 和 port,以及数据接收 buffer 等配置。

初始化 SSL/TLS 客户端

应用程序使用 mbedtls_client_init 函数初始化 TLS 客户端。

初始化阶段按照 API 参数定义传入相关参数即可,主要用来初始化网络接口、证书、SSL 会话配置等 SSL 交互必须的一些配置,以及设置相关的回调函数。

示例代码如下所示:

char *pers = "hello_world"; // 设置随机字符串种子
if((ret = mbedtls_client_init(tls_session, (void *)pers, strlen(pers))) != 0)
{rt_kprintf("MbedTLSClientInit err return : -0x%x\n", -ret);goto __exit;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

实际调用的 mbedtls 库函数如下所示:

初始化 SSL/TLS 客户端上下文

应用程序使用 mbedtls_client_context 函数配置客户端上下文信息,包括证书解析、设置主机名、设置默认 SSL 配置、设置认证模式(默认 MBEDTLS_SSL_VERIFY_OPTIONAL)等。

示例代码如下所示:

if((ret = mbedtls_client_context(tls_session)) < 0)
{rt_kprintf("MbedTLSCLlientContext err return : -0x%x\n", -ret);goto __exit;
}
  • 1
  • 2
  • 3
  • 4
  • 5

建立 SSL/TLS 连接

使用 mbedtls_client_connect 函数为 SSL/TLS 连接建立通道。这里包含整个的握手连接过程,以及证书校验结果。

示例代码如下所示:

if((ret = mbedtls_client_connect(tls_session)) != 0)
{rt_kprintf("MbedTLSCLlientConnect err return : -0x%x\n", -ret);goto __exit;
}
  • 1
  • 2
  • 3
  • 4
  • 5

读写数据

向 SSL/TLS 中写入数据

示例代码如下所示:

static const char *REQUEST = "GET https://www.howsmyssl.com/a/check HTTP/1.0\r\n""Host: www.howsmyssl.com\r\n""User-Agent: rtthread/3.1 rtt\r\n""\r\n";

while((ret = mbedtls_client_write(tls_session, (const unsigned char *)REQUEST,strlen(REQUEST))) <= 0)
{
if(ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE)
{
rt_kprintf(“mbedtls_ssl_write returned -0x%x\n”, -ret);
goto __exit;
}
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

从 SSL/TLS 中读取数据

示例代码如下所示:

memset(tls_session->buffer, 0x00, tls_session->buffer_len);
ret = mbedtls_client_read(tls_session, (unsigned char *)tls_session->buffer,len);
if(ret == MBEDTLS_ERR_SSL_WANT_READ || ret ==MBEDTLS_ERR_SSL_WANT_WRITE)continue;

if(ret MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY)
break;
if(ret < 0)
{
rt_kprintf(“mbedtls_ssl_read returned -0x%x\n”, -ret);
break;
}
if(ret 0)
{
rt_kprintf(“connection closed\n”);
break;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

注意,如果读写接口返回了一个错误,必须关闭连接。

关闭 SSL/TLS 客户端连接

客户端主动关闭连接或者因为异常错误关闭连接,都需要使用 mbedtls_client_close 关闭连接并释放资源。

示例代码如下所示:

mbedtls_client_close(tls_session);
  • 1
  • 2

mbedtls 使用范式

参考示例程序 samples/tls_app_test.c

添加新证书

CA 证书有两种常用格式 PEM 格式DER 格式,目前 RT-Thread mbedtls 仅支持 PEM 格式 的证书文件。

  • PEM 格式证书

    PEM 格式证书 通常是以 .pem.cer 后缀名结尾的文件。

    使用文本编辑器打开后,文件内容以 -----BEGIN CERTIFICATE----- 开头,以 -----END CERTIFICATE----- 结尾。

  • DER 格式证书

    DER 格式证书 是二进制文件类型。

根证书样式

双击 .cer 后缀名结尾的 CA 文件(Windows系统)可以看到证书的签发机构和有效期,如下图所示:

PEM 格式 格式的证书文件内容内容样式如下所示:

-----BEGIN CERTIFICATE-----
MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG
A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv
b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw
MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i
YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT
aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ
jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp
xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp
1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG
snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ
U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8
9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E
BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B
AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz
yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE
38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP
AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad
DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME
HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==
-----END CERTIFICATE-----
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

获取根证书

  • 直接向服务商索取

    向服务商索取 base64 编码 X.509 编码的 PEM 格式 证书文件。

  • 从服务商网站导出

    • 浏览器打开服务商网站,以 https://www.rt-thread.org/ 为例

    • 点击浏览器地址栏的 安全,然后点击证书

    • 查看证书详细信息

    • 根证书导出向导

    • 选择导出 Base64 编码证书

    • 选择证书存储位置

    • 完成证书文件导出

      完成证书导出,假设证书文件名为 USER_ROOT_CA.cer

导入证书

  • 使用文本编辑器打开上个步骤导出的根证书文件 USER_ROOT_CA.cer
  • 拷贝 USER_ROOT_CA.cer 文件到 certs 根目录
  • 使用 scons 命令重新编译

注:

scons 命令编译后,会自动将证书文件拷贝到 const char mbedtls_root_certificate[] 数组中。

2.工作原理

mbedtls** 软件包是对 SSL/TLS 协议的实现。SSL(安全套接层)和 TLS(传输安全层)均是为了保证传输过程中信息的安全,是在明文传输基础上进行的加密,然后以密文的形式传输数据。

mbedTLS 建立安全通信连接需要经过以下几个步骤:

  • 初始化 SSL/TLS 上下文
  • 建立 SSL/TLS 握手
  • 发送、接收数据
  • 交互完成,关闭连接

其中,最关键的步骤就是 SSL/TLS 握手 连接的建立,这里需要进行证书校验。
SSL/TLS 握手流程

DTLS 握手流程

为了避免拒绝服务攻击,DTLS采用和IKE一样的无状态 cookie 技术。当客户端发送 client hello 消息后,服务器发送 HelloVerifyRequest 消息,这个消息包含了无状态 cookie。客户端收到之后必须重传添加上了 cookie 的 clienthello。

DTLS 握手流程如下图所示:

3.示例程序

该示例程序提供了一个简单的 TLS client,与测试网站建立 TLS 连接并获取加密数据。

示例文件

示例程序路径 说明
samples/tls_app_test.c TLS 测试例程
例程工作流程
本例程使用了 RT-Thread 官方 TLS 测试网站 www.rt-thread.org,使用 mbedtls_client_write 函数发送 HTTP 测试请求,成功后,该网站会返回文本数据,测试例程将解析后的数据输出到控制台。

例程使用的 HTTP 请求数据如下所示

"GET /download/rt-thread.txt HTTP/1.0\r\n"
"Host: www.rt-thread.org\r\n"
"User-Agent: rtthread/3.1 rtt\r\n"
"\r\n";
  • 1
  • 2
  • 3
  • 4

mbedTLS 测试例程的基本工作流程如下所示

client连接测试网站 www.rt-thread.org

client 和 server 握手成功

client 发送请求

server 回应请求

TLS 测试成功/失败

同步设备时间

SSL/TLS 服务器进行证书校验的过程中,会对当前发起校验请求的时间进行认证,如果时间不满足服务器的要求,就会校验证书失败。因此,我们需要为设备同步本地时间。

  • 方式一: 使用 date 命令

    未同步过时间的设备输入 date 命令后如下所示:

msh />date
Thu Jan  1 00:00:06 1970
  • 1
  • 2

使用 date 设置当前时间,如下所示:

msh />date 2018 08 02 12 23 00
msh />date
Thu Aug  2 12:23:01 2018
msh />
  • 1
  • 2
  • 3
  • 4
  • 方式二: 使用 NTP 同步网络时间

    该方式需要依赖 NTP 工具包,使用 menuconfig 配置获取,如下所示:

RT-Thread online packages --->IoT - internet of things  --->-*- netutils: Networking utilities for RT-Thread  --->-*-   Enable NTP(Network Time Protocol) client(8)     Timezone for calculate local time(cn.ntp.org.cn) NTP server name
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

使用命令 ntp_sync 同步网络时间

msh />ntp_sync
Get local time from NTP server: Thu Aug  2 14:31:30 2018
The system time is updated. Timezone is 8.
msh />date
Thu Aug  2 14:31:34 2018
  • 1
  • 2
  • 3
  • 4
  • 5

启动例程

在 MSH 中使用命令 tls_test 执行示例程序,成功建立 TLS 连接后,设备会从服务器拿到一组密码套件,设备 log 如下所示:

msh />tls_test
MbedTLS test sample!
Memory usage before the handshake connection is established:
total memory: 33554408
used memory : 20968
maximum allocated memory: 20968
Start handshake tick:3313
[tls]mbedtls client struct init success...
[tls]Loading the CA root certificate success...
[tls]mbedtls client context init success...
msh />[tls]Connected www.rt-thread.org:443 success...
[tls]Certificate verified success...
Finish handshake tick:6592
MbedTLS connect success...
Memory usage after the handshake connection is established:
total memory: 33554408
used memory : 45480
maximum allocated memory: 50808
Writing HTTP request success...
Getting HTTP response...
HTTP/1.1 200 OK
Server: nginx/1.10.3 (Ubuntu)
Date: Fri, 31 Aug 2018 08:29:24 GMT
Content-Type: text/plain
Content-Length: 267
Last-Modified: Sat, 04 Aug 2018 02:14:51 GMT
Connection: keep-alive
ETag: "5b650c1b-10b"
Strict-Transport-Security: max-age=1800; includeSubdomains; preload
Accept-Ranges: bytes

RT-Thread is an open source IoT operating system from China, which has strong scalability: from a tiny kernel running on a tiny core, for example ARM Cortex-M0, or Cortex-M3/4/7, to a rich feature system running on MIPS32, ARM Cortex-A8, ARM Cortex-A9 DualCore etc.

MbedTLS connection close success.

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

4.常见问题

证书验证失败

[tls]verification info: ! The CRL is not correctly signed by the trusted CA
  • 1
  • 2
  • 原因

    mbedtls 包中支持多种主流 CA 机构根证书,部分 CA 机构未支持

  • 解决方法

    若测试其他 TLS 网站证书验证失败,手动获取测试网站根证书(Root Cerificate)添加到mbedtls/tls_cerificate.c文件中

证书时间错误

[tls]verify peer certificate fail....
[tls]verification info:   ! The certificate validity starts in the future
  • 1
  • 2
  • 3
  • 原因

    TLS 握手是证书验证需要时间的验证,本地时间获取有误导致

  • 解决方式

    检查 RTC 设备是否支持,检查 RT_USING_RTC 宏是否打开,校准设备时间。建议使用 NTP 同步本地时间。

证书 CN 错误

    verification info: ! The certificate Common Name (CN) does not match with the expected CN
  • 1
  • 2
  • 原因

    测试其他 TLS 网站时,若输入域名不符合证书的 Common Name(CN)出现 CN 验证失败问题

  • 解决方法

    检查输入域名和证书中 CN 是否匹配或输入 IP 地址

0x7200 错误

  • 原因

    部分原因是因为 mbedTls 收到了大于缓冲区大小的数据包

  • 解决方法

    menuconfig 配置增加数据帧大小 ( Maxium fragment length in bytes )

RT-Thread online packages --->security packages  --->Select Root Certificate  --->      # 选择证书文件[*] mbedtls: An portable and flexible SSL/TLS library  ---[*]   Store the AES tables in ROM(2)   Maximum window size used(6144) Maxium fragment length in bytes # 配置数据帧大小(0x7200 错误可尝试增加该大小)[*]   Enable a mbedtls client exampleversion (latest)  --->
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

参考

  • mbedTLS官方网站:https://tls.mbed.org/
  • ARMmbed GitHub:mbedtls

转:mbedtls学习2.mbedtls从0使用指南相关推荐

  1. mbedtls学习2.mbedtls从0使用指南

    1.使用指南 这里主要介绍 mbedtls 程序的基本使用流程,并针对使用过程中经常涉及到的结构体和重要 API 进行简要说明. mbedtls 的基本工作流程如下所示: 初始化 SSL/TLS 上下 ...

  2. 飞桨深度学习开源框架2.0抢先看:成熟完备的动态图开发模式

    百度飞桨于近期宣布,深度学习开源框架2.0抢先版本正式发布,进入2.0时代.其中一项重大升级,就是推出更加成熟完备的命令式编程模式,即通常说的动态图模式.同时在该版本中将默认的开发模式定为动态图模式, ...

  3. python语言入门p-MicropPython的学习,如何从0到1?

    是Java还是C++ ? 是Go还是Python? 你为你最爱或者最熟悉的编程语言打Call了吗? Python在国外是一门非常热门的编程语言,近些年在国内也被发掘与追捧, Python的开放.简洁. ...

  4. [深度学习] 分布式Tensorflow 2.0 介绍(二)

    [深度学习] 分布式模式介绍(一) [深度学习] 分布式Tensorflow 2.0介绍(二) [深度学习] 分布式Pytorch 1.0介绍(三) [深度学习] 分布式Horovod介绍(四) 一 ...

  5. 数据结构与算法学习笔记之 从0编号的数组

    数据结构与算法学习笔记之 从0编号的数组 前言 数组看似简单,但掌握精髓的却没有多少:他既是编程语言中的数据类型,又是最基础的数据结构: 一个小问题: 为什么数据要从0开始编号,而不是 从1开始呢? ...

  6. java学习_Python基础学习教程:从0学爬虫?让爬虫满足你的好奇心

    Python基础学习教程:从0学爬虫?让爬虫满足你的好奇心 有必要学爬虫吗? 我想,这已经是一个不需要讨论的问题了. 爬虫,"有用"也"有趣"! 这个数据为王的 ...

  7. MicropPython的学习,如何从0到1?

    是Java还是C++ ? 是Go还是Python? 你为你最爱或者最熟悉的编程语言打Call了吗? Python在国外是一门非常热门的编程语言,近些年在国内也被发掘与追捧, Python的开放.简洁. ...

  8. 《果壳中的C# C# 5.0 权威指南》 (09-26章) - 学习笔记

    <果壳中的C# C# 5.0 权威指南> ========== ========== ========== [作者] (美) Joseph Albahari (美) Ben Albahar ...

  9. Unity DOTS 学习笔记1 - ECS 0.50介绍和安装

    Unity DOTS 学习笔记1 - ECS 0.50介绍和安装 为什么学习这个技术 ECS的全称为Entity Component System,是最早由暴雪在GDC2017上提出的一个新的游戏设计 ...

最新文章

  1. 川崎机器人示教盒维修_专业维修丹阳市KUKA库卡KRC2库卡C4主板维修{苏州罗韦维修}...
  2. 记录一下增加标定评价标准的过程
  3. Kafka Zero-Copy 使用分析
  4. iOS 开发 需要的版本管理工具,UI图,bug管理工具等
  5. 路飞学城Python-Day9
  6. windows使用nginx实现网站负载均衡测试实例
  7. AutoPep8-----Pycharm自动排版工具
  8. php 二分查找字符串,php实现的二分查找算法示例
  9. php new static,PHP中new static()与new self()的区别异同分析
  10. [译]其实闭包并不高深莫测
  11. Python数据结构与算法笔记(八):数据结构——树,二叉树和AVL树
  12. jQuery源码解析之on事件绑定
  13. 远程win10系统桌面时提示凭证不工作问题的终极解决办法
  14. ModBus RTU协议CRC校验方式最简实现
  15. 【使用工具和软件汇总】
  16. paypal java_PaypalUtil PayPal付款JAVA工具类
  17. 基于springboot的美食点评APP设计与实现
  18. adobe清理工具_Adobe终于通过其新的渐变工具实现了这一点-UX评论
  19. C++游戏game | 井字棋游戏坤坤版(配资源+视频)【赋源码,双人对战】
  20. PHP指定日期(时间戳转换)

热门文章

  1. 目标检测YOLO实战应用案例100讲-基于YOLO模型的遥感影像 飞机目标检测技术研究
  2. 初阶指针(纯干货!!!)
  3. 使用SecureCRT连接华为设备串口设置
  4. vue 倒计时 插件_Vue学习笔记-倒计时插件
  5. 红领巾小创客机器人活动计划_环市西路小学:红领巾小创客社团活动总结
  6. websocket的简介与应用
  7. Linux驱动之oops错误:addr2line工具定位错误
  8. COMSOL中“模式分析”、“边界模式分析”的区别
  9. Idea 如何安装插件
  10. Vmware虚拟机ip为127.0.0.1的解决办法,修改虚拟机IP的详细步骤