ESP32学习笔记(9)——TCP服务端

发布者:EtherealLight最新更新时间:2025-03-04 来源: jianshu关键字:ESP32 手机看文章 扫描二维码
随时随地手机看文章

一、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 #include #include 'freertos/FreeRTOS.h'#include 'freertos/task.h'#include 'esp_system.h'#include 'esp_wifi.h'#include 'esp_event.h'#include 'esp_log.h'#include 'nvs_flash.h'#include 'esp_netif.h'#include 'protocol_examples_common.h'#include 'lwip/err.h'#include 'lwip/sockets.h'#include 'lwip/sys.h'#include #define PORT CONFIG_EXAMPLE_PORTstatic const char *TAG = 'example';static void do_retransmit(const int sock){

    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.

[1] [2] [3]
关键字:ESP32 引用地址:ESP32学习笔记(9)——TCP服务端

上一篇:ESP32学习笔记(10)——UDP客户端
下一篇:ESP32学习笔记(8)——TCP客户端

推荐阅读最新更新时间:2026-03-20 11:47

ESP32非易失性存储整型数据笔记
基于ESP-IDF4.1 1 #include stdio.h 2 #include freertos/FreeRTOS.h 3 #include freertos/task.h 4 #include esp_system.h 5 #include nvs_flash.h 6 #include nvs.h 7 8 void app_main(void) 9 { 10 // 初始化非易失性存储 11 esp_err_t err = nvs_flash_init(); 12 if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ER
[单片机]
ESP32使用SPIFFS文件系统笔记
基于ESP-IDF4.1 1 #include stdio.h 2 #include string.h 3 #include sys/unistd.h 4 #include sys/stat.h 5 #include esp_err.h 6 #include esp_log.h 7 #include esp_spiffs.h 8 9 static const char *TAG = example ; 10 11 void app_main(void) 12 { 13 ESP_LOGI(TAG, Initializing SPIFFS ); 14 15 esp_vfs_spiffs_conf_t c
[单片机]
ESP32低功耗模式
1.ESP32 系列芯片提供三种可配置的睡眠模式,针对这些睡眠模式,我们提供了了多种低功耗解决方案,用户可以结合具体需求选择睡眠模式并进行配置。三种睡眠模式如下: Modem-sleep 模式:CPU 可运行,时钟可被配置。Wi-Fi/蓝牙基带和射频关闭。 Light-sleep 模式:CPU 暂停运行,Wi-Fi/蓝牙基带和射频关闭。RTC 存储器和外设以及 ULP 协处理器运行。任何唤醒事件(MAC、主机、RTC 定时器或外部中断)都会唤醒芯片。 Deep-sleep 模式:CPU 和大部分外设都会掉电,Wi-Fi/蓝牙基带和射频关闭,只有 RTC 存储器和 RTC 外设以及 ULP 协处理器可以工作。Wi-Fi 和蓝牙
[单片机]
<font color='red'>ESP32</font>低功耗模式
ESP32高分辨率计时器笔记
尽管FreeRTOS提供了软件计时器,但这些计时器有一些限制: 最大分辨率等于RTOS滴答周期 计时器回调从低优先级任务分派 硬件计时器不受这两个限制,但是通常它们使用起来不太方便。例如,应用组件可能需要定时器事件在将来的特定时间触发,但是硬件定时器仅包含一个用于中断产生的“比较”值。这意味着需要在硬件计时器之上构建一些功能来管理挂起事件列表,以便在发生相应的硬件中断时可以调度这些事件的回调。 esp_timer 一组API提供了一次性的计时器和定期的计时器,微秒级的时间分辨率以及64位范围。 基于ESP-IDF4.1 1 #include stdio.h 2 #include string.h 3 #
[单片机]
(4)ESP32 Python 用OLED播放Bad Apple
之前已经实现过了,把OLED当作一个状态显示器。但是,仅仅显示文字肯定是不够炫酷的,因为有屏幕的地方就应该有Bad Apple。 这次我们尝试一下把OLED播放一下 Bad Apple. Bad Apple看似是一段视频,但是我们这么来想这个问题。视频打散成很多张图片,按照每秒12贞的播放,那不就是一个视频了么。 首先,我们来体验一次把图片展示在OLED上面,需要展示的图片要是pbm格式(PBM格式由Jef Poskanzer在20世纪80年代发明,为了便于通过电子邮件,用ASCII码表示单色位图,能够承受一般的文本格式的变动。) from ssd1306 import SSD1306_I2C ,framebuf from
[单片机]
ESP32实现远程MQTT VPN(远程 telnet / web 访问)遇到的问题
我这边的 ESP32 平台软件,实现了命令行(CLI)、telnet、http server。平时开发 IoT 软件的时候,可以通过 telnet 到设备,进行命令行的操作,定位问题,维护和调试设备。 IoT 设备给到用户后,设备通过 4G 上网,并通过 MQTT 与云平台连接。如果能够通过 MQTT 通道,实现远程的 telnet / web 访问,对设备的维护和故障定位非常有帮助。 我移植了开源的 MQTT_VPN 项目,但发现该项目不太稳定,经常会出现 exception 的问题。这些问题大都和 lwip 的协议有关。查看该项目的 issue,有用户报告类似的问题,但该项目已经好几年不维护了。 只能自己动手,做了一些
[单片机]
ESP32使用舵机库时的错误
请问我用的uno di r32 ESP32开发板,下载了ESP32舵机库放在了li'braries文件夹里,在ide烧录的时候说servo库与我运行的esp32开发板不兼容是怎么回事
[单片机]
ESP32 S3接ST7789屏幕线序
S3的SPI默认针与普通ESP32不一致,需要重新设置一下. tft = st7789py.ST7789(SPI(2, 30000000,mosi=Pin(21),sck=Pin(18)), 240, 240, reset=Pin(6), dc=Pin(2), cs=Pin(5), rotation=0) st7789 esp32针脚 GND GND VCC 3.3V SCL PIN 18 SDA PIN 21 RES PIN 6 DC PIN 2 CS PIN 5 BL 5V
[单片机]
小广播
最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

厂商技术中心

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

 
机器人开发圈

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2026 EEWORLD.com.cn, Inc. All rights reserved