ESP32 IDF开发 应用篇⑭ Wifi TCP客户端和服务器通信

ESP32 IDF开发 应用篇⑭ Wifi TCP客户端和服务器通信


别迷路-导航栏
快速导航找到你想要的(文章目录)

此篇文章如果对你有用,请点赞收藏,您的支持就是博主坚持的动力。

1、博主写这篇技术文章的目的:

(1)、了解 tcp udp的通信特点;
(2)、了解客户端和服务器的特点;
(3)、掌握tcp客户端和服务器编程;

2、 概述

在前面章节已经为大家介绍了ESP32 WIFI STA 及AP模式的基本应用,本章是基于wifi sta模式开发。Tcp的理论知识这里就不过多介绍,大家百度一下比我讲解起来好多了。
(1) Tcp 与udp的区别及各自的特点
TCP:
 •面向连接的:发数据前要进行连接。
 •可靠的连接:TCP 连接传送的数据,无差错,不丢失,不重复,且按序到达。
 •点到点:TCP 连接传送的数据,无差错,不丢失,不重复,且按序到达
 •最大长度有限:仅 1500 字节。(http 和 websocket 都是采用TCP通信)
UDP:
 •无连接的:发数据前不需要建立连接。
 •不可靠:尽最大努力交付,即不保证可靠交付。
 •支持一对一,一对多,多对一和多对多的交互通信
 •占用资源少,发送数据快。
(2) 客户端与服务端的区别
服务端:创建并监听多个连接,服务端可以监听多个客户端发过来的连接;
客户端:创建并发起连接,多个客户端可以连接到同一个服务端;

3、 Socket API库的介绍
1、 TCP 编程的客户端一般步骤是:
(1)创建一个 socket,用函数 socket();
(2)设置 socket 属性,用函数 setsockopt();(可选)
(3)绑定 IP 地址、端口等信息到 socket 上,用函数 bind();* 可选
(4)设置要连接的对方的 IP 地址和端口等属性;
(5)连接服务器,用函数 connect();
(6) 收发数据,用函数 send()和 recv(),或者 read()和 write();
(7)关闭网络连接;
2、TCP 编程的服务器端一般步骤是:
(1)创建一个 socket,用函数 socket();
(2)设置 socket 属性,用函数 setsockopt();(可选)
(3)绑定 IP 地址、端口等信息到 socket 上,用函数 bind();
(4)开启监听,用函数 listen();
(5)接收客户端上来的连接,用函数 accept();
(6)收发数据,用函数 send()和 recv(),或者 read()和 write();
(7)关闭网络连接; closesocket();
(8)关闭监听;

ESP32 使用的是 LwIP,LwIP 是特别适用于嵌入式设备的小型开源 TCP/IP 协议栈,对内
存资源占用很小。ESP-IDF 即是移植了 LwIP 协议栈。
我们这里直接使用标准 socket API接口,API函数只做简单的介绍,跟详细的socket API大家可以百度
 创建连接 socket();
 连接服务端函数:connect();
 关闭 socket 函数:close();
 获取 socket 错误代码:getsocketopt();
 接收数据函数:recv();
 发送数据函数:send();
 绑定函数:bing();
 监听函数:listen();
 获取连接函数:accept();

更多API 参考esp-idf\components\lwip\lwip\src\include\lwip\sockets.h

4、 软件设计

(1)、客户端软件设计
①、等待wifi sta连接好路由器
②、创建连接服务端的socket
client_connect_socket = socket(AF_INET, SOCK_STREAM, 0);
③、配置连接服务器信息,端口和IP
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
server_addr.sin_addr.s_addr = inet_addr(ip);
④、然后开始连接
connect(client_connect_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)
⑤收发数据使用recv();send();函数
(2)、服务端软件设计
①、等待wifi sta连接好路由器
②、创建服务端的socket
③、配置新建的server socket参数
④、绑定ip和端口号bind()
⑤、开始监听有没有客户端连接上来
listen(server_socket, 1) 并且设置允许最大连接客户端的数量
⑥、获取连接的socket
accept(server_socket, (struct sockaddr *)&client_addr, &socklen)
⑦、收发数据使用recv();send();函数

5、 实例

复制wifi sta工程改名字为 idf_wifi_tcp(因为本例程是基于wifi sta)即可 文件名字改为 idf_wifi_tcp.C makefile文件也改成 PROJECT_NAME := idf_wifi_tcp即可,然后复制一下代码测试。
在编译之前,为大家出入一个知识点,讲解一下如何在工程下面建立一个文件夹来管理.c和.h文件。
以此工程为例讲解
(1)、在idf_wifi_tcp文件夹下面新建文件夹network,并且添加自己的.c和.h文件
(2)、在network文件夹下面添加文件component.mk,文件内容为COMPONENT_ADD_INCLUDEDIRS := .
在这里插入图片描述

在这里插入图片描述

(3)、然后在工程的Makefile文件下面添加编译路径:
EXTRA_COMPONENT_DIRS := $(IDF_PATH)/examples/lyy/idf_wifi_tcp/network

在这里插入图片描述
(4)、最后make clean
make flash monitor
代码:idf_wifi_tcp.c文件

/**********************************************************************
*               文件名:            idf_wifi_tcp.c
*               创建人:            
*               创建日期:           
*               修改人:
*               修改日期:           
*               版本号:            V1.1
*               备注:
*               公司:
********************************************************************/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "nvs.h"
#include "esp_log.h"
#include "nvs_flash.h"

#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "lwip/err.h"
#include "lwip/sys.h"

#include "tcp_bsp.h"

static const char *TAG = "WIFI_TCP";

//事件标志组
EventGroupHandle_t xCreatedEventGroup_WifiConnect = NULL;

static int retry_num = 0;

/***********************************************************************
* 函数:  
* 描述:   wifi 回调函数
* 参数:
* 返回: 无
* 备注:
************************************************************************/
static void event_handler(void* arg, esp_event_base_t event_base,int32_t event_id, void* event_data)
{
    ESP_LOGI(TAG, "event_base: %s  ; event_id : %d",event_base,event_id);
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) 
    {
        esp_wifi_connect();//开始连接
    } 
    else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) //wifi断开时
    {
        if (retry_num < CONFIG_ESP_MAXIMUM_RETRY) //重新连接次数
        {
            esp_wifi_connect();
            retry_num++;
            ESP_LOGI(TAG, "retry to connect to the AP");
        } 
        else 
        {
            xEventGroupSetBits(xCreatedEventGroup_WifiConnect, WIFI_FAIL_BIT);
            xEventGroupClearBits(xCreatedEventGroup_WifiConnect, WIFI_CONNECTED_BIT);
        }
        ESP_LOGI(TAG,"connect to the AP fail");
    } 
    else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)//获得IP
    {
        ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
        ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
        retry_num = 0;
        xEventGroupSetBits(xCreatedEventGroup_WifiConnect, WIFI_CONNECTED_BIT);
        xEventGroupClearBits(xCreatedEventGroup_WifiConnect, WIFI_FAIL_BIT);
    }
}
/***********************************************************************
* 函数:  
* 描述:   wifi sta初始化代码相对比较固定,一般不需要做修改
* 参数:
* 返回: 无
* 备注:
************************************************************************/
void wifi_init_sta(void)
{
    ESP_ERROR_CHECK(esp_netif_init());//初始化TCP / IP堆栈和esp-netif
    ESP_ERROR_CHECK(esp_event_loop_create_default()); //创建默认event loop
    esp_netif_create_default_wifi_sta();//创建默认的WIFI STA。

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();//给wifi_init_config_t结构体初始化系统默认的数据
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));//使用默认的参数初始化wifi

    //注册wifi 连接过程中回调函数
    ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
    ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL));
    //设置账户和密码,在menuconfig中设置
    wifi_config_t wifi_config = {
        .sta = {
            .ssid = CONFIG_ESP_WIFI_SSID,
            .password = CONFIG_ESP_WIFI_PASSWORD
            //.ssid = "ssid",
            //.password = "password"
        },
    };
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );//设置为sta模式
    ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) );
    ESP_ERROR_CHECK(esp_wifi_start() );//启动

    ESP_LOGI(TAG, "wifi_init_sta finished.");
}
/***********************************************************************
* 函数:  
* 描述:   主函数
* 参数:
* 返回: 无
* 备注:
************************************************************************/
void app_main()
{    
    //初始化NVS
   esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
      ESP_ERROR_CHECK(nvs_flash_erase());
      ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    ESP_LOGI(TAG, "ESP_WIFI_MODE_STA");
    //创建事件标志组,用于等待wifi连接
    xCreatedEventGroup_WifiConnect = xEventGroupCreate();
    //WIFI sta初始化
    wifi_init_sta();
    AppTaskCreate();
}

代码:tcp_bsp.c文件

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>

#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_spi_flash.h"
#include "esp_wifi.h"

#include <sys/socket.h>
#include "freertos/event_groups.h"
#include "esp_event.h"
#include "esp_err.h"
#include "nvs_flash.h"
#include "esp_smartconfig.h"
#include "esp_netif.h"
#include "esp_wpa2.h"

#include "esp_log.h"
#include "esp_ota_ops.h"
#include "nvs.h"
#include "tcp_bsp.h"

//任务句柄 
static TaskHandle_t xHandleTaskTcpClinent = NULL;
static TaskHandle_t xHandleTaskTcpServer = NULL;

//任务堆栈大小,主要是函数嵌套参数入栈和局部变量的内存
#define TcpClinent_TASK_STACK_SIZE      8192
#define TcpServer_TASK_STACK_SIZE       8192

//任务优先级,越大越高,跟ucos相反
#define TcpClinent_TASK_PRIO            2
#define TcpServer_TASK_PRIO             3

static int client_connect_socket = 0;                     //客户端连接socket

static int server_socket = 0;                           //服务器创建的socket
static int server_connect_socket = 0;                   //有客户端连接到服务器的socket

//函数声明
static esp_err_t CreateTcpClient(const char *ip,uint16_t port);
static esp_err_t CreateTcpServer(bool isCreatServer,uint16_t port);
static void vTaskTcpClinent(void *pvParameters);
static void vTaskTcpServer(void *pvParameters);

/***********************************************************************
* 函数:  
* 描述:   创建tcp接收发送任务,给外部使用
* 参数:
* 返回: 无
* 备注:
************************************************************************/
void AppTaskCreate(void)
{
    xTaskCreate(vTaskTcpClinent,                    //任务函数 
            "vTaskTcpClinent",                      // 任务名 
            TcpClinent_TASK_STACK_SIZE,             // 任务栈大小,单位 word,也就是 4 字节 
            NULL,                                   //任务参数 
            TcpClinent_TASK_PRIO,                   //任务优先级
            &xHandleTaskTcpClinent);    
    xTaskCreate(vTaskTcpServer,                     //任务函数 
            "vTaskTcpServer",                       // 任务名 
            TcpServer_TASK_STACK_SIZE,              // 任务栈大小,单位 word,也就是 4 字节 
            NULL,                                   //任务参数 
            TcpServer_TASK_PRIO,                    //任务优先级
            &xHandleTaskTcpServer); 
}
/***********************************************************************
* 函数:  
* 描述:   创建tcp客户端连接
* 参数:ip:IP地址,port:端口号
* 返回: 无
* 备注:
************************************************************************/
static esp_err_t CreateTcpClient(const char *ip,uint16_t port)
{
    static struct sockaddr_in server_addr;                  //server地址
    //等待连接成功,或已经连接有断开连接,此函数会一直阻塞,只有有连接
   EventBits_t bits =  xEventGroupWaitBits(xCreatedEventGroup_WifiConnect,// 事件标志组句柄 
                WIFI_CONNECTED_BIT, // 等待bit0和bit1被设置 
                pdFALSE,                            //TRUE退出时bit0和bit1被清除,pdFALSE退出时bit0和bit1不清除
                pdFALSE,                            //设置为pdTRUE表示等待bit1和bit0都被设置,pdFALSE表示等待bit1或bit0其中一个被设置
                portMAX_DELAY);                     //等待延迟时间,一直等待 

    if (bits & WIFI_CONNECTED_BIT) 
    {
        //新建socket
        client_connect_socket = socket(AF_INET, SOCK_STREAM, 0);
        if (client_connect_socket < 0)
        {
            close(client_connect_socket);
            return ESP_FAIL;
        }
        //配置连接服务器信息
        server_addr.sin_family = AF_INET;
        server_addr.sin_port = htons(port);
        server_addr.sin_addr.s_addr = inet_addr(ip);
        //连接服务器
        if (connect(client_connect_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
        {
            close(client_connect_socket);
            return ESP_FAIL;
        }
        return ESP_OK;
    } 
    else
    {
        return ESP_FAIL;
    }
}
/***********************************************************************
* 函数:  
* 描述:   客户端任务
* 参数:
* 返回: 无
* 备注:
************************************************************************/
static void vTaskTcpClinent(void *pvParameters)
{
    int len = 0;            //长度
    uint8_t databuff[512];    //缓存
    //创建tcp客户端
    if (CreateTcpClient(TCP_SERVER_ADRESS,TCP_SERVER_PORT) == ESP_OK)
    {
        ESP_LOGI("vTaskTcpClinent", "connect OK");
    }
    else
    {
        ESP_LOGI("vTaskTcpClinent", "connect FAIL");
    }
    for (;;)
    {
        //读取接收数据
        len = recv(client_connect_socket, databuff, sizeof(databuff), 0);//阻塞函数
        if (len > 0)//接收到数据
        {
            send(client_connect_socket, databuff, len, 0);
        }
        else
        {
            //断开连接
            if (CreateTcpClient(TCP_SERVER_ADRESS,TCP_SERVER_PORT)== ESP_OK)
            {
                ESP_LOGI("vTaskTcpClinent", "connect OK");
            }
            vTaskDelay(500 / portTICK_PERIOD_MS);
        }
        vTaskDelay(50 / portTICK_PERIOD_MS);
    }
}

/***********************************************************************
* 函数:  
* 描述:   创建服务端监听任务
* 参数:isCreatServer:true创建socket并监听,false只监听,port监听的端口号
* 返回: 无
* 备注:
************************************************************************/
static esp_err_t CreateTcpServer(bool isCreatServer,uint16_t port)
{
    static struct sockaddr_in client_addr;   
    static unsigned int socklen = sizeof(client_addr);      //地址长度
    //等待连接成功,或已经连接有断开连接,此函数会一直阻塞,只有有连接
   EventBits_t bits =  xEventGroupWaitBits(xCreatedEventGroup_WifiConnect,// 事件标志组句柄 
                WIFI_CONNECTED_BIT,              // 等待bit0和bit1被设置 
                pdFALSE,                            //TRUE退出时bit0和bit1被清除,pdFALSE退出时bit0和bit1不清除
                pdFALSE,                            //设置为pdTRUE表示等待bit1和bit0都被设置,pdFALSE表示等待bit1或bit0其中一个被设置
                portMAX_DELAY);                     //等待延迟时间,一直等待 

    if (bits & WIFI_CONNECTED_BIT) 
    {
        if (isCreatServer == true)
        {
            server_socket = socket(AF_INET, SOCK_STREAM, 0);
            if (server_socket < 0)
            {
                close(server_socket);
                return ESP_FAIL;
            }
            //配置新建server socket参数
            client_addr.sin_family = AF_INET;
            client_addr.sin_port = htons(port);
            client_addr.sin_addr.s_addr = htonl(INADDR_ANY);
            //bind:地址的绑定
            if (bind(server_socket, (struct sockaddr *)&client_addr, sizeof(client_addr)) < 0)
            {
                //bind失败后,关闭新建的socket,等待下次新建
                close(server_socket);
                return ESP_FAIL;
            }
        }
        
        //listen,下次时,直接监听
        if (listen(server_socket, 1) < 0)
        {
            //listen失败后,关闭新建的socket,等待下次新建
            close(server_socket);
            return ESP_FAIL;
        }
        //accept,搜寻全连接队列
        server_connect_socket = accept(server_socket, (struct sockaddr *)&client_addr, &socklen);
        if (server_connect_socket < 0)
        {
            //accept失败后,关闭新建的socket,等待下次新建
            close(server_socket);
            return ESP_FAIL;
        }
        return ESP_OK;
    }
    else
    {
        return ESP_FAIL;
    }

}
/***********************************************************************
* 函数:  
* 描述:   服务端任务
* 参数:
* 返回: 无
* 备注:
************************************************************************/
static void vTaskTcpServer(void *pvParameters)
{
    int len = 0;            //长度
    uint8_t databuff[512];    //缓存
    //创建tcp客户端
    CreateTcpServer(true,TCP_CLIENT_PORT);
    for (;;)
    {
        //读取接收数据
        len = recv(server_connect_socket, databuff, sizeof(databuff), 0);//阻塞函数
        if (len > 0)//接收到数据
        {
            send(server_connect_socket, databuff, len, 0);
        }
        else
        {
            //断开连接
            CreateTcpServer(false,TCP_CLIENT_PORT);  
            vTaskDelay(500 / portTICK_PERIOD_MS);
        }
        vTaskDelay(50 / portTICK_PERIOD_MS);
    }
}
 

代码:tcp_bsp.h文件

#ifndef __TCP_BSP_H__
#define __TCP_BSP_H__
#ifdef __cplusplus
extern "C" {
#endif

#define TCP_SERVER_ADRESS       "192.168.33.16"     //作为client,要连接TCP服务器地址
#define TCP_SERVER_PORT          5200               //服务端端口

#define TCP_CLIENT_PORT          5000               //TCP客户端端口

#define WIFI_CONNECTED_BIT  BIT0
#define WIFI_FAIL_BIT       BIT1
extern EventGroupHandle_t xCreatedEventGroup_WifiConnect;

void AppTaskCreate(void);


#ifdef __cplusplus
}
#endif
#endif /*#ifndef __TCP_BSP_H__*/

6、 调试结果

方法:打开网络调试助手这是这是为TCP Server,根据IP调整代码中的IP地址和端口

#define TCP_SERVER_ADRESS       "192.168.33.16"     //作为client,要连接TCP服务器地址
#define TCP_SERVER_PORT          5200               //服务端端口
#define TCP_CLIENT_PORT          5000               //TCP客户端端口

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

所有文章源代码:https://download.csdn.net/download/lu330274924/88518092

  • 6
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
ESP32-C3 上,你可以通过以下代码来设置 TCP 客户端在连接 WiFi 时的超时时间: ```c #include "esp_wifi.h" // 设置 WiFi 连接超时时间为 10 秒 #define WIFI_CONNECT_TIMEOUT_MS (10000) // 在连接 WiFi 时设置超时时间 esp_err_t wifi_connect(const char *ssid, const char *password) { esp_err_t ret = ESP_OK; wifi_config_t wifi_config = { 0 }; strncpy((char *)wifi_config.sta.ssid, ssid, sizeof(wifi_config.sta.ssid)); strncpy((char *)wifi_config.sta.password, password, sizeof(wifi_config.sta.password)); ret = esp_wifi_set_mode(WIFI_MODE_STA); if (ret != ESP_OK) { return ret; } ret = esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config); if (ret != ESP_OK) { return ret; } ret = esp_wifi_start(); if (ret != ESP_OK) { return ret; } uint32_t start_time = esp_timer_get_time(); wifi_ap_record_t ap_info = { 0 }; while (true) { ret = esp_wifi_sta_get_ap_info(&ap_info); if (ret == ESP_OK) { break; } uint32_t time_elapsed = (esp_timer_get_time() - start_time) / 1000; if (time_elapsed >= WIFI_CONNECT_TIMEOUT_MS) { ESP_LOGE(TAG, "WiFi connection timeout"); return ESP_ERR_TIMEOUT; } vTaskDelay(pdMS_TO_TICKS(100)); } ESP_LOGI(TAG, "Connected to WiFi AP %s", ap_info.ssid); return ESP_OK; } ``` 在这个代码中,`WIFI_CONNECT_TIMEOUT_MS` 宏定义了 WiFi 连接的超时时间。在 `wifi_connect()` 函数中,我们使用了 `esp_wifi_sta_get_ap_info()` 来获取连接的 WiFi AP 信息,如果在超时时间内没有获取到 AP 信息,则会返回超时错误。你可以根据需要来调整超时时间。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

物联网程序猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值