Ở bài trước chúng ta đã làm quen với TCP — giao thức đảm bảo dữ liệu đến đúng thứ tự, không mất gói. Bài này chúng ta lập trình ESP32 giao tiếp qua UDP, người anh em ngược lại của TCP: UDP — nhanh hơn, đơn giản hơn, nhưng không đảm bảo gì cả.
Đây là một bài thực hành trong Serie Lập trình ESP32 từ A tới Z.
UDP là gì? So sánh với TCP
UDP (User Datagram Protocol) cũng là giao thức truyền dữ liệu qua mạng như TCP, nhưng theo triết lý hoàn toàn khác: cứ gửi gói tin đi, không cần bắt tay thiết lập kết nối trước, không quan tâm gói có tới nơi hay không, không quan tâm thứ tự gói tin. Giống như việc bạn ném thư vào hòm thư công cộng — gửi xong là xong, không có ai xác nhận thư đã tới tay người nhận hay chưa.
| Đặc điểm | TCP | UDP |
|---|---|---|
| Thiết lập kết nối trước | Có (bắt tay 3 bước) | Không |
| Đảm bảo gói tin tới nơi | Có, tự gửi lại nếu mất | Không |
| Đảm bảo đúng thứ tự | Có | Không |
| Tốc độ | Chậm hơn | Nhanh hơn |
| Ứng dụng phù hợp | Lệnh điều khiển, truyền file, API | Video/audio streaming, game online, DNS, NTP |
Vì không tốn thời gian bắt tay hay chờ xác nhận, UDP được dùng cho các ứng dụng cần tốc độ, chấp nhận thi thoảng mất vài gói tin không sao — như đồng bộ giờ NTP (chỉ cần lấy được giờ 1 lần là đủ, mất gói thì hỏi lại) hay stream video (mất 1 khung hình cũng không ai để ý).
Lập trình ESP32 giao tiếp qua UDP
Mở cổng lắng nghe UDP
const uint16_t udpPort = 4210;
WiFiUDP udp;
void setup() {
...
udp.begin(udpPort);
}
Khác WiFiServer của TCP (phải .available() chờ có client kết nối), WiFiUDP không có khái niệm “kết nối” — chỉ cần mở cổng lắng nghe là ESP32 sẵn sàng nhận bất kỳ gói tin UDP nào gửi tới cổng đó, từ bất kỳ ai.
Nhận gói tin và trả lời
char incomingPacket[256];
int packetSize = udp.parsePacket();
if (packetSize) {
IPAddress remoteIp = udp.remoteIP();
int remotePort = udp.remotePort();
int len = udp.read(incomingPacket, 255);
if (len > 0) {
incomingPacket[len] = '\0';
}
udp.beginPacket(remoteIp, remotePort);
udp.print("ESP32 da nhan: ");
udp.print(incomingPacket);
udp.endPacket();
}
udp.parsePacket()kiểm tra có gói tin mới tới chưa, trả về kích thước gói tin (0 nếu chưa có gì).udp.remoteIP()vàudp.remotePort()cho biết gói tin này gửi từ đâu tới — quan trọng vì UDP không có kết nối cố định, mỗi gói tin có thể đến từ 1 nguồn khác nhau, phải lưu lại thông tin này thì mới biết gửi phản hồi về đúng chỗ.udp.read(incomingPacket, 255)đọc tối đa 255 byte, rồi mình gắn thêm ký tự'\0'vào cuối để biến nó thành chuỗi C in ra được.udp.beginPacket()/udp.endPacket()đóng gói và gửi phản hồi ngược lại đúng IP, cổng vừa nhận được.
Lưu ý: Mảng incomingPacket phải khai báo 256 phần tử chứ không phải 255. Khi gói tin dài đủ 255 byte, len bằng 255 và dòng incomingPacket[len] = '\0' sẽ ghi vào phần tử thứ 256 — nếu mảng chỉ có 255 phần tử thì đây là ghi tràn bộ nhớ, ESP32 có thể chạy sai hoặc reset ngẫu nhiên rất khó dò ra nguyên nhân.
Lưu ý: Vì UDP không đảm bảo gói tin đến nơi, đoạn code này có thể gửi phản hồi đi mà không bao giờ tới được máy gửi ban đầu (do mất gói trên đường truyền) — và code cũng không hề biết điều đó, không có cơ chế báo lỗi như TCP. Nếu ứng dụng của bạn cần chắc chắn dữ liệu đến nơi (như gửi lệnh firmware update), đừng dùng UDP — quay lại dùng TCP như bài trước.
Code đầy đủ
#include <Arduino.h>
#include <WiFi.h>
#include <WiFiUdp.h>
const char* ssid = "Ten_Wifi_Cua_Ban";
const char* password = "Mat_Khau_Wifi";
const uint16_t udpPort = 4210;
WiFiUDP udp;
char incomingPacket[256];
void setup() {
Serial.begin(115200);
Serial.print("Connecting to wifi ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected.");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
udp.begin(udpPort);
Serial.print("Dang lang nghe UDP tai cong: ");
Serial.println(udpPort);
}
void loop() {
int packetSize = udp.parsePacket();
if (packetSize) {
IPAddress remoteIp = udp.remoteIP();
int remotePort = udp.remotePort();
int len = udp.read(incomingPacket, 255);
if (len > 0) {
incomingPacket[len] = '\0';
}
Serial.print("Nhan goi UDP tu ");
Serial.print(remoteIp);
Serial.print(":");
Serial.print(remotePort);
Serial.print(" - noi dung: ");
Serial.println(incomingPacket);
udp.beginPacket(remoteIp, remotePort);
udp.print("ESP32 da nhan: ");
udp.print(incomingPacket);
udp.endPacket();
}
}
Code đầy đủ mình để trên GitHub, các bạn xem tại 04-Protocol/06_UDP nhé.
Kết quả
Nạp code, lấy IP của ESP32 từ Serial Monitor. Trên máy tính có netcat, gửi thử 1 gói UDP:
echo "hello esp32" | nc -u -w1 <IP_ESP32> 4210
Gửi 2 gói UDP từ máy tính, gói nào cũng nhận lại phản hồi từ ESP32:
Log Serial Monitor của ESP32:
Theo dõi Serial Monitor của ESP32, các bạn sẽ thấy dòng log nhận được gói tin kèm đúng IP/cổng nguồn, đúng không nào. Terminal gửi cũng sẽ nhận lại phản hồi “ESP32 da nhan: hello esp32” nếu gói phản hồi không bị mất trên đường về.
Kết
TCP và UDP là 2 lựa chọn nền tảng cho mọi giao thức mạng cao hơn — chọn đúng cái nào tùy vào việc bạn cần độ tin cậy (TCP) hay tốc độ, độ trễ thấp (UDP). Giờ đã nắm cả 2 tầng thấp nhất này, quay lại đọc code của MQTT, WebSocket hay HTTP ở các bài trước, bạn sẽ thấy chúng dễ hiểu hơn hẳn vì biết chính xác chúng đang xây trên nền tảng nào.
Nếu thấy bài viết có ích, hãy chia sẻ cho bạn bè cùng học. Có thắc mắc gì cứ để lại bình luận, mình sẽ giải đáp nhé!




