NodeMCU的TCP、UDP通信
通过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
1#include <Arduino.h>
2#include <ESP8266WiFi.h>
3
4#define SERVER_PORT 8080
5#define MAX_SRV_CLIENTS 2
6
7WiFiServer server(SERVER_PORT);
8WiFiClient serverClients[MAX_SRV_CLIENTS];
9
10#define SSID "TP-LINK"
11#define PASSWORD "192.168.1.1"
12
13void setup() {
14 Serial.begin(9600);
15 WiFi.mode(WIFI_STA);
16
17 WiFi.begin(SSID, PASSWORD);
18
19 while (WiFi.status() != WL_CONNECTED) {
20 delay(1000);
21 Serial.print(".");
22 }
23 Serial.println("\nWiFi Connected!");
24 server.begin();
25 // 关闭小包合并包功能,不会延时发送数据
26 server.setNoDelay(true);
27 Serial.printf("server start success, %s:%d",
28 WiFi.localIP().toString().c_str(),
29 SERVER_PORT);
30}
31
32// 读取数据缓冲区(根据需求调整)
33char *readBuffer = new char[128];
34
35void loop() {
36 int i = 0;
37 // 读取数据大小
38 size_t readSize = 0;
39
40 // 判断是否有新的Client请求进来
41 if (server.hasClient()) {
42 for (i = 0; i < MAX_SRV_CLIENTS; i++) {
43 // 释放旧无效或者断开的client
44 if (!serverClients[i] || !serverClients[i].connected()) {
45 if (!serverClients[i]) {
46 // 停止指定客户端的连接
47 serverClients[i].stop();
48 }
49 // 分配最新的client
50 serverClients[i] = server.available();
51 Serial.printf("A new client -> %d\n", i);
52 break;
53 }
54 }
55 }
56
57 // 当达到最大连接数,无法释放无效的Client,需要拒绝连接
58 if (i == MAX_SRV_CLIENTS) {
59 WiFiClient client = server.available();
60 client.stop();
61 Serial.println("Max Connections, Connection rejected");
62 }
63
64 // 检测是否有Client发过来的数据
65 for (i = 0; i < MAX_SRV_CLIENTS; i++) {
66 if (serverClients[i] && serverClients[i].connected()) {
67 // 判断指定客户端是否有可读数据
68 if (serverClients[i].available()) {
69 readSize = serverClients[i].peekAvailable();
70 serverClients[i].read(readBuffer, readSize);
71 Serial.write(readBuffer, readSize);
72 // 数据回传给Client
73 serverClients[i].write(readBuffer, readSize);
74 }
75 }
76 }
77}
2、Ticker定时操作
TcpServer.cpp
1#include <Arduino.h>
2#include <ESP8266WiFi.h>
3#include <Ticker.h>
4
5#define SERVER_PORT 8080
6#define MAX_SRV_CLIENTS 2
7
8WiFiServer server(SERVER_PORT);
9WiFiClient serverClients[MAX_SRV_CLIENTS];
10
11#define SSID "TP-LINK"
12#define PASSWORD "192.168.1.1"
13
14Ticker handTcpTicker;
15
16// 读取数据缓冲区(根据需求调整)
17char *readBuffer = new char[128];
18
19void handTcpMessage(){
20 int i = 0;
21 // 读取数据大小
22 size_t readSize = 0;
23
24 // 判断是否有新的Client请求进来
25 if (server.hasClient()) {
26 for (i = 0; i < MAX_SRV_CLIENTS; i++) {
27 // 释放旧无效或者断开的client
28 if (!serverClients[i] || !serverClients[i].connected()) {
29 if (!serverClients[i]) {
30 // 停止指定客户端的连接
31 serverClients[i].stop();
32 }
33 // 分配最新的client
34 serverClients[i] = server.available();
35 Serial.printf("A new client -> %d\n", i);
36 break;
37 }
38 }
39 }
40
41 // 当达到最大连接数,无法释放无效的Client,需要拒绝连接
42 if (i == MAX_SRV_CLIENTS) {
43 WiFiClient client = server.available();
44 client.stop();
45 Serial.println("Max Connections, Connection rejected");
46 }
47
48 // 检测是否有Client发过来的数据
49 for (i = 0; i < MAX_SRV_CLIENTS; i++) {
50 if (serverClients[i] && serverClients[i].connected()) {
51 // 判断指定客户端是否有可读数据
52 if (serverClients[i].available()) {
53 readSize = serverClients[i].peekAvailable();
54 serverClients[i].read(readBuffer, readSize);
55 Serial.write(readBuffer, readSize);
56 // 数据回传给Client
57 serverClients[i].write(readBuffer, readSize);
58 }
59 }
60 }
61}
62
63void setup() {
64 Serial.begin(9600);
65 WiFi.mode(WIFI_STA);
66
67 WiFi.begin(SSID, PASSWORD);
68
69 while (WiFi.status() != WL_CONNECTED) {
70 delay(1000);
71 Serial.print(".");
72 }
73 Serial.println("\nWiFi Connected!");
74 server.begin();
75 //关闭小包合并包功能,不会延时发送数据
76 server.setNoDelay(true);
77 Serial.printf("server start success, %s:%d\n",
78 WiFi.localIP().toString().c_str(),
79 SERVER_PORT);
80
81
82 handTcpTicker.attach_ms(5, handTcpMessage);
83}
84
85void loop() {
86
87}
3、Ticker标志位优化
也就是说,不建议在Ticker回调函数里执行阻塞IO的操作(比如网络、序列化、文件读写)。应该在Ticker回调函数中设置一个标志位,并在循环函数中检查该标志,符合条件的情况的下进行IO操作即可。
1#include <ESP8266WiFi.h>
2#include <Ticker.h>
3
4#define SERVER_PORT 8080
5#define MAX_SRV_CLIENTS 2
6
7WiFiServer server(SERVER_PORT);
8WiFiClient serverClients[MAX_SRV_CLIENTS];
9
10#define SSID "TP-LINK"
11#define PASSWORD "192.168.1.1"
12
13Ticker handTcpTicker;
14// 读取数据缓冲区(根据需求调整)
15char *readBuffer = new char[1024];
16// 读取数据的ClientId
17int availableClientId = -1;
18// 是否需要激活IO
19bool activateMsgHand = false;
20
21void handTcpMessage(){
22 int i = 0;
23 // 读取数据大小
24 size_t readSize = 0;
25
26 // 判断是否有新的Client请求进来
27 if (server.hasClient()) {
28 for (i = 0; i < MAX_SRV_CLIENTS; i++) {
29 // 释放旧无效或者断开的client
30 if (!serverClients[i] || !serverClients[i].connected()) {
31 if (!serverClients[i]) {
32 // 停止指定客户端的连接
33 serverClients[i].stop();
34 }
35 // 分配最新的client
36 serverClients[i] = server.available();
37 Serial.printf("A new client -> %d\n", i);
38 break;
39 }
40 }
41 }
42
43 // 当达到最大连接数,无法释放无效的Client,需要拒绝连接
44 if (i == MAX_SRV_CLIENTS) {
45 WiFiClient client = server.available();
46 client.stop();
47 Serial.println("Max Connections, Connection rejected");
48 }
49
50 // 检测是否有Client发过来的数据
51 for (i = 0; i < MAX_SRV_CLIENTS; i++) {
52 if (serverClients[i] && serverClients[i].connected()) {
53 // 判断指定客户端是否有可读数据
54 if (serverClients[i].available()) {
55 activateMsgHand = true;
56 availableClientId = i;
57 }
58 }
59 }
60}
61
62void setup() {
63 Serial.begin(9600);
64 WiFi.mode(WIFI_STA);
65
66 WiFi.begin(SSID, PASSWORD);
67
68 while (WiFi.status() != WL_CONNECTED) {
69 delay(1000);
70 Serial.print(".");
71 }
72 Serial.println("\nWiFi Connected!");
73 server.begin();
74 //关闭小包合并包功能,不会延时发送数据
75 server.setNoDelay(true);
76 Serial.printf("server start success, %s:%d\n",
77 WiFi.localIP().toString().c_str(),
78 SERVER_PORT);
79 handTcpTicker.attach_ms(10, handTcpMessage);
80}
81
82void loop() {
83 if(activateMsgHand){
84 // 读取Client数据
85 size_t size = serverClients[availableClientId].peekAvailable();
86 serverClients[availableClientId].read(readBuffer, size);
87 // 串口打印数据
88 Serial.write(readBuffer, size);
89
90 // 向客户端回发数据
91 serverClients[availableClientId].write(readBuffer, size);
92
93 // 重置标志位
94 activateMsgHand = false;
95 }
96}
UDP通信
WiFiUDP库用于ESP8266开发板的物联网通讯控制以及UDP协议数据包处理。
1、Ticker标志位优化
UdpServer.cpp
1#include <ESP8266WiFi.h>
2#include <Ticker.h>
3#include <WiFiUdp.h>
4
5// 本地UDP端口
6#define LOCAL_UDP_PORT 8080
7#define SSID "TP-LINK"
8#define PASSWORD "192.168.1.1"
9
10WiFiUDP Udp;
11Ticker handUdpTicker;
12
13// 读取数据缓冲区(根据需求调整)
14char *readBuffer = new char[1024];
15// 是否需要激活IO
16bool activateMsgHand = false;
17// UDP数据包大小
18int packetSize = 0;
19
20void handUdpMessage(){
21 // 获得解析包
22 packetSize = Udp.parsePacket();
23 if(packetSize) {
24 activateMsgHand = true;
25 }
26}
27
28void setup() {
29 Serial.begin(9600);
30 WiFi.mode(WIFI_STA);
31
32 WiFi.begin(SSID, PASSWORD);
33
34 while (WiFi.status() != WL_CONNECTED) {
35 delay(1000);
36 Serial.print(".");
37 }
38 Serial.println("\nWiFi Connected!");
39
40 if(Udp.begin(LOCAL_UDP_PORT)){
41 Serial.printf("Start listen IP:%s, UDP port:%d\n",
42 WiFi.localIP().toString().c_str(),
43 LOCAL_UDP_PORT);
44 }
45
46 handUdpTicker.attach_ms(10, handUdpMessage);
47}
48
49void loop() {
50 if(activateMsgHand){
51 // 收到Udp数据包
52 Serial.printf("form %s:%d -> package size = %d\n",
53 Udp.remoteIP().toString().c_str(),
54 Udp.remotePort(), packetSize);
55
56 // 读取Udp数据包并存放在incomingPacket
57 int len = Udp.read(readBuffer, 1024);//返回数据包字节数
58 if (len > 0){
59 // 添加字符串结束标识符
60 readBuffer[len] = '\0';
61 }
62
63 // 向串口打印信息
64 Serial.printf("content is %s\n", readBuffer);
65
66 // 向客户端回发消息
67 Udp.beginPacket(Udp.remoteIP(), Udp.remotePort());
68 Udp.write(readBuffer);
69 Udp.endPacket();
70
71 // 重置标志位
72 activateMsgHand = false;
73 }
74}