添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
长情的楼房  ·  银河护卫队3 - ...·  2 年前    · 
想旅行的松球  ·  卧鱼2大结局 - 抖音·  2 年前    · 
千杯不醉的小刀  ·  特斯拉Model ...·  2 年前    · 

WebSocket和EventSource是HTML5开始提供的功能。WebSocket可以在单个TCP连接上进行全双工通讯;EventSource可以由服务器主动向客户端推送消息。两个功能可以大大提升web应用的数据交互的性能。这篇文章将介绍ESPAsyncWebServer库中这两个功能的使用方式。

本文中各例程演示均在ESP32中进行。

WebSocket

WebSocket使用并不复杂,除了正常的声明与初始化AsyncWebServer对象外,只需下面几步即可:

  • 声明 AsyncWebSocket 对象与URL;
  • 绑定 AsyncWebSocket 对象事件回调函数;
  • AsyncWebSocket 对象添加到服务器中;

使用下面代码进行测试:

#include <WiFi.h>
#include <ESPAsyncWebServer.h> //引入相应库
const char *ssid = "********";
const char *password = "********";
// 以下为网页文件
String indexhtml = String("") +
                   "<!DOCTYPE html> \n" +
                   "<html> \n" +
                   "<head> \n" +
                   "    <meta charset=\"UTF-8\"> \n" +
                   "    <title>WebSocket Test</title> \n" +
                   "    <script> \n" +
                   "        var ws; \n" +
                   "        window.onload = function () { \n" +
                   "            if (\"WebSocket\" in window) { \n" +
                   "                ws = new WebSocket(\"ws://\" + window.location.host + \"/\"); // 建立WebSocket连接 \n" +
                   "                ws.onopen = function () { // 连接建立成功时触发 \n" +
                   "                    document.getElementById(\"info\").innerHTML += \"WebSocket连接成功!\" + \"<br>\"; \n" +
                   "                    ws.send(\"connect ok!\"); // 向服务器发送数据 \n" +
                   "                }; \n" +
                   "                ws.onmessage = function (evt) { // 收到服务器数据时触发 \n" +
                   "                    document.getElementById(\"info\").innerHTML += evt.data + \"<br>\"; \n" +
                   "                }; \n" +
                   "                ws.onerror = function () { // 发生错误时触发 \n" +
                   "                    document.getElementById(\"info\").innerHTML += \"通讯发送错误!\" + \"<br>\"; \n" +
                   "                }; \n" +
                   "                ws.onclose = function () { // 连接关闭时触发 \n" +
                   "                    document.getElementById(\"info\").innerHTML += \"WebSocketTest连接已关闭!\" + \"<br>\"; \n" +
                   "                }; \n" +
                   "            } \n" +
                   "            else { \n" +
                   "                document.getElementById(\"info\").innerHTML = \"浏览器不支持 WebSocket!\"; \n" +
                   "            } \n" +
                   "        }; \n" +
                   "        function send() { \n" +
                   "            console.log(\'error\'); \n" +
                   "            ws.send(\"hahaha~~~\"); // 向服务器发送数据 \n" +
                   "        } \n" +
                   "    </script> \n" +
                   "</head> \n" +
                   "<body> \n" +
                   "    <button οnclick=\"send()\">点击向服务器发送数据</button> \n" +
                   "    <div id=\"info\"></div> \n" +
                   "</body> \n" +
                   "</html> \n";
AsyncWebServer server(80); // 声明WebServer对象
AsyncWebSocket ws("/"); // WebSocket对象,url为/
// WebSocket事件回调函数
void onEventHandle(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len)
  if (type == WS_EVT_CONNECT) // 有客户端建立连接
    Serial.printf("ws[%s][%u] connect\n", server->url(), client->id());
    client->printf("Hello Client %u !", client->id()); // 向客户端发送数据
    client->ping();                                    // 向客户端发送ping
  else if (type == WS_EVT_DISCONNECT) // 有客户端断开连接
    Serial.printf("ws[%s][%u] disconnect: %u\n", server->url(), client->id());
  else if (type == WS_EVT_ERROR) // 发生错误
    Serial.printf("ws[%s][%u] error(%u): %s\n", server->url(), client->id(), *((uint16_t *)arg), (char *)data);
  else if (type == WS_EVT_PONG) // 收到客户端对服务器发出的ping进行应答(pong消息)
    Serial.printf("ws[%s][%u] pong[%u]: %s\n", server->url(), client->id(), len, (len) ? (char *)data : "");
  else if (type == WS_EVT_DATA) // 收到来自客户端的数据
    AwsFrameInfo *info = (AwsFrameInfo *)arg;
    Serial.printf("ws[%s][%u] frame[%u] %s[%llu - %llu]: ", server->url(), client->id(), info->num, (info->message_opcode == WS_TEXT) ? "text" : "binary", info->index, info->index + len);
    data[len] = 0;
    Serial.printf("%s\n", (char *)data);
void setup()
  Serial.begin(115200);
  Serial.println();
  WiFi.mode(WIFI_STA);
  WiFi.setSleep(false);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED)




    

    delay(500);
    Serial.print(".");
  Serial.println("Connected");
  Serial.print("IP Address:");
  Serial.println(WiFi.localIP());
  ws.onEvent(onEventHandle); // 绑定回调函数
  server.addHandler(&ws);    // 将WebSocket添加到服务器中
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { //注册链接"/lambda"与对应回调函数(匿名函数形式声明)
    request->send(200, "text/html", indexhtml);                 //向客户端发送响应和内容
  });
  server.begin(); //启动服务器
  Serial.println("Web server started");
void loop()
  delay(2000);
  ws.textAll("lalala~~~"); // 向所有建立连接的客户端发送数据
  ws.cleanupClients();     // 关闭过多的WebSocket连接以节省资源

上面的代码直接拷贝可能网页的字符串部分代码会出现编码问题导致无法正确运行,该部分最好删除重新手打。如果想偷懒的话可以试试只删除 onclick 这个词的 o 然后手打输入。
在这里插入图片描述
上面就是一个使用WebSocket进行双向通讯的例子,所有的关键步骤都在上面代码中写了注释。可以看到使用WebSocket进行数据交互比传统的用Ajax的方式要方便多了。

WebSocket用于双向通讯,服务器端接收来自客户端的消息上面已经进行了演示,是在事件回调函数中进行的,同时在回调函数中也可以通过AsyncWebSocketClient对象发送消息,主要方式如下:

  • 格式化输出方式:
    size_t printf(const char *format, ...) __attribute__ ((format (printf, 2, 3)));
    size_t printf_P(PGM_P formatP, ...) __attribute__ ((format (printf, 2, 3))); // ESP32特有
  • 文本数据输出方式:
    void text(const char * message, size_t len);
    void text(const char * message);
    void text(uint8_t * message, size_t len);
    void text(char * message);
    void text(const String &message);
    void text(const __FlashStringHelper *data);
    void text(AsyncWebSocketMessageBuffer *buffer);
  • 二进制数据输出方式:
    void binary(const char * message, size_t len);
    void binary(const char * message);
    void binary(uint8_t * message, size_t len);
    void binary(char * message);
    void binary(const String &message);
    void binary(const __FlashStringHelper *data, size_t len);
    void binary(AsyncWebSocketMessageBuffer *buffer);

AsyncWebSocketClient对象除了可以用来发送数据,还可以获得一些信息 IPAddress remoteIP();uint16_t remotePort();,可以使用 void ping(uint8_t *data=NULL, size_t len=0); 方法向客户端发送ping信息,可以使用 void keepAlivePeriod(uint16_t seconds) 设置定时自动发送ping。最后可以用 void close(uint16_t code=0, const char * message=NULL); 方法关闭当前建立的连接。

除了上面客户端对象可以用来发送数据,AsyncWebSocket对象自身也可以发送数据,发送方法和客户端对象差不多,只不过第一个参数新增了客户端id:

  • size_t printf(uint32_t id, const char *format, ...) __attribute__ ((format (printf, 3, 4)));
    ……
  • void text(uint32_t id, const char * message, size_t len);
    ……
  • void binary(uint32_t id, const char * message, size_t len);
    ……

此外AsyncWebSocket对象还可以同时向全体已连接的客户端发送消息,方法和上面的差不多,主要是不需要id,然后方法名后跟上All:

  • size_t printfAll(const char *format, ...) __attribute__ ((format (printf, 2, 3)));
    ……
  • void textAll(const char * message, size_t len);
    ……
  • void binaryAll(const char * message, size_t len);
    ……

AsyncWebSocket对象除了上面这些发送消息的功能外还有一些其它方法,可以用 size_t count() const; 获取已连接客户端数量,可以用 bool hasClient(uint32_t id){ return client(id) != NULL; } 判断是否有指定客户端,可以用 void ping(uint32_t id, uint8_t *data=NULL, size_t len=0);void pingAll(uint8_t *data=NULL, size_t len=0); 发送ping信息。最后可以用 void close(uint32_t id, uint16_t code=0, const char * message=NULL);void closeAll(uint16_t code=0, const char * message=NULL); 关闭客户端连接。
另外对于AsyncWebSocket对象还有一个特殊的方法:void cleanupClients(uint16_t maxClients = DEFAULT_MAX_WS_CLIENTS);。该方法上面演示中也有用到,依据建立事件,从最早的开始关闭超出设定数量的客户端连接。(主要是芯片性能有限,无限制的建立长连接可能就无法正常工作了。默认情况下ESP32可以建立32个连接、ESP8266可以建立8个连接。)

EventSource

EventSource的使用比较简单,除了正常的声明与初始化AsyncWebServer对象外,只要下面几步就可以使用了:

  • 声明 AsyncEventSource 对象与URL;
  • 绑定 AsyncEventSource 对象事件回调函数; (可选)
  • AsyncEventSource 对象添加到服务器中;

使用下面代码进行测试:

#include <WiFi.h>
#include <ESPAsyncWebServer.h> //引入相应库
const char *ssid = "********";
const char *password = "********";
// 以下为网页文件
String indexhtml = String("") +
                   "<!DOCTYPE html>\n" +
                   "<head>\n" +
                   "    <meta charset=\"UTF-8\">\n" +
                   "    <title>EventSource Test</title>\n" +
                   "</head>\n" +
                   "<body>\n" +
                   "    <div id=\"info\"></div>\n" +
                   "    <script>\n" +
                   "        if (typeof (EventSource) !== \"undefined\") {\n" +
                   "            var source = new EventSource(\"/es\"); // 建立EventSource连接\n" +
                   "            source.onopen = function () { // 连接建立成功时触发\n" +
                   "                document.getElementById(\"info\").innerHTML += \"EventSource连接成功!\" + \"<br>\";\n" +
                   "            };\n" +
                   "            source.onmessage = function (event) { // 收到服务器数据时触发\n" +
                   "                document.getElementById(\"info\").innerHTML += event.data + \"<br>\";\n" +
                   "            };\n" +
                   "            source.onerror = function () { // 发生错误时触发\n" +
                   "                document.getElementById(\"info\").innerHTML += \"通讯发送错误!\" + \"<br>\";\n" +
                   "            };\n" +
                   "        }\n" +
                   "        else {\n" +
                   "            document.getElementById(\"info\").innerHTML = \"浏览器不支持 EventSource!\";\n" +
                   "        }\n" +
                   "    </script>\n" +
                   "</body>\n" +
                   "</html>\n";
AsyncWebServer server(80); // 声明WebServer对象
AsyncEventSource events("/es"); // EventSource对象,url为/es; 注意EventSource和Http是共用url的
// EventSource事件回调函数
void onEventHandle(AsyncEventSourceClient *client)
  Serial.printf("Client reconnected! Last message ID that it gat is: %u\n", client->lastId());
  // 向客户端发送hello!
  // events.count()为已经结客户端数,这里用作给客户端的id号
  // 1000表示告诉客户端如果连接断开则再1000毫秒后尝试重新连接
  client->send("hello!", NULL, events.count(), 1000);
void setup()
  Serial.begin(115200);
  Serial.println();
  WiFi.mode(WIFI_STA);
  WiFi.setSleep(false);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED)
    delay(500);
    Serial.print(".");
  Serial.println("Connected");
  Serial.print("IP Address:");
  Serial.println(WiFi.localIP());
  events.onConnect(onEventHandle); // 绑定当有客户端连接时的回调函数
  server.addHandler(&events);      // 将EventSource添加到服务器中
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { //注册链接"/lambda"与对应回调函数(匿名函数形式声明)
    request->send(200, "text/html", indexhtml);                 //向客户端发送响应和内容
  });
  server.begin(); //启动服务器
  Serial.println("Web server started");
void loop()
  delay(2000);
  events.send("lalala~~~"); // 向所有已连接的客户端推送消息 // 也可以用events.send("lalala~~~", NULL, events.count(), 1000);

在这里插入图片描述
上面就是一个使用EventSource进行通讯的例子,所有的关键步骤都在上面代码中写了注释。可以看到使用EventSource功能服务器可以主动向客户端推送数据。

EventSource相比WebSocket简单很多,在完成基本的初始化工作后就只有一个向全体客户端发送消息的功能:
void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0);
另外AsyncEventSource对象还有 void close();size_t count() const; 等少数几个不常用的方法。

在AsyncEventSource对象的onConnect事件回调函数中我们也可以用下面的方法向触发该事件的客户端发送消息:
client->write(const char * message, size_t len);
client->send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0);

ESPAsyncWebServer的WebSocket和EventSource主要就是上面这些内容,更多内容可以查看项目官方的文档和例程,或者我的其他ESPAsyncWebServer的文章:
ESPAsyncWebServer项目地址:
https://github.com/me-no-dev/ESPAsyncWebServer
《Arduino for ESP8266&ESP32适用库ESPAsyncWebServer:快速入门》
《Arduino for ESP8266&ESP32适用库ESPAsyncWebServer:事件处理绑定》
《Arduino for ESP8266&ESP32适用库ESPAsyncWebServer:请求与响应》
《Arduino for ESP8266&ESP32适用库ESPAsyncWebServer:静态文件和模板引擎》
《Arduino for ESP8266&ESP32适用库ESPAsyncWebServer:WebSocket和EventSource》

这是一个简单的,实现了在Arduino上运行的Websocket客户端。 将安装到Arduino Sketchbook文件夹中的“ libraries”文件夹中。 例如,在Mac上的~/Documents/Arduino/libraries ,而在Windows上,则可以在C:/Users/%username%/Documents/Arduino/libraries 。 尝试这些示例,以确保一切正常。 开始玩自己的代码! 我将很快将自己的实现添加到示例中,该示例根据您使用的是哪个来为WiFi和以太网客户端提供切换。 原始仅支持EthernetClient。 该已得到增强,并支持大于65535个字符的消息。 不过,请勿在Arduino Uno上尝试此操作,因为据报道该平台使用如此大的消息会失败。 此外,该支持单框架文本框架, 它是 WebSockets 的替代方案,因为它比 WebSockets 更简单,更适合处理服务器向客户端发送数据的情况。是一种在单个 TCP 连接上提供全双工通信的协议,它使得客户端和服务器之间进行实时交互变得更加容易。它是一种标准化的通信协议,客户端和服务器都可以通过它发送消息。和其他实时通信协议,并提供了一组易于使用的 API。它既可以在客户端上使用,也可以在服务器端上使用,它还提供了许多高级功能,例如自动重连、心跳机制和房间等概念。,只有服务器能够发送消息,所以它更安全。 fre内网穿透只能穿透http协议,且页面大小限制在1496B以内(MTU,包含header头部) 包含函数如下: begin(char*,char*,char*,uint16,String,String);//设定参数信息,第一个参数是wifi名称,第二个参数是wifi密码,第三个参数是服务器地址,第四个参数是服务器端口,都五个参数是自定义域名密码,第六个参数是域名(多个域名以英文逗号分割) on(String,void);//页面以及回调函数,第一个参数是页面路径,第二个参数是回调函数 header(String,String);//设定header头部信息 sendfile();//发送默认的SPIFFS文件 sendfile(String);//发送指定的SPIFFS文件 send(int,string,string);//发送string,第一个参数是响应代码一般是200,第二个参数是页面类型,第三个参数是需要发送的文本 send(int,string,uint8_t*,size_t);//发送uint8_t数组文件,第一个参数是响应代码一般是200,第二个参数是页面类型,第三个参数是需要发送的uint8_t数组文件,第四个参数是uint8_t数组文件的大小 send(int,string,char*,size_t);//发送char数组文件,第一个参数是响应代码一般是200,第二个参数是页面类型,第三个参数是需要发送的char数组文件,第四个参数是char数组文件的大小 Authenticate(String, String);//判断Auth鉴权信息,第一个参数是用户名,第二个参数是密码 requestAuthentication();//向页面返回鉴权需求,使页面弹出Auth鉴权登录信息 getheader(String);//返回header指定头部信息参数值 arg(String);//返回GET请求参数值 这个实验的功能演示 ESP32WebSocket 的使用方法。 这个实验的代码为工程“4_8_wifi_WebSocket”目录。 4.8.1. 实验内容 (1) 学习 Websocket 原理和工作过程 4.8.2. WebSocket 简介 WebSocket 是一种网络通信协议,是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的 协议。 WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。 在 WebSocket API 中,浏览器和服务 最近有个项目客户服务器是Websocket server,走的是WSS协议,要加密的嘛,我这边用ESP8266连上秒过,ESP32一直连不上,提示 然后就是愉快地找底层,把ESP8266和ESP32一步一步对比,终于发现了问题所在: 原因就是ESP8266和ESP32都没有证书的,那么当客户端的话正常流程就是跳过证书验证就好了,结果ESP8266跳过去了,ESP32没跳过. 本文将演示如何创建一个页面通过Webosocket通信协议来远程控制ESP8266输出,并将输出状态显示在页面上,并在所有客户端中自动更新。Web服务器将使用ESPAsyncWebserver来实现。 实现简单WebSockets服务器 通过前面[WeMos物联网开板实例入门-连接WIFI]的实例将WebMos开发板连接上了WIFI,在本节里,将实例一个简单的WebSockets服务器。 1、硬件准备 WeMos D1 R2开发板一个 笔记本电脑(台式电脑)一台 发光LED一个 杜邦线两根 面包板一个 发光LED通过面包板、杜邦线与开发板连接。如下图: 2、代码实现 在本次应用中,需要使用到WebSockets,下载完成后,将其导入到Arduino IDE中,如下图: [外链图片转存失败,源站可 websocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。... 文章目录asyncioEventloopCoroutineFuture示例websockets操作类使用 asyncio是用来编写并发代码的,使用async/await语法;其被用作高性能异步框架的基础(包括网络和网站服务,数据连接,分布式任务队列等等)。 asyncio asyncio提供一组高层级API用于: 并发地运行Python协程并对其执行过程实现完全控制; 执行网络IO和IPC; 控制子进程; 通过队列实现分布式任务; 同步并发代码; Eventloop Eventloop实例提供了注 实时聊天过程中, 1、websocket连接后,长时间远端和客户端不发消息,服务端会把websocket给断开; 2、所以就需要一种机制来检测客户端和服务端是否处于正常的链接状态。 3、因此就有了websocket的‘心跳监测’。 4、还有心跳,说明还活着,没有心跳说明已经断开了。 websocket基础使用 1、心跳机制是每隔一段时间会向服务器发送一个数据包: 告诉服务器(后台)自己还活着,同时客户端(浏览器)会确认服务器端是否还活着 2、如果还活着的话,就会回