目录

在云服务器上安装emqx+wss并使用ESP32连接

本文介绍如何在云服务器上,使用docker安装emqx,搭建基于websockets的mqtt服务并使用ESP32进行连接。

获取并制作证书

获取SSL证书

若要为emqx启用加密的mqttswss协议,需要SSL证书。通常情况下,有两种渠道取得SSL证书:自签证书与向CA申请证书。使用自签证书,不需要向CA申请证书,但是需要为每个客户端安装CA根证书,而CA的根证书预先安装在了设备上,因此使用CA签发的证书不需要在客户端上安装证书(对于例如ESP32的嵌入式设备仍需要)。

由于我的域名在阿里云上,并且阿里云提供免费的SSL证书,因此在此我选择使用CA签发的证书。后文的证书配置流程也以阿里云的免费证书以及域名iot.yuanze.wang为例,具体的申请过程在此不再赘述。

在申请到证书后,前往阿里云SSL证书控制台下载证书,下载格式请选择Apache。由于emqx的CA证书需要完整的证书链,因此还需额外下载根证书。对于阿里云免费SSL证书来说,需要下载DigiCert OV和DV根证书

https://img.yuanze.wang/posts/emqx-wss-esp32/download-certs.jpg
从阿里云下载SSL证书

下载完成后,将解压后的网站证书与根证书放到同一个文件夹中,可以发现文件夹中共有4个文件,它们分别是:

  • iot.yuanze.wang.key:网站证书的私钥文件
  • iot.yuanze.wang_public.crt:网站证书的公钥文件
  • iot.yuanze.wang_chain.crt:中间CA机构Encryption Everywhere的证书
  • Digicert-OV-DV-root.cer:顶级CADigicert的根证书

制作emqx要求的证书

上文中提到,emqx需要CA的完整证书链。对于我的免费SSL证书来说,其证书链为Digicert->Encryption Everywhere->iot.yuanze.wang,CA证书链即为去掉iot.yuanze.wang后的前两个。

https://img.yuanze.wang/posts/emqx-wss-esp32/certs-chain.png
证书链

emqx需要三个证书文件,cacert.pem cert.pemkey.pem ,它们分别为CA证书链、网站证书的公钥与私钥。因此,直接将iot.yuanze.wang.key重命名为key.pem(若从其他途径申请证书,需要注意私钥格式为RSA,可以打开私钥文件后,确认其第一行是否为BEGIN RSA PRIVATE KEY),将iot.yuanze.wang_public.crt重命名为cert.pem。接下来,分别用文本编辑器打开Digicert-OV-DV-root.ceriot.yuanze.wang_chain.crt,将其中内容合并成一个文件,命名为cacert.pem

-----BEGIN CERTIFICATE-----
MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEqjCCA5KgAwIBAgIQAnmsRYvBskWr+YBTzSybsTANBgkqhkiG9w0BAQsFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
QTAeFw0xNzExMjcxMjQ2MTBaFw0yNzExMjcxMjQ2MTBaMG4xCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xLTArBgNVBAMTJEVuY3J5cHRpb24gRXZlcnl3aGVyZSBEViBUTFMgQ0EgLSBH
MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALPeP6wkab41dyQh6mKc
oHqt3jRIxW5MDvf9QyiOR7VfFwK656es0UFiIb74N9pRntzF1UgYzDGu3ppZVMdo
lbxhm6dWS9OK/lFehKNT0OYI9aqk6F+U7cA6jxSC+iDBPXwdF4rs3KRyp3aQn6pj
pp1yr7IB6Y4zv72Ee/PlZ/6rK6InC6WpK0nPVOYR7n9iDuPe1E4IxUMBH/T33+3h
yuH3dvfgiWUOUkjdpMbyxX+XNle5uEIiyBsi4IvbcTCh8ruifCIi5mDXkZrnMT8n
wfYCV6v6kDdXkbgGRLKsR4pucbJtbKqIkUGxuZI2t7pfewKRc5nWecvDBZf3+p1M
pA8CAwEAAaOCAU8wggFLMB0GA1UdDgQWBBRVdE+yck/1YLpQ0dfmUVyaAYca1zAf
BgNVHSMEGDAWgBQD3lA1VtFMu2bwo+IbG8OXsj3RVTAOBgNVHQ8BAf8EBAMCAYYw
HQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMBIGA1UdEwEB/wQIMAYBAf8C
AQAwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp
Y2VydC5jb20wQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2NybDMuZGlnaWNlcnQu
Y29tL0RpZ2lDZXJ0R2xvYmFsUm9vdENBLmNybDBMBgNVHSAERTBDMDcGCWCGSAGG
/WwBAjAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BT
MAgGBmeBDAECATANBgkqhkiG9w0BAQsFAAOCAQEAK3Gp6/aGq7aBZsxf/oQ+TD/B
SwW3AU4ETK+GQf2kFzYZkby5SFrHdPomunx2HBzViUchGoofGgg7gHW0W3MlQAXW
M0r5LUvStcr82QDWYNPaUy4taCQmyaJ+VB+6wxHstSigOlSNF2a6vg4rgexixeiV
4YSB03Yqp2t3TeZHM9ESfkus74nQyW7pRGezj+TC44xCagCQQOzzNmzEAP2SnCrJ
sNE2DpRVMnL8J6xBRdjmOsC3N6cQuKuRXbzByVBjCqAA8t1L0I+9wXJerLPyErjy
rMKWaBFLmfK/AHNF4ZihwPGOc7w6UHczBZXH5RFzJNnww+WnKuTPI0HfnVH8lg==
-----END CERTIFICATE-----

然后,将上述3个证书文件上传到云服务器上。本文中我将证书上传到/home/docker/emqx-certs目录下。

wangyz@aliyun-host:/home/docker/emqx-certs$ tree .
.
├── cacert.pem
├── cert.pem
└── key.pem

配置emqx

用下面的命令启动emqx容器。

docker run -d --name emqx -p 1883:1883 -p 8083:8083 -p 8084:8084 -p 8883:8883 -p 18083:18083 -v /home/docker/emqx-certs:/opt/emqx/etc/certs --restart always emqx/emqx:4.3.5

emqx默认将1883端口用于基于TCP的mqtt协议,8883端口用于基于TCP及SSL加密的mqtts协议;80838084端口则用于websocket与启用了TLS的websockets协议。除此之外,18083端口用于web管理页面,因此在启动容器时需要开启上述端口。如果不需要上述协议中的某个协议,也可以不映射对应的端口。同时,上述命令还将存放上传证书的目录挂载到了容器对应的目录中。

开启认证插件

由于后文将要禁止匿名登录,因此需要首先选择一个认证插件。emqx提供了多种认证插件,在这里仅配置最简单的mnesia认证。它无需数据库,可以通过预设定的账户与密码来实现身份认证。但是该插件默认是禁用状态,要想实现认证功能,必须将其启用。emqx集成了一个web管理页面,通过web管理界面,可以快速的启用该插件。

访问iot.yuanze.wang:18083,打开web管理页面,初始账户为admin,初始密码为public。进入后,点击右侧插件选项卡,找到emqx_auth_mnesia插件,点击右侧的启用按钮,即可启用mnesia认证功能。同时,还可以在通用-用户选项卡中,点击admin右侧的编辑按钮对管理员默认密码进行修改。

https://img.yuanze.wang/posts/emqx-wss-esp32/enable-plugin.png
启用mnesia认证插件

修改emqx的配置文件

为了对emqx进行配置,需要先进入docker容器。

docker exec -it emqx bash

进入容器后,默认目录为/opt/emqx。此目录下的etc目录存放了配置文件,emqx的主配置文件为etc/emqx.conf文件,使用编辑器打开该文件。

vi etc/emqx.conf
功能 设置项
关闭匿名登录 allow_anonymous = false
启用mqtts的CA证书链 listener.ssl.external.cacertfile = etc/certs/cacert.pem
启用wss的CA证书链 listener.wss.external.cacertfile = etc/certs/cacert.pem

修改完上述配置后,保存退出。

接下来为mnesia认证插件配置一个默认密码。尽管官方不建议直接将明文密码写在配置文件中,但对于个人调试用途的服务器来说已经足够使用了。插件的配置文件为etc/plugins/emqx_auth_mnesia.conf,使用编辑器打开该文件,取消auth.user.1.usernameauth.user.1.password的注释,并在后面填写用户名与密码即可。

执行完上述配置后,按Ctrl + D退出容器。然后,重启容器。

docker restart emqx

测试服务器

配置完成后,便可以尝试使用mqtt客户端对配置好的服务器进行测试,例如MQTTX软件。

https://img.yuanze.wang/posts/emqx-wss-esp32/mqttx.png
服务器参数

对服务器参数进行配置后,点击连接按钮,若配置正确,将可以正确连接至服务器。发布并订阅同一个topic后,可以收到信息,证明服务器工作正常。

https://img.yuanze.wang/posts/emqx-wss-esp32/mqttx-test.png
测试服务器

使用ESP32连接服务器

乐鑫在esp-idf中已经提供了基于wss的连接例程,在esp-idf/examples/protocols/mqtt/wss下,本文下面的实验也基于该例程。

首先,在idf.py menuconfig中的Example Connection Configuration设置中,配置好WiFi的SSID与密码。然后,打开源码目录的main/app_main.c文件,找到其中对mqtt连接信息的声明部分。

const esp_mqtt_client_config_t mqtt_cfg = {
   .uri = CONFIG_BROKER_URI,
   .cert_pem = (const char *)mqtt_eclipseprojects_io_pem_start,
};

将其改为实际的服务器信息。在源码中更改CONFIG_BROKER_URI后,可以不用在menuconfig中更改服务器的地址。

const esp_mqtt_client_config_t mqtt_cfg = {
   .uri = "wss://iot.yuanze.wang:8084/mqtt",
   .username = "xxxxx",
   .password = "xxxxx",
   .cert_pem = (const char *)mqtt_eclipseprojects_io_pem_start,
};

由于嵌入式系统中并没有根证书,还需要将SSL根证书加入到程序之中。找到main/qtt_eclipseprojects_io.pem文件,将前述的Digicert-OV-DV-root.cer根证书中的所有内容替换到该文件中,然后编译并下载。

I (2078) esp_netif_handlers: example_connect: sta ip: 192.168.2.215, mask: 255.255.255.0, gw: 192.168.2.1
I (2078) example_connect: Got IPv4 event: Interface "example_connect: sta" address: 192.168.2.215
I (2088) example_connect: Connected to example_connect: sta
I (2088) example_connect: - IPv4 address: 192.168.2.215
I (2098) MQTTWSS_EXAMPLE: [APP] Free memory: 246972 bytes
I (2108) MQTTWSS_EXAMPLE: Other event id:7
W (2118) wifi:<ba-add>idx:1 (ifx:0, 88:c3:97:50:05:6a), tid:0, ssn:4, winSize:64
I (3908) MQTTWSS_EXAMPLE: MQTT_EVENT_CONNECTED
I (3918) MQTTWSS_EXAMPLE: sent subscribe successful, msg_id=31044
I (3918) MQTTWSS_EXAMPLE: sent subscribe successful, msg_id=6202
I (3918) MQTTWSS_EXAMPLE: sent unsubscribe successful, msg_id=2784
I (3988) MQTTWSS_EXAMPLE: MQTT_EVENT_SUBSCRIBED, msg_id=31044
I (3988) MQTTWSS_EXAMPLE: sent publish successful, msg_id=0
I (3998) MQTTWSS_EXAMPLE: MQTT_EVENT_SUBSCRIBED, msg_id=6202
I (3998) MQTTWSS_EXAMPLE: sent publish successful, msg_id=0
I (4008) MQTTWSS_EXAMPLE: MQTT_EVENT_UNSUBSCRIBED, msg_id=2784
I (4068) MQTTWSS_EXAMPLE: MQTT_EVENT_DATA
TOPIC=/topic/qos0
DATA=data
I (4068) MQTTWSS_EXAMPLE: MQTT_EVENT_DATA
TOPIC=/topic/qos0
DATA=data

从日志中可以看到,ESP32已经成功连接上WiFi,并连接上了mqtt服务器,向/topic/qos0发布了内容为data的消息,之后订阅了/topic/qos0并收到了data。此时,通过MQTTX软件同样可以向ESP32发布消息。

https://img.yuanze.wang/posts/emqx-wss-esp32/mqttx-to-esp32.png
从MQTTX发送消息到ESP32