一、TCP与UDP优缺点
1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接。
2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付。
TCP通过校验和,重传控制,序号标识,滑动窗口、确认应答实现可靠传输。如丢包时的重发控制,还可以对次序乱掉的分包进行顺序控制。3、UDP具有较好的实时性,工作效率比TCP高,适用于对高速传输和实时性有较高的通信或广播通信。
4、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信。
5、TCP对系统资源要求较多,UDP对系统资源要求较少。
二、概述
ESP-IDF使用开源 lwIP轻量级的TCP / IP堆栈。ESP-IDF版本lwIP(esp-lwip)与上游项目相比有一些修改和补充。
ESP-IDF支持以下功能 lwIP TCP / IP堆栈功能:
BSD套接字API
Netconn API已启用,但ESP-IDF应用程序未正式支持
BSD套接字API
BSD套接字API是一个通用的跨平台TCP / IP套接字API,该API起源于UNIX的Berkeley标准发行版,但现在已在POSIX规范的一部分中进行了标准化。BSD套接字有时称为POSIX套接字或Berkeley套接字。
正如ESP-IDF中实施的那样,lwIP支持BSD套接字API的所有常用用法。
ESP-IDF 编程指南——ESP-NETIF
ESP-IDF 编程指南——lwIP
三、API说明
以下 BSD Socket 接口位于 lwip/lwip/src/include/lwip/sockets.h。
socket()
bind()
accept()
shutdown()
getpeername()
getsockopt()&setsockopt()(请参阅套接字选项)
close()(通过虚拟文件系统组件)
read(),readv(),write(),writev()(经由虚拟文件系统部件)
recv(),recvmsg(),recvfrom()
send(),sendmsg(),sendto()
select()(通过虚拟文件系统组件)
poll()(注意:在ESP-IDF上,poll()是通过内部调用select来实现的,因此,select()如果有可用的方法选择,建议直接使用。)
fcntl()(请参阅fcntl)
非标准功能:
ioctl()(请参阅ioctls)
四、TCP服务端
4.1 主要流程

4.1.1 第一步:新建socket
int addr_family = 0;int ip_protocol = 0;addr_family = AF_INET;ip_protocol = IPPROTO_IP;int listen_sock = socket(addr_family, SOCK_STREAM, ip_protocol);if(listen_sock < 0) {
ESP_LOGE(TAG, 'Unable to create socket: errno %d', errno);}
4.1.2 第二步:配置服务器信息
#define TCP_PORT 3333 // TCP服务器端口号struct sockaddr_in dest_addr;dest_addr.sin_family = AF_INET;dest_addr.sin_addr.s_addr = htonl(INADDR_ANY);dest_addr.sin_port = htons(TCP_PORT);
4.1.3 第三步:绑定地址
int err = bind(listen_sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr));if(err != 0) {
ESP_LOGE(TAG, 'Socket unable to bind: errno %d', errno);
ESP_LOGE(TAG, 'IPPROTO: %d', addr_family);
close(listen_sock);}ESP_LOGI(TAG, 'Socket bound, port %d', PORT);
4.1.4 第四步:开始监听
err = listen(listen_sock, 1); // 这里为啥是1,网上大多数是5if(err != 0) {
ESP_LOGE(TAG, 'Error occurred during listen: errno %d', errno);
close(listen_sock);}
4.1.5 第五步:等待客户端连接
while(1){
struct sockaddr_in6 source_addr; // Large enough for both IPv4 or IPv6
uint addr_len = sizeof(source_addr);
int connect_sock = accept(listen_sock, (struct sockaddr *)&source_addr, &addr_len);
if(connect_sock < 0)
{
ESP_LOGE(TAG, 'Unable to accept connection: errno %d', errno);
close(listen_sock);
} }
4.1.6 第六步:接收数据
int len;char rx_buffer[128];while(1){
memset(rx_buffer, 0, sizeof(rx_buffer)); // 清空缓存
len = recv(connect_sock, rx_buffer, sizeof(rx_buffer), 0); // 读取接收数据
if(len < 0)
{
ESP_LOGE(TAG, 'Error occurred during receiving: errno %d', errno);
}
else if (len == 0)
{
ESP_LOGW(TAG, 'Connection closed');
}
else
{
ESP_LOGI(TAG, 'Received %d bytes: %s', len, rx_buffer);
}}
4.1.7 第七步:发送数据
send(connect_socket, rx_buffer, sizeof(rx_buffer, 0);
4.2 配置SSID和密码连接WIFI创建TCP服务端
TCP Server类似

使用 esp-idfexamplesprotocolssocketstcp_server 中的例程
/* BSD Socket API Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/#include int len; char rx_buffer[128]; do { len = recv(sock, rx_buffer, sizeof(rx_buffer) - 1, 0); if (len < 0) { ESP_LOGE(TAG, 'Error occurred during receiving: errno %d', errno); } else if (len == 0) { ESP_LOGW(TAG, 'Connection closed'); } else { rx_buffer[len] = 0; // Null-terminate whatever is received and treat it like a string ESP_LOGI(TAG, 'Received %d bytes: %s', len, rx_buffer); // send() can return less bytes than supplied length. // Walk-around for robust implementation. int to_write = len; while (to_write > 0) { int written = send(sock, rx_buffer + (len - to_write), to_write, 0); if (written < 0) { ESP_LOGE(TAG, 'Error occurred during sending: errno %d', errno); } to_write -= written; } } } while (len > 0);}static void tcp_server_task(void *pvParameters){ char addr_str[128]; int addr_family = (int)pvParameters; int ip_protocol = 0; struct sockaddr_in6 dest_addr; if (addr_family == AF_INET) { struct sockaddr_in *dest_addr_ip4 = (struct sockaddr_in *)&dest_addr; dest_addr_ip4->sin_addr.s_addr = htonl(INADDR_ANY); dest_addr_ip4->sin_family = AF_INET; dest_addr_ip4->sin_port = htons(PORT); ip_protocol = IPPROTO_IP; } else if (addr_family == AF_INET6) { bzero(&dest_addr.sin6_addr.un, sizeof(dest_addr.sin6_addr.un)); dest_addr.sin6_family = AF_INET6; dest_addr.sin6_port = htons(PORT); ip_protocol = IPPROTO_IPV6; } int listen_sock = socket(addr_family, SOCK_STREAM, ip_protocol); if (listen_sock < 0) { ESP_LOGE(TAG, 'Unable to create socket: errno %d', errno); vTaskDelete(NULL); return; }#if defined(CONFIG_EXAMPLE_IPV4) && defined(CONFIG_EXAMPLE_IPV6) // Note that by default IPV6 binds to both protocols, it is must be disabled // if both protocols used at the same time (used in CI) int opt = 1; setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); setsockopt(listen_sock, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt));#endif ESP_LOGI(TAG, 'Socket created'); int err = bind(listen_sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr)); if (err != 0) { ESP_LOGE(TAG, 'Socket unable to bind: errno %d', errno); ESP_LOGE(TAG, 'IPPROTO: %d', addr_family); goto CLEAN_UP; } ESP_LOGI(TAG, 'Socket bound, port %d', PORT); err = listen(listen_sock, 1); if (err != 0) { ESP_LOGE(TAG, 'Error occurred during listen: errno %d', errno); goto CLEAN_UP; } while (1) { ESP_LOGI(TAG, 'Socket listening'); struct sockaddr_in6 source_addr; // Large enough for both IPv4 or IPv6 uint addr_len = sizeof(source_addr); int sock = accept(listen_sock, (struct sockaddr *)&source_addr, &addr_len); if (sock < 0) { ESP_LOGE(TAG, 'Unable to accept connection: errno %d', errno); break; } // Convert ip address to string if (source_addr.sin6_family == PF_INET) { inet_ntoa_r(((struct sockaddr_in *)&source_addr)->sin_addr.s_addr, addr_str, sizeof(addr_str) - 1); } else if (source_addr.sin6_family == PF_INET6) { inet6_ntoa_r(source_addr.sin6_addr, addr_str, sizeof(addr_str) - 1); } ESP_LOGI(TAG, 'Socket accepted ip address: %s', addr_str); do_retransmit(sock); shutdown(sock, 0); close(sock); }CLEAN_UP: close(listen_sock); vTaskDelete(NULL);}void app_main(void){ ESP_ERROR_CHECK(nvs_flash_init()); ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK(esp_event_loop_create_default()); /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig. * Read 'Establishing Wi-Fi or Ethernet Connection' section in * examples/protocols/README.md for more information about this function.
上一篇:ESP32学习笔记(10)——UDP客户端
下一篇:ESP32学习笔记(8)——TCP客户端
推荐阅读最新更新时间:2026-03-20 11:47
- 使用 ON Semiconductor 的 FAN2518S 的参考设计
- LTC1530S8、3.3V/3A 稳压器
- 使用 ON Semiconductor 的 ADP3167 的参考设计
- 使用 Analog Devices 的 LT3420EDD 的参考设计
- 基于Kinetis® M的低成本单相电表参考设计
- LTC3708、具有上升/下降轨跟踪功能的 2.5V/15A 和 1.2V/15A 稳压器
- NXQ1TXH5插件板
- 应变仪仪表放大器
- WRL-13287,基于 ESP8266 802.11 无线局域网的 SparkFun Wi-Fi Shield
- 4.1W、3-LED 通用 LED 照明驱动器



【Follow me第三季第3期】代码
【Follow me第三季第3期】XIAO ESP32S3任务代码提交
Follow me第三季第3期源代码
现代雷达系统的信号设计
LTC6102HVHMS8
BFR340T






京公网安备 11010802033920号