这章主要讲述下 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: 测试结果 如果对你又帮助,麻烦点个赞,加个关注