WebSocket

  由于在HTTP协议中,服务器不能主动向设备推送信息。设备使用轮询的方式向服务器请求数据时会消耗大量的设备运行资源与网络资源,因此WebSocket协议诞生。
  WebSocket协议是建立在运输层协议TCP上进行全双工通信的协议,可以实现设备与物联网协议之间的平等传输,即客户端可以主动向服务器发送请求,服务器也可以向客户端推送信息。

在这里插入图片描述
  若使用WebSocket,先引入WebSocket库,这里以 Links2004/arduinoWebSockets 的WebSocket库为例。库作者GitHub:
https://github.com/Links2004/arduinoWebSockets
引入方法:

  1. 打开Ardunio IDE

  2. 点击标题栏项目 --> 加载库 --> 管理库打开库管理器
    在这里插入图片描述

  3. 搜索WebSockets,找到Markus Sattler版本的WebSockets库,点击安装

WebSockets事件类型

关键字描述
WStype_ERRORWebSocket连接发生错误时触发
WStype_DISCONNECTED客户端或服务端关闭连接时触发
WStype_CONNECTED客户端连接到服务端时触发
WStype_TEXT接收到文本数据时触发
WStype_BIN接收到二进制数据时触发
WStype_PINGPing和Pong是websocket里的心跳,用来保证客户端是在线的,服务端给客户端发送PING,客户端发送PONG来回应
WStype_PONG

使用步骤

WebSocketsServer

  使用WebSockets库前需要将ESP8266NodeMCU连接到WIFI网络,并使其工作在WIFI_STA模式

  1. 引入WebSocketsServer库
#include <WebSocketsServer.h>
  1. 初始化WebSocketsServer对象,并设置端口号为81
WebSocketsServer server(81);
  1. 在setup函数中启动WebSocketsServer,并指定事件处理函数
// 启动WebSocketsServer
server.begin();
// 指定事件处理函数
server.onEvent([](uint8_t num, WStype_t type, uint8_t * payload, size_t length){
	/* param:
		num: 客户端编号,可以判断接收的消息来自哪个连接的客户端
		type: 事件类型
		payload: 事件携带数据
		length: 事件携带数据长度
	*/
	if (type == WStype_TEXT) String data = (char*)payload;
});
  1. 在loop函数中添加server.loop();
  2. 若服务器想要给客户端发送文本数据,则使用sendTXT方法
/* param:
	num: int8_t类型,客户端编号
	str: String类型,要发送的数据
*/
server.sendTXT(num, str);

  其中客户端编号是根据客户端连接服务端的先后顺序来编号的,若知道客户端IP地址,可以通过遍历server.remoteIP();获取,例如:

// 遍历当前连接服务端的所有客户端地址,并与需要获取编号的客户端IP地址进行比较,若找到IP地址,返回IP地址索引,否则返回-1
int8_t get_num_from_ip(IPAddress ip_needed) {
    for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) {
        IPAddress ip = server.remoteIP(i);
        if(ip == ip_needed) {
           // ip found
           return i;
        }
    }
    return -1
}

WebSocketsClient

  1. 引入WebSocketsClient库
#include <WebSocketsClient.h>
  1. 初始化WebSocketsClient对象
WebSocketsClient wsClient;
  1. 在setup函数中启动WebSocketsClient,并指定事件处理函数
// 启动WebSocketsClient,设定WebSocket服务器地址为remoteHost,端口号为81
wsClient.begin(remoteHost, 81);
// 指定事件处理函数
wsClient.onEvent([](WStype_t type, uint8_t * payload, size_t length) {
  /* param:
		type: 事件类型
		payload: 事件携带数据
		length: 事件携带数据长度
  */
  if (type == WStype_TEXT) String data = (char*)payload;
});
  1. 在loop函数中添加wsClient.loop();
  2. 若客户端想要给服务器发送文本数据,则使用sendTXT方法
/* param:
	str: String类型,要发送的数据
*/
wsClient.sendTXT(str);

使用WebSocket控制LED

  在该例子中,将使用两块ESP8266NodeMCU开发板A与B,实现使用开发板A上的FLASH按钮控制开发板B的LED灯,同时使用开发板B上的FLASH按钮控制开发板A的LED灯。两块开发板连接同一WIFI网络,并使用WebSocket协议进行网络通信。

开发板A:服务端开发板代码

#include <ESP8266WiFi.h>
#include <WebSocketsServer.h>

// 在这里填入WiFi名称
const String ssid = "";
// 在这里填入WiFi密码
const String passwd = "";
// 在这里指定WebSocket服务端IP地址
IPAddress host(192, 168, 2, 10);
// 在这里填入WiFi网关地址
IPAddress gateway(192, 168, 2, 1);
// 在这里填入WiFi子网掩码,网关地址与子网掩码可用ifconfig(mac os)或ipconfig(windows)命令获取
IPAddress netmask(255, 255, 255, 0);
// 初始化WebSocketsServer对象
WebSocketsServer server(81);

int pressState;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  pinMode(0, INPUT);
  pinMode(LED_BUILTIN, OUTPUT);
  
  // 设置WiFi运行模式为无线终端模式
  WiFi.mode(WIFI_STA);
  // 为当前设备配置固定IP
  if (!WiFi.config(host, gateway, netmask)) {
    Serial.println("Can't config wifi.");
  }
  Serial.println("Connecting to " + ssid);
  // 连接WiFi
  WiFi.begin(ssid, passwd);
  // 判断是否连接成功
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print('.');
    delay(100);
  }
  Serial.println("");
  Serial.println("Connected to " + ssid);
  Serial.print("Current ip address is ");
  Serial.println(WiFi.localIP());

  // 启动WebSocket服务器
  server.begin();
  // 指定事件处理函数
  server.onEvent([](uint8_t num, WStype_t type, uint8_t * payload, size_t length){
    if (type == WStype_CONNECTED) {
      // 若为客户端连接事件,显示提示信息
      Serial.println("New connection!");
    } else if (type == WStype_DISCONNECTED) {
      // 若为连接断开事件,显示提示信息
      Serial.println("Close the connection.");
    } else if (type == WStype_TEXT) {
      // 接收来自客户端的信息(客户端FLASH按键状态),并控制LED的工作
      String data = (char*)payload;
      if(data.substring(data.indexOf("=") + 1) == "on") {
        digitalWrite(LED_BUILTIN, LOW);
      } else if (data.substring(data.indexOf("=") + 1) == "off") {
        digitalWrite(LED_BUILTIN, HIGH);
      }
    }
  });
}

// 通过IP地址获取客户端编号
int8_t get_num_from_ip(IPAddress ip_needed) {
    for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) {
        IPAddress ip = server.remoteIP(i);
        if(ip == ip_needed) {
           // ip found
           return i;
        }
    }
    return -1;
}

void loop() {
  // put your main code here, to run repeatedly:
  server.loop();
  // 判断当前FLASH按键状态是否与之前状态一致,若不一致则按键状态发生改变
  if (digitalRead(0) != pressState) {
  	// 若按键状态改变,记录当前按键状态,并向客户端发送按键状态
    pressState = digitalRead(0);
    String data = String("ledStatus=") + (digitalRead(0) == HIGH ? "off": "on");
    server.sendTXT(get_num_from_ip(IPAddress(192, 168, 2, 8)), data);
  }
}

开发板B:客户端开发板代码

#include <ESP8266WiFi.h>
#include <WebSocketsClient.h>

// 在这里填入WiFi名称
const String ssid = "";
// 在这里填入WiFi密码
const String passwd = "";
// 在这里指定WebSocket服务端IP地址
IPAddress host(192, 168, 2, 8);
// 在这里填入WiFi网关地址
IPAddress gateway(192, 168, 2, 1);
// 在这里填入WiFi子网掩码,网关地址与子网掩码可用ifconfig(mac os)或ipconfig(windows)命令获取
IPAddress netmask(255, 255, 255, 0);
// WebSocket服务器IP地址
String remoteHost = "192.168.2.10";
// 初始化WebSocketsClient对象
WebSocketsClient wsClient;

int pressState;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  pinMode(0, INPUT);
  pinMode(LED_BUILTIN, OUTPUT);
  
  // 设置WiFi运行模式为无线终端模式
  WiFi.mode(WIFI_STA);
  // 为当前设备配置固定IP
  if (!WiFi.config(host, gateway, netmask)) {
    Serial.println("Can't config wifi.");
  }
  Serial.println("Connecting to " + ssid);
  // 连接WiFi
  WiFi.begin(ssid, passwd);
  // 判断是否连接成功
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print('.');
    delay(100);
  }
  Serial.println("");
  Serial.println("Connected to " + ssid);
  Serial.print("Current ip address is ");
  Serial.println(WiFi.localIP());

  // 启动WebSocket客户端
  wsClient.begin(remoteHost, 81);
  // 指定事件处理函数
  wsClient.onEvent([](WStype_t type, uint8_t * payload, size_t length) {
    if (type == WStype_TEXT) {
      // 接收来自服务端的信息(服务端FLASH按键状态),并控制LED的工作
      String data = (char*)payload;
      if(data.substring(data.indexOf("=") + 1) == "on") {
        digitalWrite(LED_BUILTIN, LOW);
      } else if (data.substring(data.indexOf("=") + 1) == "off") {
        digitalWrite(LED_BUILTIN, HIGH);
      }
    }
  });
}

void loop() {
  // put your main code here, to run repeatedly:
  wsClient.loop();
  // 判断当前FLASH按键状态是否与之前状态一致,若不一致则按键状态发生改变
  if (digitalRead(0) != pressState) {
    // 若按键状态改变,记录当前按键状态,并向服务端发送按键状态
    pressState = digitalRead(0);
    String data = String("ledStatus=") + (digitalRead(0) == HIGH ? "off": "on");
    Serial.println(data);
    wsClient.sendTXT(data);
  }
}
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐