ESP32-C3_TCP

发布于:2025-08-16 ⋅ 阅读:(16) ⋅ 点赞:(0)

这章主要讲述下 TCP
1:环境
VSCODE+IDF5.4
ESP32-C3

2:直接上代码

#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_http_client.h"
#include "esp_netif.h"  // 替代 tcpip_adapter 的新网络接口组件
#include <string.h>



// 配置
#define WIFI_SSID "*******"
#define WIFI_PASSWORD "********"
#define SERVER_HOST "192.168.1.3"
#define SERVER_PORT 8888
#define QUEUE_LEN 10        // 队列长度
#define DATA_BUF_SIZE 1024  // 数据缓冲区大小

// 日志标签
static const char *TAG = "WIFI_TCP_EXAMPLE";


// 数据结构体(队列中传递的数据格式)
typedef struct {
    char data[DATA_BUF_SIZE];
    int len;  // 实际数据长度
} DataPacket;

// 全局队列句柄(发送和接收队列)
QueueHandle_t tx_queue;  // 处理→发送
QueueHandle_t rx_queue;  // 接收→处理

// TCP连接句柄(全局,供发送/接收任务使用,需确保线程安全)
int g_socket_fd = -1;
bool b_connect =false ;

static bool ready_connect();
// 事件组:用于等待 WiFi 连接成功
static EventGroupHandle_t s_wifi_event_group;
const int WIFI_CONNECTED_BIT = BIT0;
// 1. 发送任务:从tx_queue取数据,通过TCP发送
static void tx_task(void *pvParameters) {
    DataPacket packet;
    while (1) {
        // 等待队列中有数据(阻塞等待,超时100ms)
        if (xQueueReceive(tx_queue, &packet, pdMS_TO_TICKS(100)) == pdTRUE) {
            if (g_socket_fd == -1) {
                 ESP_LOGE(TAG,"SEND TASK:disconnect ,data throw");
               // printf("发送任务:未连接,丢弃数据\n");
                continue;
            }
            // 发送数据到服务器
            ssize_t sent = send(g_socket_fd, packet.data, packet.len, 0);
            if (sent < 0) {
                //printf("发送失败\n");
                ESP_LOGE(TAG,"SEND fail,retry?");
                // 可在此处触发重连逻辑
            } else {
                ESP_LOGE(TAG,"SEND SUCCESS %.*s",packet.len, packet.data);
               // printf("发送成功:%.*s\n", packet.len, packet.data);
            }
        }
    }
}

// 2. 接收任务:从TCP读取数据,存入rx_queue
static void rx_task(void *pvParameters) {
    DataPacket packet;
    while (1) {
        if (g_socket_fd == -1) {
            vTaskDelay(pdMS_TO_TICKS(100));  // 未连接则休眠
            if(b_connect){
                ready_connect();
            }
            continue;
        }
        // 从TCP读取数据(非阻塞,避免卡死)
        ssize_t recv_len = recv(g_socket_fd, packet.data, DATA_BUF_SIZE-1, 0);
        if (recv_len > 0) {
            packet.len = recv_len;
            packet.data[recv_len] = '\0';  // 确保字符串结束
            ESP_LOGE(TAG,"RECV_DATA:%.*s", recv_len, packet.data);
            // 存入接收队列(若队列满则丢弃)
            if (xQueueSend(rx_queue, &packet, 0) != pdTRUE) {
                ESP_LOGE(TAG,"RECV QUEUE IS FULL");
            }
        } else if (recv_len < 0) {
            ESP_LOGE(TAG,"SERVER DISCONNECT[%d]",recv_len);
          //  closesocket(g_socket_fd);
            g_socket_fd = -1;  // 标记连接断开
          //  b_connect =false;
            break;  // 退出任务,等待重连后重启
        }
        vTaskDelay(pdMS_TO_TICKS(10));  // 短暂休眠,降低CPU占用
    }
}

// 3. 处理任务:从rx_queue取数据,处理后将结果存入tx_queue
static void process_task(void *pvParameters) {
    DataPacket in_packet, out_packet;
    while (1) {
        // 等待接收队列有数据(阻塞等待)
        if (xQueueReceive(rx_queue, &in_packet, portMAX_DELAY) == pdTRUE) {
            // 示例处理逻辑:将收到的数据加上"Reply: "前缀,作为响应
            snprintf(out_packet.data, DATA_BUF_SIZE, "Reply: %.*s", 
                     in_packet.len, in_packet.data);
            out_packet.len = strlen(out_packet.data);
            // 发送到发送队列
            if (xQueueSend(tx_queue, &out_packet, pdMS_TO_TICKS(100)) != pdTRUE) {
             //   printf("发送队列满,处理结果丢弃\n");
                ESP_LOGE(TAG,"SEND QUEUE IS  FULL,MSG throw");
            }
        }
    }
}

static bool ready_connect(){
    char host_ip[] = SERVER_HOST;
    int addr_family = 0;
    int ip_protocol = 0;
    #define PORT SERVER_PORT
#if defined(CONFIG_EXAMPLE_IPV4)
        struct sockaddr_in dest_addr;
        inet_pton(AF_INET, host_ip, &dest_addr.sin_addr);
        dest_addr.sin_family = AF_INET;
        dest_addr.sin_port = htons(SERVER_PORT);
        addr_family = AF_INET;
        ip_protocol = IPPROTO_IP;
#elif defined(CONFIG_EXAMPLE_SOCKET_IP_INPUT_STDIN)
        struct sockaddr_storage dest_addr = { 0 };
        ESP_ERROR_CHECK(get_addr_from_stdin(PORT, SOCK_STREAM, &ip_protocol, &addr_family, &dest_addr));
#endif
    int sock =  socket(addr_family, SOCK_STREAM, ip_protocol);
    if (sock < 0) {
        ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
        return false ;
    }
    ESP_LOGI(TAG, "Socket created, connecting to %s:%d", host_ip, PORT);

    int err = connect(sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
    if (err != 0) {
        ESP_LOGE(TAG, "Socket unable to connect: errno %d", errno);
        return false ;
    }
    ESP_LOGI(TAG, "Successfully connected[%d]",sock);
    g_socket_fd = sock;//赋值
    b_connect= true;
    return true ;
}
// TCP连接函数(连接成功后启动收发任务)
static void tcp_connect(void) {
    // (省略创建socket、connect的过程,参考之前的示例)
    if(!ready_connect()){
        ESP_LOGI(TAG, "fail connected");
        return ;
    }
    // 假设连接成功,g_socket_fd被赋值为有效句柄
  //  printf("TCP连接成功\n");
    
    // 启动/重启收发任务(确保任务只运行一个实例)
    static TaskHandle_t rx_task_handle = NULL;
    static TaskHandle_t tx_task_handle = NULL;
    if (rx_task_handle == NULL) {
        xTaskCreate(rx_task, "rx_task", 4096, NULL, 5, &rx_task_handle);
    } else {
        vTaskResume(rx_task_handle);  // 若已存在则唤醒
    }
    if (tx_task_handle == NULL) {
        xTaskCreate(tx_task, "tx_task", 4096, NULL, 5, &tx_task_handle);
    }
     ESP_LOGI(TAG,"create task finish[%d]",g_socket_fd);
     {
        //临时发送一条数据
         DataPacket packet;
         packet.len= strlen("hello server");
      //   memset(packet.data,0,sizeof(packet.data));
         strncpy(packet.data,"hello server", packet.len+1);
         packet.data[packet.len]=0;
         ESP_LOGI(TAG,"send to server data=%.*s",packet.len,packet.data);
        ssize_t sent = send(g_socket_fd, packet.data, packet.len, 0);
        if (sent < 0) {
            //printf("发送失败\n");
            ESP_LOGE(TAG,"SEND fail,retry?");
            // 可在此处触发重连逻辑
        } else {
            ESP_LOGE(TAG,"SEND SUCCESS %.*s",packet.len, packet.data);
            // printf("发送成功:%.*s\n", packet.len, packet.data);
        }
     }
}

// WiFi 事件处理函数
static void event_handler(void* arg, esp_event_base_t event_base,
                          int32_t event_id, void* event_data)
{
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
        // WiFi 启动后开始连接
        esp_wifi_connect();
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
        // 连接断开后重试
        esp_wifi_connect();
        xEventGroupClearBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
        ESP_LOGW(TAG, "WiFi disconnect,try again...");
    } 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, "get IP address: " IPSTR, IP2STR(&event->ip_info.ip));
        xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
    }
}

void wifi_init_sta(void)
{
    // 1. 初始化事件组(用于同步 WiFi 连接状态)
    s_wifi_event_group = xEventGroupCreate();

    // 2. 初始化 esp-netif(替代旧的 tcpip_adapter_init())
    ESP_ERROR_CHECK(esp_netif_init());

    // 3. 创建默认事件循环(处理 WiFi 和 IP 事件)
    ESP_ERROR_CHECK(esp_event_loop_create_default());

    // 4. 创建默认的 WiFi Station 网络接口
    esp_netif_create_default_wifi_sta();

    // 5. 初始化 WiFi 驱动
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    // 6. 注册事件处理函数(监听 WiFi 和 IP 事件)
    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));

    // 7. 配置 WiFi 连接参数(SSID 和密码)
    wifi_config_t wifi_config = {
        .sta = {
            .ssid = WIFI_SSID,
            .password = WIFI_PASSWORD,
        },
    };
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));         // 设置为 Station 模式
    ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));  // 应用配置
    ESP_ERROR_CHECK(esp_wifi_start());                         // 启动 WiFi

    ESP_LOGI(TAG, "WiFi init finish,connect %s...", WIFI_SSID);
}


void app_main(void) {
    /////////////////////////////////////////////////////////////
    // 1. 初始化 NVS(存储 WiFi 配置等信息)
    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());  // 若 NVS 满或版本不兼容,先擦除
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    // 2. 初始化 WiFi 并连接
    wifi_init_sta();

    // 3. 等待 WiFi 连接成功(阻塞直到获取 IP)
    xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT,
                        pdFALSE, pdTRUE, portMAX_DELAY);
    ///////////////////////////////////////////////////////////////
    // 初始化队列
    tx_queue = xQueueCreate(QUEUE_LEN, sizeof(DataPacket));
    rx_queue = xQueueCreate(QUEUE_LEN, sizeof(DataPacket));
    if (!tx_queue || !rx_queue) {
        printf("队列创建失败\n");
        return;
    }

    // 启动处理任务(一直运行)
    xTaskCreate(process_task, "process_task", 4096, NULL, 5, NULL);

    // 初始化Wi-Fi并连接(连接成功后调用tcp_connect)
    tcp_connect();  // Wi-Fi连接成功后调用
  //   vTaskDelay(pdMS_TO_TICKS(2000));  // 未连接则休眠
}

//接受网络消息失败就重连

3:服务器代码 go

package main

import (
	"fmt"
	"net" 
	"time"
)

func Process(conn net.Conn) {
	// 循环接收客户端发送的数据
	clientIp := conn.RemoteAddr().String() // 客户端IP:port
	defer conn.Close()
	//先发送一条消息
	//fmt.Printf("hello client",)
	//_, err := conn.Write([]byte("hello client"))
	//if err != nil {
	//	return
	//}
	// 关闭conn
	for {
		// 创建一个新的切片
		buf := make([]byte, 1024)
		// fmt.Printf("服务器在等待客户端%s发送信息\n", conn.RemoteAddr().String())
		n, err := conn.Read(buf) // 从conn中读取
		// 等待客户端通过conn发送信息,
		// 如果客户端没有发送(write),就会阻塞在这里
		if err != nil {
			// 一般为这个err
			fmt.Printf("[%v]客户端%s已退出[%v]..\n", time.Now().Unix(), clientIp, err.Error())
			return
		}
		// 显示客户端发送的内容到服务器的终端
		fmt.Printf("%s: %s", clientIp, string(buf[:n])) // 读到了n个数据
		curtime := time.Now().Unix()
		time.Sleep(time.Second)
		conn.Write([]byte(fmt.Sprintf("server time=%v len=%v", curtime, n)))
	}
}

func main() {
	fmt.Println("服务器开始监听...")
	// tcp表示使用的网络协议
	// 127.0.0.1:8888表示监听的IP:port
	listen, err := net.Listen("tcp", "0.0.0.0:8888")
	if err != nil {
		fmt.Println("listen err =", err)
		return
	}
	defer listen.Close() // 延时关闭listen
	fmt.Println("listening success:", listen.Addr())

	// 循环等待客户端来连接
	fmt.Println("等待客户端来连接..")
	for {
		conn, err := listen.Accept()
		if err != nil {
			fmt.Println("Accept() err =", err)
		} else {
			fmt.Printf("[%v]客户端%s已连接..\n", time.Now().Unix(), conn.RemoteAddr().String())
		}
		// 准备一个协程,为客户端服务
		go Process(conn)
	}

}

4: 测试结果 如果对你又帮助,麻烦点个赞,加个关注
在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到