文章总结(帮你们节约时间)
- TCP虽然可靠但带来额外开销,ESP32能够绕过传输层,在网络层甚至链路层直接实现自定义通信协议,显著降低延迟并提高效率。
- 使用Arduino环境为ESP32实现自定义网络协议不需要深厚的网络知识,通过esp_wifi原生API和lwip库即可实现原始套接字通信和802.11帧发送。
- 自定义协议特别适合对延迟敏感、资源受限或需要高效率的场景,如实时控制系统、传感器网络和低功耗应用。
- 虽然舍弃了TCP的可靠性,但通过自定义确认机制、校验和和简单加密,仍可构建稳定且安全的通信系统。
引言
想象一下你正在开派对,而TCP就像那个超级讲究礼节的宾客——先问"你好吗?“,等待"我很好,你呢?”,再回应"我也很好,谢谢",然后才开始真正的对话。天哪!这也太墨迹了吧!有时候,我们就想直接冲进去大喊:“派对开始啦!”,然后开始疯狂蹦迪——这就是我们今天要探讨的:抛开繁文缛节,让ESP32直接在网络层肆意狂欢!谁说通信必须按部就班?老规矩有时候就是用来打破的!
TCP:互联网的礼仪大师
TCP究竟是什么?
传输控制协议(TCP),互联网的脊梁,负责确保数据包从A点到B点的可靠传输。它就像那个神经质的朋友,寄包裹时非要买保险、要签收、要追踪号码,还要打电话确认你收到了没有。是的,非常可靠,但有时候——老实说——也是非常烦人!
TCP建立连接时的"三次握手"堪称数字世界最著名的礼仪:
- “嘿,我想跟你聊天!”(SYN)
- “好啊,我听到了,也想跟你聊!”(SYN+ACK)
- “太好了!我确认听到你也想聊,现在开始吧!”(ACK)
结束通话还要"四次挥手"——比分手还复杂!这一切都是为了确保可靠性,但在某些场景下,这种可靠性就像派对上的监护人——必要但扫兴。
TCP的优缺点
优点:
- 可靠传输:丢包?重传!乱序?重排!
- 流量控制:防止发送方淹没接收方
- 拥塞控制:体贴地照顾整个网络的感受
缺点:
- 协议开销大:每个数据包都带着厚重的头信息
- 延迟较高:握手、确认、等待重传都需要时间
- 资源消耗:对小型嵌入式设备来说可能是奢侈品
ESP32:口袋里的网络猛兽
ESP32不只是一个微控制器,它是一头伪装成芯片的野兽!双核处理器、WiFi、蓝牙、一大堆GPIO,还有各种外设——所有这些竟然塞进了指甲盖大小的封装里!难怪它成为IoT项目的宠儿。
ESP32的网络能力
ESP32内置的WiFi栈支持:
- 标准TCP/IP协议栈
- 2.4GHz WiFi(802.11 b/g/n)
- 软AP模式与Station模式
- 可同时作为AP和Station运行
- 最高150Mbps的数据传输率
但更令人兴奋的是,ESP32允许我们抛开传统,深入底层网络协议,自行设计通信方式。这就像有了厨房的钥匙,不再局限于点菜单上的食物,而是可以按自己的喜好创造美食!
网络分层:探索TCP之下的世界
在深入ESP32的直接网络层通信前,让我们理解网络的分层结构。传统的OSI模型将网络分为7层,而TCP/IP简化模型分为4层:
- 应用层:HTTP、FTP、SMTP等
- 传输层:TCP、UDP
- 网络层:IP
- 链路层:以太网、WiFi等
TCP位于传输层,而我们今天的冒险将跳过它,直接在网络层甚至链路层上构建通信。这就像绕过高速公路的收费站,走那些鲜为人知的小路——可能崎岖但兴奋刺激!
为什么要绕过TCP?
你可能会问:"TCP工作得很好,为什么要另起炉灶?"好问题!以下是一些compelling的理由:
- 极低延迟:没有握手、确认等礼节,数据即发即至
- 减少开销:更小的包头意味着更高的有效数据比例
- 资源节约:对内存和处理能力有限的设备尤为重要
- 自定义控制:完全掌控通信流程,按需调整
- 特殊应用:某些实时应用如游戏控制器、传感器网络等不需要TCP的可靠性而更看重速度
ESP32直接网络层通信:原理详解
理解原始套接字(Raw Sockets)
原始套接字允许程序直接访问底层网络协议,绕过传输层。这就像拥有VIP通道,可以跳过安检直接登机!在ESP32上,我们可以创建原始套接字来发送和接收IP数据包,甚至是以太网帧。
自定义协议设计
设计自己的协议时,需要考虑几个关键因素:
- 帧格式:定义数据包的结构,包括头部字段和载荷
- 寻址方案:如何标识和定位网络中的设备
- 错误检测:如何发现传输错误(如校验和)
- 数据分片与重组:大数据如何分割和重建
- 流控制:如何防止接收方被淹没
我们的简单协议可能看起来像这样:
[魔数(2字节)][源地址(4字节)][目标地址(4字节)][数据长度(2字节)][数据(变长)][校验和(4字节)]
魔数可以是0xE5P3,帮助接收方识别这是我们的协议包而非其他随机数据。
在Arduino中实现ESP32网络层直接通信
让我们用Arduino IDE为ESP32创建一个简单的直接网络层通信示例。首先是发送端:
#include <WiFi.h>
#include <lwip/ip.h>
#include <lwip/raw.h>
// WiFi凭据
const char* ssid = "你的WiFi名称";
const char* password = "你的WiFi密码";
// 自定义协议ID(魔数)
const uint16_t PROTOCOL_MAGIC = 0xE5P3;
// 目标设备的IP地址
IPAddress targetIP(192, 168, 1, 100);
// 原始套接字
struct raw_pcb* raw_pcb;
// 自定义数据包结构
typedef struct {
uint16_t magic; // 协议魔数
uint32_t sourceIP; // 源IP
uint32_t destIP; // 目标IP
uint16_t dataLength; // 数据长度
uint8_t data[256]; // 数据负载
uint32_t checksum; // 校验和
} CustomPacket;
// 计算校验和
uint32_t calculateChecksum(uint8_t* data, size_t length) {
uint32_t sum = 0;
for (size_t i = 0; i < length; i++) {
sum += data[i];
}
return sum;
}
// 准备自定义数据包
void preparePacket(CustomPacket* packet, const char* message) {
packet->magic = PROTOCOL_MAGIC;
packet->sourceIP = WiFi.localIP();
packet->destIP = (uint32_t)targetIP;
size_t messageLength = strlen(message);
packet->dataLength = messageLength;
memcpy(packet->data, message, messageLength);
// 计算除校验和外的所有字段的校验和
packet->checksum = calculateChecksum((uint8_t*)packet,
sizeof(CustomPacket) - sizeof(uint32_t));
}
// 发送回调函数
u8_t send_callback(struct raw_pcb* pcb, struct pbuf* p, const ip_addr_t* addr) {
// 可以在这里添加发送状态处理代码
return 0;
}
void setup() {
Serial.begin(115200);
// 连接WiFi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi连接成功");
Serial.print("IP地址: ");
Serial.println(WiFi.localIP());
// 创建原始套接字
ip_addr_t anyAddr;
IP4_ADDR(&anyAddr, 0, 0, 0, 0);
// 使用IP协议255表示自定义协议
raw_pcb = raw_new(255);
if (raw_pcb != NULL) {
raw_recv(raw_pcb, send_callback, NULL);
raw_bind(raw_pcb, &anyAddr);
Serial.println("原始套接字创建成功");
} else {
Serial.println("原始套接字创建失败");
}
}
void loop() {
// 每5秒发送一条消息
static unsigned long lastSendTime = 0;
unsigned long currentTime = millis();
if (currentTime - lastSendTime > 5000) {
lastSendTime = currentTime;
// 准备数据包
CustomPacket packet;
char message[100];
sprintf(message, "来自ESP32的直接网络层消息! 时间: %lu", currentTime);
preparePacket(&packet, message);
// 创建pbuf并填充数据
struct pbuf* p = pbuf_alloc(PBUF_TRANSPORT, sizeof

最低0.47元/天 解锁文章
926

被折叠的 条评论
为什么被折叠?



