ESP32-C3_TCP
这章主要讲述下 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));
#endifint 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()); // 启动 WiFiESP_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 mainimport ("fmt""net" "time"
)func Process(conn net.Conn) {// 循环接收客户端发送的数据clientIp := conn.RemoteAddr().String() // 客户端IP:portdefer conn.Close()//先发送一条消息//fmt.Printf("hello client",)//_, err := conn.Write([]byte("hello client"))//if err != nil {// return//}// 关闭connfor {// 创建一个新的切片buf := make([]byte, 1024)// fmt.Printf("服务器在等待客户端%s发送信息\n", conn.RemoteAddr().String())n, err := conn.Read(buf) // 从conn中读取// 等待客户端通过conn发送信息,// 如果客户端没有发送(write),就会阻塞在这里if err != nil {// 一般为这个errfmt.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:portlisten, err := net.Listen("tcp", "0.0.0.0:8888")if err != nil {fmt.Println("listen err =", err)return}defer listen.Close() // 延时关闭listenfmt.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: 测试结果 如果对你又帮助,麻烦点个赞,加个关注