编辑
2021-10-27
浅尝嵌入式开发
00
请注意,本文编写于 697 天前,最后修改于 39 天前,其中某些信息可能已经过时。

目录

TCP通信
1、Loop直接操作
2、Ticker定时操作
3、Ticker标志位优化
UDP通信
1、Ticker标志位优化

通过ESP8266 NodeMCU建立TCP/UDP Server,可以轻松实现单点/多点Client与MCU的通信, WiFiServer库用于ESP8266的TCP协议通信,WiFiUDP库用于UDP协议通信。 为了不阻塞Loop中的任务,而且Ticker中不建议进行阻塞式IO,则可以用Ticker负责检测新的客户端连接或者新数据包,Loop中进行IO操作,这样更高效。

TCP通信

WiFiServer库用于ESP8266的TCP协议物联网通讯。

1、Loop直接操作

TcpServer.cpp

cpp
#include <Arduino.h> #include <ESP8266WiFi.h> #define SERVER_PORT 8080 #define MAX_SRV_CLIENTS 2 WiFiServer server(SERVER_PORT); WiFiClient serverClients[MAX_SRV_CLIENTS]; #define SSID "TP-LINK" #define PASSWORD "192.168.1.1" void setup() { Serial.begin(9600); WiFi.mode(WIFI_STA); WiFi.begin(SSID, PASSWORD); while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.print("."); } Serial.println("\nWiFi Connected!"); server.begin(); // 关闭小包合并包功能,不会延时发送数据 server.setNoDelay(true); Serial.printf("server start success, %s:%d", WiFi.localIP().toString().c_str(), SERVER_PORT); } // 读取数据缓冲区(根据需求调整) char *readBuffer = new char[128]; void loop() { int i = 0; // 读取数据大小 size_t readSize = 0; // 判断是否有新的Client请求进来 if (server.hasClient()) { for (i = 0; i < MAX_SRV_CLIENTS; i++) { // 释放旧无效或者断开的client if (!serverClients[i] || !serverClients[i].connected()) { if (!serverClients[i]) { // 停止指定客户端的连接 serverClients[i].stop(); } // 分配最新的client serverClients[i] = server.available(); Serial.printf("A new client -> %d\n", i); break; } } } // 当达到最大连接数,无法释放无效的Client,需要拒绝连接 if (i == MAX_SRV_CLIENTS) { WiFiClient client = server.available(); client.stop(); Serial.println("Max Connections, Connection rejected"); } // 检测是否有Client发过来的数据 for (i = 0; i < MAX_SRV_CLIENTS; i++) { if (serverClients[i] && serverClients[i].connected()) { // 判断指定客户端是否有可读数据 if (serverClients[i].available()) { readSize = serverClients[i].peekAvailable(); serverClients[i].read(readBuffer, readSize); Serial.write(readBuffer, readSize); // 数据回传给Client serverClients[i].write(readBuffer, readSize); } } } }

2、Ticker定时操作

TcpServer.cpp

cpp
#include <Arduino.h> #include <ESP8266WiFi.h> #include <Ticker.h> #define SERVER_PORT 8080 #define MAX_SRV_CLIENTS 2 WiFiServer server(SERVER_PORT); WiFiClient serverClients[MAX_SRV_CLIENTS]; #define SSID "TP-LINK" #define PASSWORD "192.168.1.1" Ticker handTcpTicker; // 读取数据缓冲区(根据需求调整) char *readBuffer = new char[128]; void handTcpMessage(){ int i = 0; // 读取数据大小 size_t readSize = 0; // 判断是否有新的Client请求进来 if (server.hasClient()) { for (i = 0; i < MAX_SRV_CLIENTS; i++) { // 释放旧无效或者断开的client if (!serverClients[i] || !serverClients[i].connected()) { if (!serverClients[i]) { // 停止指定客户端的连接 serverClients[i].stop(); } // 分配最新的client serverClients[i] = server.available(); Serial.printf("A new client -> %d\n", i); break; } } } // 当达到最大连接数,无法释放无效的Client,需要拒绝连接 if (i == MAX_SRV_CLIENTS) { WiFiClient client = server.available(); client.stop(); Serial.println("Max Connections, Connection rejected"); } // 检测是否有Client发过来的数据 for (i = 0; i < MAX_SRV_CLIENTS; i++) { if (serverClients[i] && serverClients[i].connected()) { // 判断指定客户端是否有可读数据 if (serverClients[i].available()) { readSize = serverClients[i].peekAvailable(); serverClients[i].read(readBuffer, readSize); Serial.write(readBuffer, readSize); // 数据回传给Client serverClients[i].write(readBuffer, readSize); } } } } void setup() { Serial.begin(9600); WiFi.mode(WIFI_STA); WiFi.begin(SSID, PASSWORD); while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.print("."); } Serial.println("\nWiFi Connected!"); server.begin(); //关闭小包合并包功能,不会延时发送数据 server.setNoDelay(true); Serial.printf("server start success, %s:%d\n", WiFi.localIP().toString().c_str(), SERVER_PORT); handTcpTicker.attach_ms(5, handTcpMessage); } void loop() { }

3、Ticker标志位优化

也就是说,不建议在Ticker回调函数里执行阻塞IO的操作(比如网络、序列化、文件读写)。应该在Ticker回调函数中设置一个标志位,并在循环函数中检查该标志,符合条件的情况的下进行IO操作即可。

cpp
#include <ESP8266WiFi.h> #include <Ticker.h> #define SERVER_PORT 8080 #define MAX_SRV_CLIENTS 2 WiFiServer server(SERVER_PORT); WiFiClient serverClients[MAX_SRV_CLIENTS]; #define SSID "TP-LINK" #define PASSWORD "192.168.1.1" Ticker handTcpTicker; // 读取数据缓冲区(根据需求调整) char *readBuffer = new char[1024]; // 读取数据的ClientId int availableClientId = -1; // 是否需要激活IO bool activateMsgHand = false; void handTcpMessage(){ int i = 0; // 读取数据大小 size_t readSize = 0; // 判断是否有新的Client请求进来 if (server.hasClient()) { for (i = 0; i < MAX_SRV_CLIENTS; i++) { // 释放旧无效或者断开的client if (!serverClients[i] || !serverClients[i].connected()) { if (!serverClients[i]) { // 停止指定客户端的连接 serverClients[i].stop(); } // 分配最新的client serverClients[i] = server.available(); Serial.printf("A new client -> %d\n", i); break; } } } // 当达到最大连接数,无法释放无效的Client,需要拒绝连接 if (i == MAX_SRV_CLIENTS) { WiFiClient client = server.available(); client.stop(); Serial.println("Max Connections, Connection rejected"); } // 检测是否有Client发过来的数据 for (i = 0; i < MAX_SRV_CLIENTS; i++) { if (serverClients[i] && serverClients[i].connected()) { // 判断指定客户端是否有可读数据 if (serverClients[i].available()) { activateMsgHand = true; availableClientId = i; } } } } void setup() { Serial.begin(9600); WiFi.mode(WIFI_STA); WiFi.begin(SSID, PASSWORD); while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.print("."); } Serial.println("\nWiFi Connected!"); server.begin(); //关闭小包合并包功能,不会延时发送数据 server.setNoDelay(true); Serial.printf("server start success, %s:%d\n", WiFi.localIP().toString().c_str(), SERVER_PORT); handTcpTicker.attach_ms(10, handTcpMessage); } void loop() { if(activateMsgHand){ // 读取Client数据 size_t size = serverClients[availableClientId].peekAvailable(); serverClients[availableClientId].read(readBuffer, size); // 串口打印数据 Serial.write(readBuffer, size); // 向客户端回发数据 serverClients[availableClientId].write(readBuffer, size); // 重置标志位 activateMsgHand = false; } }

UDP通信

WiFiUDP库用于ESP8266开发板的物联网通讯控制以及UDP协议数据包处理。

1、Ticker标志位优化

UdpServer.cpp

cpp
#include <ESP8266WiFi.h> #include <Ticker.h> #include <WiFiUdp.h> // 本地UDP端口 #define LOCAL_UDP_PORT 8080 #define SSID "TP-LINK" #define PASSWORD "192.168.1.1" WiFiUDP Udp; Ticker handUdpTicker; // 读取数据缓冲区(根据需求调整) char *readBuffer = new char[1024]; // 是否需要激活IO bool activateMsgHand = false; // UDP数据包大小 int packetSize = 0; void handUdpMessage(){ // 获得解析包 packetSize = Udp.parsePacket(); if(packetSize) { activateMsgHand = true; } } void setup() { Serial.begin(9600); WiFi.mode(WIFI_STA); WiFi.begin(SSID, PASSWORD); while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.print("."); } Serial.println("\nWiFi Connected!"); if(Udp.begin(LOCAL_UDP_PORT)){ Serial.printf("Start listen IP:%s, UDP port:%d\n", WiFi.localIP().toString().c_str(), LOCAL_UDP_PORT); } handUdpTicker.attach_ms(10, handUdpMessage); } void loop() { if(activateMsgHand){ // 收到Udp数据包 Serial.printf("form %s:%d -> package size = %d\n", Udp.remoteIP().toString().c_str(), Udp.remotePort(), packetSize); // 读取Udp数据包并存放在incomingPacket int len = Udp.read(readBuffer, 1024);//返回数据包字节数 if (len > 0){ // 添加字符串结束标识符 readBuffer[len] = '\0'; } // 向串口打印信息 Serial.printf("content is %s\n", readBuffer); // 向客户端回发消息 Udp.beginPacket(Udp.remoteIP(), Udp.remotePort()); Udp.write(readBuffer); Udp.endPacket(); // 重置标志位 activateMsgHand = false; } }

本文作者:Tim

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!