1, Advantages and disadvantages of TCP and UDP
1. TCP is connection oriented (for example, dial up to establish a connection before making a call); UDP is connectionless, that is, there is no need to establish a connection before sending data.
2. TCP provides reliable services. In other words, the data transmitted through TCP connection is error free, not lost, not repeated, and arrives in sequence; UDP does its best to deliver, that is, reliable delivery is not guaranteed.
TCP realizes reliable transmission through checksum, retransmission control, serial number identification, sliding window and confirmation response. For example, the retransmission control in case of packet loss can also control the sequence of packets that are out of order.
3. UDP has better real-time performance and higher work efficiency than TCP. It is suitable for communication or broadcast communication with high-speed transmission and real-time performance.
4. Each TCP connection can only be point-to-point; UDP supports one-to-one, one to many, many to one and many to many interactive communication.
5. TCP requires more system resources and UDP requires less system resources.
2, Overview
ESP-IDF uses open source lwIP lightweight TCP / IP stack . ESP-IDF version lwIP( esp-lwip )Compared with upstream projects, there are some modifications and additions.
ESP-IDF supports the following functions: (lwIP) TCP / IP stack function:
-
Netconn API Enabled, but not formally supported by ESP-IDF application
BSD socket API
BSD socket API is a general cross platform TCP / IP socket API, which originated from the Berkeley standard distribution of UNIX, but has been standardized in part of POSIX specification. BSD sockets are sometimes referred to as POSIX sockets or Berkeley sockets.
As implemented in ESP-IDF, lwIP supports all common uses of the BSD socket API.
ESP-IDF Programming Guide - ESP-NETIF
ESP-IDF Programming Guide - lwIP
3, API description
The following BSD Socket interfaces are located in LwIP / LwIP / SRC / include / LwIP / sockets h.
- socket()
- bind()
- accept()
- shutdown()
- getpeername()
- getsockopt() & setsockopt() (see Socket options )
- Close Virtual file system components)
- read(), readv(), write(), writev() (via Virtual file system components)
- recv(),recvmsg(),recvfrom()
- send(),sendmsg(),sendto()
- select() (via) Virtual file system components)
- Poll() (Note: on ESP-IDF, poll() is implemented by calling select internally. Therefore, if select() is available, it is recommended to use it directly.)
- See ntfcl () fcntl)
Non standard functions:
- ioctl() (see ioctls)
4, TCP server
4.1 main process
4.1.1 step 1: create a new socket
int addr_family = 0; int ip_protocol = 0; addr_family = AF_INET; ip_protocol = IPPROTO_IP; int listen_sock = socket(addr_family, SOCK_STREAM, ip_protocol); if(listen_sock < 0) { ESP_LOGE(TAG, "Unable to create socket: errno %d", errno); }
4.1.2 step 2: configure server information
#define TCP_ Port 3333 / / TCP server port number struct sockaddr_in dest_addr; dest_addr.sin_family = AF_INET; dest_addr.sin_addr.s_addr = htonl(INADDR_ANY); dest_addr.sin_port = htons(TCP_PORT);
4.1.3 step 3: bind address
int err = bind(listen_sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr)); if(err != 0) { ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno); ESP_LOGE(TAG, "IPPROTO: %d", addr_family); close(listen_sock); } ESP_LOGI(TAG, "Socket bound, port %d", PORT);
4.1.4 step 4: start listening
err = listen(listen_sock, 1); // Most of them are online 5, why is it here if(err != 0) { ESP_LOGE(TAG, "Error occurred during listen: errno %d", errno); close(listen_sock); }
4.1.5 step 5: wait for the client to connect
while(1) { struct sockaddr_in6 source_addr; // Large enough for both IPv4 or IPv6 uint addr_len = sizeof(source_addr); int connect_sock = accept(listen_sock, (struct sockaddr *)&source_addr, &addr_len); if(connect_sock < 0) { ESP_LOGE(TAG, "Unable to accept connection: errno %d", errno); close(listen_sock); } }
4.1.6 step 6: receive data
int len; char rx_buffer[128]; while(1) { memset(rx_buffer, 0, sizeof(rx_buffer)); // wipe cache len = recv(connect_sock, rx_buffer, sizeof(rx_buffer), 0); // Read received data if(len < 0) { ESP_LOGE(TAG, "Error occurred during receiving: errno %d", errno); } else if (len == 0) { ESP_LOGW(TAG, "Connection closed"); } else { ESP_LOGI(TAG, "Received %d bytes: %s", len, rx_buffer); } }
4.1.7 step 7: send data
send(connect_socket, rx_buffer, sizeof(rx_buffer, 0);
4.2 configure SSID and password to connect WIFI and create TCP server
Similar to TCP Server
Use ESP IDF \ examples \ protocols \ sockets \ TCP_ Routines in server
/* BSD Socket API Example This example code is in the Public Domain (or CC0 licensed, at your option.) Unless required by applicable law or agreed to in writing, this software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */ #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> #define PORT CONFIG_EXAMPLE_PORT static const char *TAG = "example"; static void do_retransmit(const int sock) { int len; char rx_buffer[128]; do { len = recv(sock, rx_buffer, sizeof(rx_buffer) - 1, 0); if (len < 0) { ESP_LOGE(TAG, "Error occurred during receiving: errno %d", errno); } else if (len == 0) { ESP_LOGW(TAG, "Connection closed"); } else { rx_buffer[len] = 0; // Null-terminate whatever is received and treat it like a string ESP_LOGI(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(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; struct sockaddr_in6 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; } else if (addr_family == AF_INET6) { bzero(&dest_addr.sin6_addr.un, sizeof(dest_addr.sin6_addr.un)); dest_addr.sin6_family = AF_INET6; dest_addr.sin6_port = htons(PORT); ip_protocol = IPPROTO_IPV6; } int listen_sock = socket(addr_family, SOCK_STREAM, ip_protocol); if (listen_sock < 0) { ESP_LOGE(TAG, "Unable to create socket: errno %d", errno); vTaskDelete(NULL); return; } #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) int opt = 1; setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); setsockopt(listen_sock, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt)); #endif ESP_LOGI(TAG, "Socket created"); int err = bind(listen_sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr)); if (err != 0) { ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno); ESP_LOGE(TAG, "IPPROTO: %d", addr_family); goto CLEAN_UP; } ESP_LOGI(TAG, "Socket bound, port %d", PORT); err = listen(listen_sock, 1); if (err != 0) { ESP_LOGE(TAG, "Error occurred during listen: errno %d", errno); goto CLEAN_UP; } while (1) { ESP_LOGI(TAG, "Socket listening"); struct sockaddr_in6 source_addr; // Large enough for both IPv4 or IPv6 uint addr_len = sizeof(source_addr); int sock = accept(listen_sock, (struct sockaddr *)&source_addr, &addr_len); if (sock < 0) { ESP_LOGE(TAG, "Unable to accept connection: errno %d", errno); break; } // Convert ip address to string if (source_addr.sin6_family == PF_INET) { inet_ntoa_r(((struct sockaddr_in *)&source_addr)->sin_addr.s_addr, addr_str, sizeof(addr_str) - 1); } else if (source_addr.sin6_family == PF_INET6) { inet6_ntoa_r(source_addr.sin6_addr, addr_str, sizeof(addr_str) - 1); } ESP_LOGI(TAG, "Socket accepted ip address: %s", addr_str); do_retransmit(sock); shutdown(sock, 0); close(sock); } CLEAN_UP: close(listen_sock); vTaskDelete(NULL); } void app_main(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 }
idf.py menuconfig configuring server ports
Configure SSID and password
Then IDF Py flash compilation and download
View print:
Check the obtained IP address after connecting to WIFI (e.g. 192.168.61.107)
Open the TCP client, enter the server IP and port, select the connection and send data
4.3 create TCP server as AP
According to ESP IDF \ examples \ protocols \ sockets \ TCP_ Routine modification in server
/* BSD Socket API Example This example code is in the Public Domain (or CC0 licensed, at your option.) Unless required by applicable law or agreed to in writing, this software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */ #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> #define PORT CONFIG_EXAMPLE_PORT #define EXAMPLE_ESP_WIFI_SSID "ESP32_TEST" #define EXAMPLE_ESP_WIFI_PASS "12345678" #define EXAMPLE_ESP_WIFI_CHANNEL 1 #define EXAMPLE_MAX_STA_CONN 4 static const char *TAG = "example"; 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(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(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 }, }; 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(ESP_IF_WIFI_AP, &wifi_config)); ESP_ERROR_CHECK(esp_wifi_start()); ESP_LOGI(TAG, "wifi_init_softap finished. SSID:%s password:%s channel:%d", EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS, EXAMPLE_ESP_WIFI_CHANNEL); } static void do_retransmit(const int sock) { int len; char rx_buffer[128]; do { len = recv(sock, rx_buffer, sizeof(rx_buffer) - 1, 0); if (len < 0) { ESP_LOGE(TAG, "Error occurred during receiving: errno %d", errno); } else if (len == 0) { ESP_LOGW(TAG, "Connection closed"); } else { rx_buffer[len] = 0; // Null-terminate whatever is received and treat it like a string ESP_LOGI(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(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; struct sockaddr_in6 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; } else if (addr_family == AF_INET6) { bzero(&dest_addr.sin6_addr.un, sizeof(dest_addr.sin6_addr.un)); dest_addr.sin6_family = AF_INET6; dest_addr.sin6_port = htons(PORT); ip_protocol = IPPROTO_IPV6; } int listen_sock = socket(addr_family, SOCK_STREAM, ip_protocol); if (listen_sock < 0) { ESP_LOGE(TAG, "Unable to create socket: errno %d", errno); vTaskDelete(NULL); return; } #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) int opt = 1; setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); setsockopt(listen_sock, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt)); #endif ESP_LOGI(TAG, "Socket created"); int err = bind(listen_sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr)); if (err != 0) { ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno); ESP_LOGE(TAG, "IPPROTO: %d", addr_family); goto CLEAN_UP; } ESP_LOGI(TAG, "Socket bound, port %d", PORT); err = listen(listen_sock, 1); if (err != 0) { ESP_LOGE(TAG, "Error occurred during listen: errno %d", errno); goto CLEAN_UP; } while (1) { ESP_LOGI(TAG, "Socket listening"); struct sockaddr_in6 source_addr; // Large enough for both IPv4 or IPv6 uint addr_len = sizeof(source_addr); int sock = accept(listen_sock, (struct sockaddr *)&source_addr, &addr_len); if (sock < 0) { ESP_LOGE(TAG, "Unable to accept connection: errno %d", errno); break; } // Convert ip address to string if (source_addr.sin6_family == PF_INET) { inet_ntoa_r(((struct sockaddr_in *)&source_addr)->sin_addr.s_addr, addr_str, sizeof(addr_str) - 1); } else if (source_addr.sin6_family == PF_INET6) { inet6_ntoa_r(source_addr.sin6_addr, addr_str, sizeof(addr_str) - 1); } ESP_LOGI(TAG, "Socket accepted ip address: %s", addr_str); do_retransmit(sock); shutdown(sock, 0); close(sock); } CLEAN_UP: close(listen_sock); vTaskDelete(NULL); } void app_main(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()); wifi_init_softap(); #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 }
idf.py menuconfig configuring server ports
Then IDF Py flash compilation and download
View print:
The default IP address is 192.168.4.1, which was written by Lexin when leaving the factory
Open the TCP client, enter the server IP and port, select the connection and send data
• by Leung Written on April 28, 2021
• reference: Lexin esp32 learning journey ⑨ realize the role of local TCP client and server on esp32, which can disconnect and reconnect the original way to return data
ESP32 Development Notes (III) source code example 20_WIFI_STA_TCP_Server implements TCP server in sta mode
ESP32 Development Notes (III) source code example 16_WIFI_AP_TCP_Server implements TCP server in AP mode