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}