本章学习lwIP 的 Socket接口如何配置 TCP客户端,并在此基础上实现收发功能。
        本章分为如下几个部分:
53.1 Socket 编程 TCPClient 连接流程
53.2 硬件设计
53.3 软件设计
53.4 下载验证

53.1 Socket 编程 TCPClient 连接流程

        在实现 TCP 协议之前,用户需要按照以下步骤配置结构体 sockaddr_in 的成员变量,以便建
立 TCPClient 连接:
①:配置 ESP32-S3 设备连接网络(必须的,因为 WiFi 是无线通信,所以需搭建通信桥梁)。
②:将 sin_family 设置为 AF_INET,表示使用 IPv4 网络协议。
③:设置 sin_port 为所需的端口号,例如 8080。
④:设置 sin_addr.s_addr 为远程 IP 地址。
⑤:调用函数 Socket 创建 Socket 连接。请注意,该函数的第二个参数指定连接类型。
SOCK_STREAM 表示 TCP 连接,而 SOCK_DGRAM 表示 UDP 连接。
⑥:调用函数 connect 连接远程 IP 地址。
⑦:调用适当的收发函数来接收或发送数据。
通过遵循这些步骤,用户可成功地配置并建立TCPClient连接,以实现数据的发送和接收。

53.2 硬件设计

1. 例程功能
        本章实验功能简介:
        本实验主要通过Socket编程接口实现了一个TCPClient客户端。这个客户端具有以下功能:
①:可以通过按键发送 TCPClient 数据发送至服务器。
②:能够接收服务器发送的数据。
③:实时将接收到的数据显示在 LCD 屏幕上。
        通过这个实验,可深入了解 TCP 协议的工作原理,并掌握如何使用 Socket 编程接口来实现 TCP 通信。这对于开发基于 TCP 的网络应用程序非常有用,例如实时传输、文件传输等。

2. 硬件资源
        1) LED 灯
                LED-IO1
        2) XL9555
                IIC_INT-IO0(需在 P5 连接 IO0)
                IIC_SDA-IO41
                IIC_SCL-IO42
        3) SPILCD
                CS-IO21
                SCK-IO12
                SDA-IO11
                DC-IO40(在 P5 端口,使用跳线帽将 IO_SET 和 LCD_DC 相连)
                PWR- IO1_3(XL9555)
                RST- IO1_2(XL9555)
        4) ESP32-S3 内部 WiFi

3. 原理图
        本章实验使用的 WiFi 为 ESP32-S3 的片上资源,因此并没有相应的连接原理图。

53.3 软件设计
53.3.1 程序流程图

        本实验的程序流程图:

图 53.3.1.1 程序流程图

53.3.2 程序解析

        在本章节中,主要关注两个文件: lwip_demo.c 和 lwip_demo.h。 lwip_demo.h 文件主要定义了发送标志位并声明了 lwip_demo 函数,这部分相对简单,所以我们暂不详细解释。主要关注点是 lwip_demo.c 文件中的函数。在 lwip_demo 函数中,我们配置了相关的 TCPClient 参数,
并创建了一个名为 lwip_send_thread 的发送数据线程。这个线程通过调用 scokec 函数来发送数据
到服务器。接下来,将分别详细解释 lwip_demo 函数和 lwip_send_thread 任务。

/* 需要自己设置远程IP地址 */
 #define IP_ADDR   "192.168.1.2"
 
 #define LWIP_DEMO_RX_BUFSIZE         128                        /* 最大接收数据长度 */
 #define LWIP_DEMO_PORT               8080                       /* 连接的本地端口号 */
 #define LWIP_SEND_THREAD_PRIO       ( tskIDLE_PRIORITY + 3 )    /* 发送数据线程优先级 */
 /* 接收数据缓冲区 */
 char g_lwip_demo_recvbuf[LWIP_DEMO_RX_BUFSIZE]; 
 
 /* 发送数据内容 */
 uint8_t g_lwip_demo_sendbuf[] = "I am ESP32 S3, TCP test.\r\n";
 /* 数据发送标志位 */
 uint8_t g_lwip_send_flag;
 int g_sock = -1;
 int g_lwip_connect_state = 0;
 static void lwip_send_thread(void *arg);
 
 
 /**
  * @brief       发送数据线程
  * @param       无
  * @retval      无
  */
 void lwip_data_send(void)
 {
     xTaskCreate(lwip_send_thread, "lwip_send_thread", 4096, NULL, LWIP_SEND_THREAD_PRIO, NULL);
 }
 
 /**
  * @brief       lwip_demo实验入口
  * @param       无
  * @retval      无
  */
 void lwip_demo(void)
 {
     struct sockaddr_in atk_client_addr;
     err_t err;
     int recv_data_len;
     char host_ip[] = IP_ADDR;
     lwip_data_send();                                           /* 创建发送数据线程 */

     while (1)
     {
 sock_start:
         g_lwip_connect_state = 0;
         inet_pton(AF_INET, host_ip, &atk_client_addr.sin_addr);
         atk_client_addr.sin_family = AF_INET;                   /* 表示IPv4网络协议 */
         atk_client_addr.sin_port = htons(LWIP_DEMO_PORT);       /* 端口号 */
         g_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);               /* 可靠数据流交付服务既是TCP协议 */
         memset(&(atk_client_addr.sin_zero), 0, sizeof(atk_client_addr.sin_zero));
         
         /* 连接远程IP地址 */
         err = connect(g_sock, (struct sockaddr *)&atk_client_addr, sizeof(atk_client_addr));
 
         if (err == -1)
         {
             ESP_LOGE("TAG", "Socket unable to connect: errno %d", errno);
             goto sock_start;
         }
 
         g_lwip_connect_state = 1;
         ESP_LOGI("TAG", "Successfully connected");
         while (1)
         {
             recv_data_len = recv(g_sock,g_lwip_demo_recvbuf,
                                 sizeof(g_lwip_demo_recvbuf) - 1,0);
        
             if (recv_data_len < 0)
             {
                 g_lwip_connect_state = 0;
                 ESP_LOGE("TAG", "recv failed: errno %d", errno);
                 break;
             }
             else
             {
                g_lwip_demo_recvbuf[recv_data_len] = 0;
                ESP_LOGI("TAG", "Received %d bytes from %s:", recv_data_len, host_ip);
                ESP_LOGI("TAG", "%s", g_lwip_demo_recvbuf);
            }
        }

        if (g_sock != -1)
        {
            shutdown(g_sock, 0);
            close(g_sock);
        }
     }
 }
 
 /**
  * @brief       发送数据线程函数
  * @param       pvParameters : 传入参数(未用到)
  * @retval      无
  */
 void lwip_send_thread(void *pvParameters)
 {
     pvParameters = pvParameters;
     
     err_t err;
     
     while (1)
     {
         while (1)
         {
             if(((g_lwip_send_flag & LWIP_SEND_DATA) == LWIP_SEND_DATA) && (g_lwip_connect_state == 1)) /* 有数据要发送 */
             {
                 err = send(g_sock, g_lwip_demo_sendbuf, sizeof(g_lwip_demo_sendbuf),0);
                 
                 if (err < 0)
                 {
                     break;
                 }
                 
                 g_lwip_send_flag &= ~LWIP_SEND_DATA;
             }
             
             vTaskDelay(pdMS_TO_TICKS(10));
         }
         
         shutdown(g_sock, 0);
         close(g_sock);
     }
 }
 

        在上述源码中,首先创建了一个用于发送 ESP32-S3 设备数据的任务。然后,对 TCPClient进行网络参数配置,并调用 connect 函数来建立与远程服务器的连接。当连接成功时,系统将进入接收轮询任务。如果出现断开连接的情况,系统将尝试重新连接服务器。在发送线程中,发送数据前会检查标志位。如果标志位有效,则通过 send() 函数发送数据并重置标志位。

53.4 下载验证

        实验前需要在程序中,将相关配置修改为自己的配置,具体下面两点:

  • Wifi账号和密码:首先需要设置好能够连接的网络账号(将宏DEFAULT_SSID修改为自己的账号,将宏DEFAULT_PWD修改为自己的密码);
  • 电脑IP地址:设置自己电脑的远程IP地址(修改宏IP_ADDR为自己电脑IP地址,方法是在Windows的命令提示符中输入ipconfig,就会看到电脑IP地址)。

        然后,使用电脑作为终端,确保它与 ESP32-S3 设备处于同一网络段内。当 ESP32-S3 设备成功连接到网络时,它的 LCD 显示屏上会显示相应的内容:

图 53.4.1 设备连接到网络时 LCD 显示的信息

        打开网络调试助手,然后配置网络参数,如 UDP 协议、端口号、目标主机设置等,设置内容如下图所示,在确保网络连接正常后,通过按下开发板上的 KEY0 按键来发送数据(存在数组g_lwip_demo_sendbuf[] = "I am ESP32 S3, TCP test.\r\n";)至网络调试助手。
        当网络调试助手接收到开发板发送的字符串时,它会在显示区域展示这个信息。此外,用户还可以在调试助手的发送区域输入要发送的数据,然后点击发送键(I am a computer, TCP test.),将数据发送至ESP32-S3 设备。此时, ESP32-S3 的串口将打印接收到的数据,具体操作和输出如下图所示。

图 53.4.3 接收网络调试助手的数据

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐