本文旨在介绍如何在使用Arduino内核的ESP32上创建一个简单的websocket客户端。我们将用它来联系一个远程测试websocket服务器。本ESP32教程的测试是使用集成在ESP32开发板中的DFRobot的ESP-WROOM-32设备进行的。
为了不必关注websocket协议的底层细节,我们将使用一个辅助库文件来帮助我们处理这些细节。
您可以点击此处查看库文件的GitHub页面。请注意,这是一个ESP8266库文件,在撰写本文时,ESP32还没有官方支持的库文件。尽管如此,通过一些更改,我们可以在ESP32上运行此库文件。
遗憾的是,Arduino IDE库管理器尚不提供此库文件。因此,我们需要手动安装。
为此,我们需要下载源代码,单击GitHub页面顶部“Clone or download(拷贝或下载)”按钮,然后单击“Download ZIP(下载压缩文件)”按钮即可获得源代码。图1中突出显示了这些按钮。
在下载的“.zip”文件中有一个名为ESP8266-Websocket-master的文件夹。将此文件夹解压并重命名为ESP8266-Websocket。
现在,我们需要手动将解压和重命名的文件夹放在其他Arduino库文件的同一目录中。通常而言,这些库文件位于Windows用户文档(Documents)中名为Arduino的文件夹中。例如,在此示例中,库文件的存储地址为C:\ Users \ MyUsername \ Documents \ Arduino \ libraries。
在找到Arduino库文件夹后,只需将ESP8266-Websocket文件夹粘贴到其中即可。
在继续编程之前,我们需要更改ESP8266-Websocket文件夹中某些函数文件的名称。如果未执行这些更改,那么在尝试编译ESP32的代码时,我们将收到函数名称冲突导致的相关错误。
因此,为了避免该错误,我们需要接受ESP32论坛帖子中的一些建议。按照这些建议,我们需要打开ESP8266-Websocket文件夹中的MD5.c和MD5.h文件并重命名以下函数:
将MD5Init更改为MD5InitXXX
将MD5Update更改为MD5UpdateXXX
将MD5Final更改为MD5FinalXXX
请注意,函数名末尾的后缀XXX可以是其他内容,只要不使用原始名称就可以,使用原始名称会与其他定义相冲突。但无论怎样命名,我们都必须在.c和.h的文件中使用相同的新名称。
最后,保存更改并打开Arduino IDE,我们应该能够从该库中找到示例,如图2所示。
接下来我们将分析以下代码,这些基于WebSocletClient_Demo的代码用于通过ESP32与服务器建立连接,我强烈建议您仔细了解一下WebSocletClient_Demo。其使用示例也可以从此处的GitHub页面获得。
我们将从导入一些库文件开始编写代码。首先,我们需要WiFi.h库文件,它用于连接WiFi网络。此外,我们还需要引用WebSocketClient.h库文件,以便可以访问连接服务器所需的所有函数。
#include
#include
接下来,我们将把连接WiFi网络所需的证书放入两个全局变量。网络名称(ssid)和密码是必需的信息。
const char* ssid = "YourNetworkName";
const char* password = "YourNetworkPassword;
随后,我们需要指定连接的主机以及使用的URL路径。为简单起见,我们将使用在线回显 websocket服务器,它可以返回我们先前发送的内容。这样我们就可以测试我们的客户端,而无需自己设置websocket服务器。
因此,我们可将服务器主机设置为demos.kaazing.com,URL路径设置为/ echo。您可以点击此处测试此服务器。
char path[] = "/echo";
char host[] = "demos.kaazing.com";
我们还需要一个WebSocketClient类对象,用于调用与服务器交互所需的函数。最后,我们需要一个WiFiClient类对象,用于在后台为WebSocketClient对象提供支持。
WebSocketClient webSocketClient;
WiFiClient client;
现在,我们将使用Arduino setup函数执行初始化任务。按照惯例,我们将首先建立一个串口连接以输出程序的结果。接下来,我们将把ESP32连接到一个WiFi网络。关于如何建立网络连接的详细信息,请参考先前的教程内容。
Serial.begin(115200);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
delay(5000);
在连接WiFi网络之后,我们将通过调用WiFiClient对象的connect方法与主机建立TCP连接。
此方法的第一个参数是一个字符串,它表示我们尝试访问的主机(我们在先前已将其声明为全局变量),第二个参数是正在侦听的端口编号。我们使用的测试服务器正在侦听端口80,即默认的HTTP端口。
我们还引用了对此连接尝试的错误检查程序。如果连接失败,程序将向串行端口打印一条消息,但程序仍将继续运行并最终失败。我们这样做是为了简化测试过程,因为我们处于受控的测试环境中。对于在实际应用中使用的弹性程序,我们应该引用更强大的错误检查程序,并在检测到错误时采取相应的措施。
if(client.connect(host, 80)) {
Serial.println("Connected");
}else {
Serial.println("Connection failed.");
}
接下来我们需要执行websocket handshake握手。可通过调用之前声明的WebSocketClient对象的handshake方法来完成握手。此方法的输入参数为WiFiClient对象,利用该对象与服务器通信。
如需了解更多关于握手期间的细节信息,可以点击此处查看源代码。重要的是,它将向服务器发送HTTP请求以升级websocket连接。
此升级很有必要,这是因为服务器可以利用websocket协议通过相同的端口与HTTP和websocket客户端进行通信[1]。因此,客户端需要指定通信对象为websocket。
在调用handshake方法之前,我们需要为WebSocketClient的路径和主机数据分配全局变量,这些先前定义的全局变量可用于表示主机及URL路径。
webSocketClient.path = path;
webSocketClient.host = host;
if (webSocketClient.handshake(client)) {
Serial.println("Handshake successful");
} else {
Serial.println("Handshake failed.");
}
完整的setup函数如下所示。
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
delay(5000);
if (client.connect(host, 80)) {
Serial.println("Connected");
} else {
Serial.println("Connection failed.");
}
webSocketClient.path = path;
webSocketClient.host = host;
if (webSocketClient.handshake(client)) {
Serial.println("Handshake successful");
} else {
Serial.println("Handshake failed.");
}
}
让我们开始编写主循环程序。现在,我们将编写程序向服务器发送和接收数据。首先,我们需要声明一个String类型的变量,该变量将用作从服务器接收的内容的数据缓冲区。
String data;
接下来,我们将检查客户端与服务器是否仍然处于连接状态。我们之前使用WiFiClient对象连接了服务器。 因此,我们将通过调用其connected方法检查连接状态是否正常,该方法不带参数,如果连接仍处于活动状态,则返回true,否则返回false。
在初始化WebSocketClient之后使用WiFiClient对象似乎很奇怪。我们可以将WebSocketClient视为WiFiClient上层的封装包,用于处理websocket协议的特定任务。因此,检查连接是否仍然可用是较低层级的函数,所以我们可以直接使用WiFiClient对象。
如果客户端与服务器仍然处于活跃状态,我们将向其发送一些数据。为此,我们只需调用WebSocketClient对象的sendData方法,将包含发送内容的字符串作为其输入参数。此函数的返回值为空(void)。
webSocketClient.sendData("Info to be echoed back");
如前所述,我们访问的服务器将简单地回显我们发送给它的数据。因此,我们可以从客户端获取数据,它应该与我们之前发送的内容完全匹配。
为了从服务器获取数据,我们调用了getData函数,将字符串缓冲区作为其输入参数,服务器的返回内容将被存放在此字符串缓冲区中。
webSocketClient.getData(data);
随后,如果返回内容的长度大于0,我们可以简单地打印出接收的数据。完整的主循环函数如下所示,其中包含了打印内容。请注意,在每次迭代主循环函数之前,我们还添加了一个小小的延迟。
void loop() {
String data;
if (client.connected()) {
webSocketClient.sendData("Info to be echoed back");
webSocketClient.getData(data);
if (data.length() > 0) {
Serial.print("Received data: ");
Serial.println(data);
}
} else {
Serial.println("Client disconnected.");
}
delay(3000);
}
最终源代码如下所示。
#include
#include
const char* ssid = "YourNetworkName";
const char* password = "YourNetworkPassword;
char path[] = "/echo";
char host[] = "demos.kaazing.com";
WebSocketClient webSocketClient;
WiFiClient client;
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
delay(5000);
if (client.connect(host, 80)) {
Serial.println("Connected");
} else {
Serial.println("Connection failed.");
}
webSocketClient.path = path;
webSocketClient.host = host;
if (webSocketClient.handshake(client)) {
Serial.println("Handshake successful");
} else {
Serial.println("Handshake failed.");
}
}
void loop() {
String data;
if (client.connected()) {
webSocketClient.sendData("Info to be echoed back");
webSocketClient.getData(data);
if (data.length() > 0) {
Serial.print("Received data: ");
Serial.println(data);
}
} else {
Serial.println("Client disconnected.");
}
delay(3000);
}
为了测试此代码,只需编译并将其上传到ESP32即可。您将得到类似于图1的输出结果,发送至服务器的消息被返回并打印至串口控制台。
请注意,如果未按照“安装库文件”的说明重命名库函数,则无法编译ESP32代码。
注:本文作者是Nuno Santos,他是一位和蔼可亲的电子和计算机工程师,住在葡萄牙里斯本 (Lisbon)。
他写了200多篇有关ESP32、ESP8266的有用的教程和项目。涉及arduino、micropython、 Picoweb、Espruino、Bluetooth、RFID、IDF……等等非常广泛,说是最全的完全不为过。
精华教程:
ESP32 MicroPython教程:uPyCraft IDE入门
ESP32 MicroPython教程:解析JSON
ESP32 MicroPython教程:MicroPython支持
ESP32 MicroPython教程:连接Wi-Fi网络
ESP32 / ESP8266 MicroPython教程:自动连接WiFi
ESP32 / ESP8266 MicroPython教程:从文件系统运行脚本
ESP32 / ESP8266 MicroPython教程:HTTP GET请求
ESP32 Arduino教程:用于构建ESP32编译环境的Arduino IDE软件
ESP32 Arduino教程:FreeRTOS队列性能测试
ESP32 RFID教程:打印MFRC522固件版本
ESP32 Picoweb教程:获取请求的HTTP方法
……
还有更多教程: ESP32教程 合集
英文版 :ESP32 tutorial合集