基于ESP32的TCP/IP传输实现
TCP/IP协议原理
TCP/IP协议是Internet互联网最基本的协议,TCP/IP协议的应用层的主要协议有HTTP、Telnet、FTP、SMTP等,是用来读取来自传输层的数据或者将数据传输写入传输层;传输层的主要协议有UDP、TCP,实现端对端的数据传输;网络层的主要协议有ICMP、IP、IGMP,主要负责网络中数据包的传送等;链路层有时也称作数据链路层或网络接口层,主要协议有ARP、RARP,通常包括操作系统中的设备驱动程序和计算机中对应的网络接口卡,它们一起处理与传输媒介(如电缆或其他物理设备)的物理接口细节。
TCP协议是一个面向连接的、可靠的传输协议,它提供一种可靠的字节流,能保证数据完整、无损并且按顺序到达。TCP尽量连续不断地测试网络的负载并且控制发送数据的速度以避免网络过载。另外,TCP试图将数据按照规定的顺序发送。
ESP32作为热点+TCP服务端
参考乐鑫开源程序,设置wifi模式为AP,运行TCP服务端程序。
ESP32作为站点+TCP客户端
参考乐鑫开源程序,设置wifi模式为STA,运行TCP客户端程序。
AP+TCP服务端程序
主函数
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_mac.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "lwip/err.h"
#include "lwip/sys.h"
#include "tcp_server.h"
/* The examples use WiFi configuration that you can set via project configuration menu.
If you'd rather not, just change the below entries to strings with
the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid"
*/
//#define EXAMPLE_ESP_WIFI_SSID CONFIG_ESP_WIFI_SSID
//#define EXAMPLE_ESP_WIFI_PASS CONFIG_ESP_WIFI_PASSWORD
#define EXAMPLE_ESP_WIFI_SSID "myesp32AP"
#define EXAMPLE_ESP_WIFI_PASS "esp45678"
#define EXAMPLE_ESP_WIFI_CHANNEL CONFIG_ESP_WIFI_CHANNEL
#define EXAMPLE_MAX_STA_CONN CONFIG_ESP_MAX_STA_CONN
#define WIFI_TAG "wifi softAP"
//static const char *WIFI_TAG = "wifi softAP";
static void wifi_event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
if (event_id == WIFI_EVENT_AP_STACONNECTED) {
wifi_event_ap_staconnected_t* event = (wifi_event_ap_staconnected_t*) event_data;
ESP_LOGI(WIFI_TAG, "station "MACSTR" join, AID=%d",
MAC2STR(event->mac), event->aid);
} else if (event_id == WIFI_EVENT_AP_STADISCONNECTED) {
wifi_event_ap_stadisconnected_t* event = (wifi_event_ap_stadisconnected_t*) event_data;
ESP_LOGI(WIFI_TAG, "station "MACSTR" leave, AID=%d",
MAC2STR(event->mac), event->aid);
}
}
void wifi_init_softap(void)
{
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_ap();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&wifi_event_handler,
NULL,
NULL));
wifi_config_t wifi_config = {
.ap = {
.ssid = EXAMPLE_ESP_WIFI_SSID,
.ssid_len = strlen(EXAMPLE_ESP_WIFI_SSID),
.channel = EXAMPLE_ESP_WIFI_CHANNEL,
.password = EXAMPLE_ESP_WIFI_PASS,
.max_connection = EXAMPLE_MAX_STA_CONN,
.authmode = WIFI_AUTH_WPA_WPA2_PSK,
.pmf_cfg = {
.required = false,
},
},
};
if (strlen(EXAMPLE_ESP_WIFI_PASS) == 0) {
wifi_config.ap.authmode = WIFI_AUTH_OPEN;
}
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
ESP_LOGI(WIFI_TAG, "wifi_init_softap finished. SSID:%s password:%s channel:%d",
EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS, EXAMPLE_ESP_WIFI_CHANNEL);
}
void app_main(void)
{
//Initialize 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(WIFI_TAG, "ESP_WIFI_MODE_AP");
wifi_init_softap();
vTaskDelay(1000 / portTICK_PERIOD_MS);
tcpServerStart();
}
tcp_server.c
/*
#include <string.h>
#include <sys/param.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_netif.h"
#include "protocol_examples_common.h"
#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include <lwip/netdb.h>
*/
#include "tcp_server.h"
#define PORT CONFIG_EXAMPLE_PORT
#define KEEPALIVE_IDLE CONFIG_EXAMPLE_KEEPALIVE_IDLE
#define KEEPALIVE_INTERVAL CONFIG_EXAMPLE_KEEPALIVE_INTERVAL
#define KEEPALIVE_COUNT CONFIG_EXAMPLE_KEEPALIVE_COUNT
#define TCP_TAG "TCP"
//static const char *TCP_TAG= "example";
static void do_retransmit(const int sock)
{
int len;
char rx_buffer[128];
char tx_buffer[128];
do {
len = recv(sock, rx_buffer, sizeof(rx_buffer) - 1, 0);
if (len < 0) {
ESP_LOGE(TCP_TAG, "Error occurred during receiving: errno %d", errno);
} else if (len == 0) {
ESP_LOGW(TCP_TAG, "Connection closed");
} else {
rx_buffer[len] = 0; // Null-terminate whatever is received and treat it like a string
ESP_LOGI(TCP_TAG, "Received %d bytes: %s", len, rx_buffer);
// send() can return less bytes than supplied length.
// Walk-around for robust implementation.
int to_write = len;
while (to_write > 0) {
int written = send(sock, rx_buffer + (len - to_write), to_write, 0);
if (written < 0) {
ESP_LOGE(TCP_TAG, "Error occurred during sending: errno %d", errno);
}
to_write -= written;
}
}
} while (len > 0);
}
static void tcp_server_task(void *pvParameters)
{
char addr_str[128];
int addr_family = (int)pvParameters;
int ip_protocol = 0;
int keepAlive = 1;
int keepIdle = KEEPALIVE_IDLE;
int keepInterval = KEEPALIVE_INTERVAL;
int keepCount = KEEPALIVE_COUNT;
struct sockaddr_storage dest_addr;
if (addr_family == AF_INET) {
struct sockaddr_in *dest_addr_ip4 = (struct sockaddr_in *)&dest_addr;
dest_addr_ip4->sin_addr.s_addr = htonl(INADDR_ANY);
dest_addr_ip4->sin_family = AF_INET;
dest_addr_ip4->sin_port = htons(PORT);
ip_protocol = IPPROTO_IP;
}
#ifdef CONFIG_EXAMPLE_IPV6
else if (addr_family == AF_INET6) {
struct sockaddr_in6 *dest_addr_ip6 = (struct sockaddr_in6 *)&dest_addr;
bzero(&dest_addr_ip6->sin6_addr.un, sizeof(dest_addr_ip6->sin6_addr.un));
dest_addr_ip6->sin6_family = AF_INET6;
dest_addr_ip6->sin6_port = htons(PORT);
ip_protocol = IPPROTO_IPV6;
}
#endif
int listen_sock = socket(addr_family, SOCK_STREAM, ip_protocol);
if (listen_sock < 0) {
ESP_LOGE(TCP_TAG, "Unable to create socket: errno %d", errno);
vTaskDelete(NULL);
return;
}
int opt = 1;
setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
#if defined(CONFIG_EXAMPLE_IPV4) && defined(CONFIG_EXAMPLE_IPV6)
// Note that by default IPV6 binds to both protocols, it is must be disabled
// if both protocols used at the same time (used in CI)
setsockopt(listen_sock, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt));
#endif
ESP_LOGI(TCP_TAG, "Socket created");
int err = bind(listen_sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
if (err != 0) {
ESP_LOGE(TCP_TAG, "Socket unable to bind: errno %d", errno);
ESP_LOGE(TCP_TAG, "IPPROTO: %d", addr_family);
goto CLEAN_UP;
}
ESP_LOGI(TCP_TAG, "Socket bound, port %d", PORT);
err = listen(listen_sock, 1);
if (err != 0) {
ESP_LOGE(TCP_TAG, "Error occurred during listen: errno %d", errno);
goto CLEAN_UP;
}
while (1) {
ESP_LOGI(TCP_TAG, "Socket listening");
struct sockaddr_storage source_addr; // Large enough for both IPv4 or IPv6
socklen_t addr_len = sizeof(source_addr);
int sock = accept(listen_sock, (struct sockaddr *)&source_addr, &addr_len);
if (sock < 0) {
ESP_LOGE(TCP_TAG, "Unable to accept connection: errno %d", errno);
break;
}
// Set tcp keepalive option
setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &keepAlive, sizeof(int));
setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, &keepIdle, sizeof(int));
setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, &keepInterval, sizeof(int));
setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, &keepCount, sizeof(int));
// Convert ip address to string
if (source_addr.ss_family == PF_INET) {
inet_ntoa_r(((struct sockaddr_in *)&source_addr)->sin_addr, addr_str, sizeof(addr_str) - 1);
}
#ifdef CONFIG_EXAMPLE_IPV6
else if (source_addr.ss_family == PF_INET6) {
inet6_ntoa_r(((struct sockaddr_in6 *)&source_addr)->sin6_addr, addr_str, sizeof(addr_str) - 1);
}
#endif
ESP_LOGI(TCP_TAG, "Socket accepted ip address: %s", addr_str);
do_retransmit(sock);
shutdown(sock, 0);
close(sock);
}
CLEAN_UP:
close(listen_sock);
vTaskDelete(NULL);
}
void tcpServerStart(void)
{
//ESP_ERROR_CHECK(nvs_flash_init());
//ESP_ERROR_CHECK(esp_netif_init());
//ESP_ERROR_CHECK(esp_event_loop_create_default());
/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
* Read "Establishing Wi-Fi or Ethernet Connection" section in
* examples/protocols/README.md for more information about this function.
*/
//ESP_ERROR_CHECK(example_connect());
#ifdef CONFIG_EXAMPLE_IPV4
xTaskCreate(tcp_server_task, "tcp_server", 4096, (void*)AF_INET, 5, NULL);
#endif
#ifdef CONFIG_EXAMPLE_IPV6
xTaskCreate(tcp_server_task, "tcp_server", 4096, (void*)AF_INET6, 5, NULL);
#endif
}
ATP+TCP客户端程序
主函数
#include <string.h>
#include <sys/param.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_netif.h"
#include "addr_from_stdin.h"
#include "lwip/err.h"
#include "lwip/sockets.h"
#include "my_wifi.h"
#include "tcp_client.h"
void app_main(void)
{
//wifi conect
wifiStaStart();
//tcp client
tcpClientStart();
}
my_wifi.c
#include "my_wifi.h"
/* The examples use WiFi configuration that you can set via project configuration menu
If you'd rather not, just change the below entries to strings with
the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid"
*/
#define EXAMPLE_ESP_WIFI_SSID "myesp32AP"
#define EXAMPLE_ESP_WIFI_PASS "esp45678"
#define EXAMPLE_ESP_MAXIMUM_RETRY CONFIG_ESP_MAXIMUM_RETRY
#if CONFIG_ESP_WIFI_AUTH_OPEN
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_OPEN
#elif CONFIG_ESP_WIFI_AUTH_WEP
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WEP
#elif CONFIG_ESP_WIFI_AUTH_WPA_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA_PSK
#elif CONFIG_ESP_WIFI_AUTH_WPA2_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA2_PSK
#elif CONFIG_ESP_WIFI_AUTH_WPA_WPA2_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA_WPA2_PSK
#elif CONFIG_ESP_WIFI_AUTH_WPA3_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA3_PSK
#elif CONFIG_ESP_WIFI_AUTH_WPA2_WPA3_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA2_WPA3_PSK
#elif CONFIG_ESP_WIFI_AUTH_WAPI_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WAPI_PSK
#endif
/* FreeRTOS event group to signal when we are connected*/
static EventGroupHandle_t s_wifi_event_group;
/* The event group allows multiple bits for each event, but we only care about two events:
* - we are connected to the AP with an IP
* - we failed to connect after the maximum amount of retries */
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
static const char *TAG = "wifi station";
static int s_retry_num = 0;
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) {
esp_wifi_connect();
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
if (s_retry_num < EXAMPLE_ESP_MAXIMUM_RETRY) {
esp_wifi_connect();
s_retry_num++;
ESP_LOGI(TAG, "retry to connect to the AP");
} else {
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
}
ESP_LOGI(TAG,"connect to the AP fail");
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_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));
s_retry_num = 0;
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
}
}
void wifi_init_sta(void)
{
s_wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
esp_event_handler_instance_t instance_any_id;
esp_event_handler_instance_t instance_got_ip;
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&event_handler,
NULL,
&instance_any_id));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
IP_EVENT_STA_GOT_IP,
&event_handler,
NULL,
&instance_got_ip));
wifi_config_t wifi_config = {
.sta = {
.ssid = EXAMPLE_ESP_WIFI_SSID,
.password = EXAMPLE_ESP_WIFI_PASS,
/* Authmode threshold resets to WPA2 as default if password matches WPA2 standards (pasword len => 8).
* If you want to connect the device to deprecated WEP/WPA networks, Please set the threshold value
* to WIFI_AUTH_WEP/WIFI_AUTH_WPA_PSK and set the password with length and format matching to
* WIFI_AUTH_WEP/WIFI_AUTH_WPA_PSK standards.
*/
.threshold.authmode = ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD,
.sae_pwe_h2e = WPA3_SAE_PWE_BOTH,
},
};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );
ESP_ERROR_CHECK(esp_wifi_start() );
ESP_LOGI(TAG, "wifi_init_sta finished.");
/* Waiting until either the connection is established (WIFI_CONNECTED_BIT) or connection failed for the maximum
* number of re-tries (WIFI_FAIL_BIT). The bits are set by event_handler() (see above) */
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
pdFALSE,
pdFALSE,
portMAX_DELAY);
/* xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event actually
* happened. */
if (bits & WIFI_CONNECTED_BIT) {
ESP_LOGI(TAG, "connected to ap SSID:%s password:%s",
EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);
} else if (bits & WIFI_FAIL_BIT) {
ESP_LOGI(TAG, "Failed to connect to SSID:%s, password:%s",
EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);
} else {
ESP_LOGE(TAG, "UNEXPECTED EVENT");
}
}
void wifiStaStart(void)
{
//Initialize 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_init_sta();
//tcpStart();
}
tcp_client.c
#include "tcp_client.h"
#if defined(CONFIG_EXAMPLE_IPV4)
#define HOST_IP_ADDR CONFIG_EXAMPLE_IPV4_ADDR
#elif defined(CONFIG_EXAMPLE_IPV6)
#define HOST_IP_ADDR CONFIG_EXAMPLE_IPV6_ADDR
#else
#define HOST_IP_ADDR ""
#endif
#define PORT CONFIG_EXAMPLE_PORT
static const char *TAGC = "example";
static const char *payload = "Message from Client1:tem,hum,c2h4 ";
static void tcp_client_task(void *pvParameters)
{
char rx_buffer[128];
char host_ip[] = HOST_IP_ADDR;
int addr_family = 0;
int ip_protocol = 0;
while (1) {
#if defined(CONFIG_EXAMPLE_IPV4)
struct sockaddr_in dest_addr;
dest_addr.sin_addr.s_addr = inet_addr(host_ip);
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(PORT);
addr_family = AF_INET;
ip_protocol = IPPROTO_IP;
#elif defined(CONFIG_EXAMPLE_IPV6)
struct sockaddr_in6 dest_addr = { 0 };
inet6_aton(host_ip, &dest_addr.sin6_addr);
dest_addr.sin6_family = AF_INET6;
dest_addr.sin6_port = htons(PORT);
dest_addr.sin6_scope_id = esp_netif_get_netif_impl_index(EXAMPLE_INTERFACE);
addr_family = AF_INET6;
ip_protocol = IPPROTO_IPV6;
#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(TAGC, "Unable to create socket: errno %d", errno);
break;
}
ESP_LOGI(TAGC, "Socket created, connecting to %s:%d", host_ip, PORT);
int err = connect(sock, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr_in6));
if (err != 0) {
ESP_LOGE(TAGC, "Socket unable to connect: errno %d", errno);
break;
}
ESP_LOGI(TAGC, "Successfully connected");
while (1) {
int err = send(sock, payload, strlen(payload), 0);
if (err < 0) {
ESP_LOGE(TAGC, "Error occurred during sending: errno %d", errno);
break;
}
int len = recv(sock, rx_buffer, sizeof(rx_buffer) - 1, 0);
// Error occurred during receiving
if (len < 0) {
ESP_LOGE(TAGC, "recv failed: errno %d", errno);
break;
}
// Data received
else {
rx_buffer[len] = 0; // Null-terminate whatever we received and treat like a string
ESP_LOGI(TAGC, "Received %d bytes from %s:", len, host_ip);
ESP_LOGI(TAGC, "%s", rx_buffer);
}
vTaskDelay(2000 / portTICK_PERIOD_MS);
}
if (sock != -1) {
ESP_LOGE(TAGC, "Shutting down socket and restarting...");
shutdown(sock, 0);
close(sock);
}
}
vTaskDelete(NULL);
}
void tcpClientStart(void)
{
// ESP_ERROR_CHECK(nvs_flash_init());
// ESP_ERROR_CHECK(esp_netif_init());
// ESP_ERROR_CHECK(esp_event_loop_create_default());
/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
* Read "Establishing Wi-Fi or Ethernet Connection" section in
* examples/protocols/README.md for more information about this function.
*/
// ESP_ERROR_CHECK(example_connect());
xTaskCreate(tcp_client_task, "tcp_client", 4096, NULL, 5, NULL);
}
注意
需要在Kconfig.projbuild文件中预先进行一些配置如IP,端口号等。CMakeLists.txt需要包含主函数调用的所有c文件。
结果
成功通信运行的结果如下图所示
服务端运行
客户端运行