Попытка уменьшить задержку сокета TCP на ESP32

В настоящее время я работаю над прототипом WIFI-соединения между моим ESP-WROVER-KIT и моим ПК. У меня есть вопросы о моих методах и результатах.

Целью этого прототипа было получить как можно меньшую задержку в сети между ESP32 и ПК (около 6-10 мс было бы отлично) с постоянным размером пакета в 512 байт. В настоящее время ПК подключен к маршрутизатору WIFI через Ethernet-кабель. Поскольку это мой первый опыт работы в сети, я провел небольшое исследование и пришел к выводу, что подключение через сокет TCP будет хорошо документированным методом с возможностью получения данных. требуется задержка. Я использовал примеры сокетов esp-idf для ESP32 и онлайн-пример кода для приложения Windows Forms для моего ПК в качестве базы кода с некоторыми изменениями для измерения времени обратного пути и отправки требуемого размера пакета. Измерение времени обратного пути было выполнено путем добавления секундомера (Windows Forms) или таймера вверх (ESP32) и измерения времени между отправкой и получением пакетов в блокирующем вызове.

В первом тесте ESP32 использовался в качестве сервера, действующего как эхо, а формы Windows были клиентом, отправляющим 512-байтовый пакет и измеряющим время, необходимое для его возврата. Записанное время обратного пути этой транзакции составляло примерно 40-50 мсек. Это значение было подтверждено с помощью Wireshark.

ESP32 Server Echo


   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[513];

    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;
                //vTaskDelay(10);
            }
        }
    } while (len > 0);
}

static void tcp_server_task(void *pvParameters)
{
    char addr_str[513];
    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
}

Отправка / получение / время клиента Windows Forms

using System.Text;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Diagnostics;
using System.Threading;

public class GetSocket
{
    private static Socket ConnectSocket()
    {
        Socket socket = null;
        IPHostEntry hostEntry = null;

        // Get host related information.


        // Loop through the AddressList to obtain the supported AddressFamily. This is to avoid
        // an exception that occurs when the host IP Address is not compatible with the address family
        // (typical in the IPv6 case).

        IPEndPoint ipe = new IPEndPoint(771860672, 3333);
        ///IPEndPoint ipe = new IPEndPoint(2046929088, 3333);
        /*Socket tempSocket =
            new Socket(ipe.AddressFamily, SocketType.Dgram, ProtocolType.Udp);*/
        Socket tempSocket =
               new Socket(ipe.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

        tempSocket.Connect(ipe);

            if(tempSocket.Connected)
            {
            socket = tempSocket;

            }

        return socket;
    }

    // This method requests the home page content for the specified server.
    private static string SocketSendReceive(string server, int port)
    {
        string request = "abcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdfeghijhk";
        Byte[] bytesSent = Encoding.ASCII.GetBytes(request);
        Byte[] bytesReceived = new Byte[512];
        string page = "";
        

        // Create a socket connection with the specified server and port.
        using (Socket socket = ConnectSocket()) {

            if (socket == null)
                return ("Connection failed");

            socket.NoDelay = true; /*Need to remove for UDP connection*/
            int dataLengthReceived = 0;
            UInt32[] bytearray = new UInt32[512];
            int index = 0;
            double[] timearraymili = new double[512];
            
            int timindex = 0;
            byte sendCount = 0;
            socket.ReceiveTimeout = 0;
            Stopwatch stopWatch = new Stopwatch();
            TimeSpan ts = stopWatch.Elapsed;
            do
            {
                
                ts = TimeSpan.Zero;

                for(var count = 0; count < bytesSent.Length; count++)
                {
                    bytesSent[count] = (byte)sendCount;
                }

                stopWatch.Reset();
                stopWatch.Start();
                socket.Send(bytesSent, bytesSent.Length, 0);
                dataLengthReceived = socket.Receive(bytesReceived, bytesReceived.Length, 0);
                stopWatch.Stop();
                ts = stopWatch.Elapsed;

                if(dataLengthReceived == 512)
                {
                    timearraymili[timindex] = ts.TotalMilliseconds;
                    timindex++;

                    Debug.WriteLine("Send Count {0} -  Latency = {1} ms", sendCount, ts.TotalMilliseconds.ToString("0.000"));

                    for(var count = 0; count < bytesReceived.Length; count++)
                    {
                        if(bytesReceived[count] != sendCount)
                        {
                            Debug.WriteLine("Invalid Data Received {0} != {1}", bytesReceived[count], sendCount);
                        }
                    }
                }
                else
                {
                    Debug.WriteLine("bytes received != 512: {0} - elapsed{2}",  dataLengthReceived.ToString(), stopWatch.ElapsedMilliseconds);
                }
                sendCount++;
                //Thread.Sleep(100);

            }
            while (dataLengthReceived > 0);
        }

        return page;
    }

    public static void Main(string[] args)
    {
        string host;
        int port = 3333;

        if (args.Length == 0)
            // If no server name is passed as argument to this program,
            // use the current host name as the default.
            host = Dns.GetHostName();
        else
            host = args[0];

        string result = SocketSendReceive(host, port);
        Console.WriteLine(result);
    }
}

Во втором тесте ESP32 использовался в качестве клиента, действующего как эхо, а формы Windows были сервером, отправляющим 512-байтовый пакет и измеряющим время, которое потребовалось до его возврата. Записанное время обратного пути этой транзакции составляло примерно 40-50 мсек. Это значение было подтверждено с помощью Wireshark.

ESP32 Client Echo


   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 "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 "protocol_examples_common.h"
#include "addr_from_stdin.h"
#include "lwip/err.h"
#include "lwip/sockets.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 *TAG = "example";


static void tcp_client_task(void *pvParameters)
{
    int len;
    char rx_buffer[513];
    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_in6 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);
            break;
        }
        ESP_LOGI(TAG, "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(TAG, "Socket unable to connect: errno %d", errno);
            break;
        }
        ESP_LOGI(TAG, "Successfully connected");

        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;  
                ESP_LOGI(TAG, "Received %d bytes: %s", len, rx_buffer);


                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);

        if (sock != -1) {
            ESP_LOGE(TAG, "Shutting down socket and restarting...");
            shutdown(sock, 0);
            close(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());

    xTaskCreate(tcp_client_task, "tcp_client", 4096, NULL, 5, NULL);
}

Отправка / получение / время сервера Windows Forms

using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Diagnostics;
using System.Threading;

class MyTcpListener
{
    public static void Main()
    {
        TcpListener server = null;
        try
        {
            // Set the TcpListener on port 13000.
            Int32 port = 3333;
            IPAddress localAddr = IPAddress.Parse("192.168.1.199");

            // TcpListener server = new TcpListener(port);
            server = new TcpListener(localAddr, port);

            // Start listening for client requests.
            server.Start();

            // Buffer for reading data
            string request = "abcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdfeghijhk";
            Byte[] bytesSent = Encoding.ASCII.GetBytes(request);
            Byte[] bytesReceived = new Byte[512];
            
            int dataLengthReceived = 0;
            String dataReceived;
            UInt32[] bytearray = new UInt32[512];
            double[] timearraymili = new double[512];

            int timindex = 0;
            byte sendCount = 0;
            Stopwatch stopWatch = new Stopwatch();
            TimeSpan ts = stopWatch.Elapsed;

            // Enter the listening loop.
            while (true)
            {
                Debug.Write("Waiting for a connection... ");

                // Perform a blocking call to accept requests.
                // You could also use server.AcceptSocket() here.
                TcpClient client = server.AcceptTcpClient();
                Debug.WriteLine("Connected!");

                
                // Get a stream object for reading and writing
                NetworkStream stream = client.GetStream();
                                
                do
                {
                    ts = TimeSpan.Zero;

                    for (var count = 0; count < bytesSent.Length; count++)
                    {
                        bytesSent[count] = (byte)sendCount;
                    }

                    stopWatch.Reset();
                    stopWatch.Start();
                    stream.Write(bytesSent, 0, bytesSent.Length);
                    stream.Read(bytesReceived, 0, bytesReceived.Length);
                    stopWatch.Stop();
                    ts = stopWatch.Elapsed;
                    dataReceived = System.Text.Encoding.ASCII.GetString(bytesReceived, 0, bytesReceived.Length);
                    dataLengthReceived = dataReceived.Length;

                    if (dataLengthReceived == 512)
                    {
                        timearraymili[timindex] = ts.TotalMilliseconds;
                        timindex++;

                        Debug.WriteLine("Send Count {0} -  Latency = {1} ms", sendCount, ts.TotalMilliseconds.ToString("0.000"));

                        for (var count = 0; count < bytesReceived.Length; count++)
                        {
                            if (bytesReceived[count] != sendCount)
                            {
                                Debug.WriteLine("Invalid Data Received {0} != {1}", bytesReceived[count], sendCount);
                            }
                        }
                    }
                    else
                    {
                        Debug.WriteLine("bytes received != 512: {0} - elapsed{2}", dataLengthReceived.ToString(), stopWatch.ElapsedMilliseconds);
                    }
                    sendCount++;


                }
                while (dataLengthReceived > 0);
                // Shutdown and end connection
                client.Close();
            }
        }
        catch (SocketException e)
        {
            Console.WriteLine("SocketException: {0}", e);
        }
        finally
        {
            // Stop listening for new clients.
            server.Stop();
        }

        Console.WriteLine("\nHit enter to continue...");
        Console.Read();
    }
}

В третьем тесте ESP32 был клиентом, отправляющим 512-байтовый пакет, и временем, которое потребовалось до его возврата, а формы Windows - это сервер, действующий как эхо. Записанное время обратного пути этой транзакции составляло примерно 6-10 мс (значение времени обратного пути было отправлено в следующем пакете). Это значение было подтверждено с помощью Wireshark при измерении обратного пути от точки ESP32 (ESP32-PC-ESP32). Однако, если измерять в Wireshark с точки зрения ПК (PC-ESP32-PC), время обратного пути составляет примерно 40-50 мс.

ESP32 Клиент-сервер Отправка / получение / Время


   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 "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 "protocol_examples_common.h"
#include "addr_from_stdin.h"
#include "lwip/err.h"
#include "lwip/sockets.h"
#include "timer_types.h"
#include "timer.h"
#include <esp_app_trace.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 *TAG = "example";
char ESP_Tx_512Bytes[512] = "Message from ESP32333 ";

static void tcp_client_task(void *pvParameters)
{
    char rx_buffer[513];
    char host_ip[] = HOST_IP_ADDR;
    int addr_family = 0;
    int ip_protocol = 0;
    double stopwatchtime;

    
    timer_config_t stopwatch;
    stopwatch.divider = 80;
    stopwatch.counter_dir = TIMER_COUNT_UP;
    stopwatch.alarm_en = TIMER_ALARM_DIS;
    timer_init(TIMER_GROUP_0, 0, &stopwatch);

    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_in6 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);
            break;
        }
        ESP_LOGI(TAG, "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(TAG, "Socket unable to connect: errno %d", errno);
            break;
        }
        ESP_LOGI(TAG, "Successfully connected");

        while (1) {
            timer_set_counter_value(TIMER_GROUP_0, 0, 0);
            timer_start(TIMER_GROUP_0, 0);
            int err = send(sock, ESP_Tx_512Bytes, strlen(ESP_Tx_512Bytes), 0);
            if (err < 0) {
                ESP_LOGE(TAG, "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(TAG, "recv failed: errno %d", errno);
                break;
            }
            // Data received
            else {
                rx_buffer[len] = 0;  // Null-terminate whatever we received and treat like a string
                timer_pause(TIMER_GROUP_0, 0);
                timer_get_counter_time_sec(TIMER_GROUP_0, 0, &stopwatchtime);
                ESP_LOGI(TAG, "Received %d bytes from %s:", len, host_ip);
                ESP_LOGI(TAG, "%s", rx_buffer);
                ESP_LOGI(TAG, "Latency: %lf seconds", stopwatchtime);
                
                sprintf(ESP_Tx_512Bytes, "Latency: %lf seconds PacketSizePacketSizePacketSizePacketSizePacketSizePacketSizePacketSizePacketSizePacketSizePacketSizePacketSizePacketSizePacketSizePacketSizePacketSizePacketSizePacketSizePacketSizePacketSizePacketSizePacketSizePacketSizePacketSizePacketSizePacketSizePacketSizePacketSizePacketSizePacketSizePacketSizePacketSizePacketSizePacketSizePacketSizePacketSizePacketSizePacketSizePacketSizePacketSizePacketSizePacketSizePacketSizePacketSizePacketSizePacketSizePacketSizePacketSizePacketSizePZPZ", stopwatchtime);
                                
            }
            
        }

        if (sock != -1) {
            ESP_LOGE(TAG, "Shutting down socket and restarting...");
            shutdown(sock, 0);
            close(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());

    xTaskCreate(tcp_client_task, "tcp_client", 4096, NULL, 5, NULL);
}

Эхо Windows Forms

using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;

class MyTcpListener
{
    public static void Main()
    {
        TcpListener server = null;
        try
        {
            // Set the TcpListener on port 13000.
            Int32 port = 3333;
            IPAddress localAddr = IPAddress.Parse("192.168.1.199");

            // TcpListener server = new TcpListener(port);
            server = new TcpListener(localAddr, port);

            // Start listening for client requests.
            server.Start();

            // Buffer for reading data
            Byte[] bytes = new Byte[513];
            String data = null;

            // Enter the listening loop.
            while (true)
            {
                Console.Write("Waiting for a connection... ");

                // Perform a blocking call to accept requests.
                // You could also use server.AcceptSocket() here.
                TcpClient client = server.AcceptTcpClient();
                Console.WriteLine("Connected!");

                data = null;

                // Get a stream object for reading and writing
                NetworkStream stream = client.GetStream();

                int i;

                // Loop to receive all the data sent by the client.
                while ((i = stream.Read(bytes, 0, bytes.Length)) != 0)
                {
                    // Translate data bytes to a ASCII string.
                    data = System.Text.Encoding.ASCII.GetString(bytes, 0, i);
                    Console.WriteLine("Received: {0}", data);

                    // Process the data sent by the client.
                    data = data.ToUpper();

                    byte[] msg = System.Text.Encoding.ASCII.GetBytes(data);

                    // Send back a response.
                    stream.Write(msg, 0, msg.Length);
                    Console.WriteLine("Sent: {0}", data);
                }

                // Shutdown and end connection
                client.Close();
            }
        }
        catch (SocketException e)
        {
            Console.WriteLine("SocketException: {0}", e);
        }
        finally
        {
            // Stop listening for new clients.
            server.Stop();
        }

        Console.WriteLine("\nHit enter to continue...");
        Console.Read();
    }
}

Вопросы: В случае теста 3, указывает ли время обратного пути, измеренное с помощью wirehark, на то, что самая большая задержка связана с обработкой данных через ESP32? Или я неверно интерпретирую данные? Точен ли мой метод проверки времени обратного пути в приложении Windows Form (секундомер) и ESP32 (таймер, отсчитывающий вверх) при вызове блокировки? Поскольку транзакции с ПК на ESP32 и обратно приводят к среднему времени обратного пути 40-50 мс, есть ли какие-либо настройки или оптимизации кода, о которых я должен знать, которые помогают сократить это время? Если это лучшее время, на которое я могу рассчитывать, порекомендуете ли вы какие-либо другие протоколы для улучшения времени обратного пути? Обратите внимание: я отключил алгоритм Нэгла для сокета TCP (TCP_NODELAY) в каждом случае, однако это не повлияло на результат.

Спасибо за ваше время


person Sacingram    schedule 12.01.2021    source источник
comment
Добро пожаловать в Stack Overflow. Это слишком длинный вопрос. Можете ли вы переформатировать вопрос следующим образом: Постановка проблемы, что вы пробовали, каков ваш ожидаемый результат, образцы данных, если таковые имеются. Прочтите, как задавать хорошие вопросы. Убедитесь, что ваш вопрос охватывает следующие 3 элемента: 1. Постановка проблемы 2. Ваш код (он должен быть минимальным воспроизводимым примером 3. Сообщение об ошибке (желательно полное отслеживание, чтобы помочь другим просмотреть и оставить отзыв).   -  person Joe Ferndz    schedule 12.01.2021


Ответы (1)


Задержка и джиттер, которые вы можете ожидать от подключения к ESP32, сильно зависят от наличия бесплатного WiFi-эфира на выбранном канале. На перегруженном беспроводном канале (имеется в виду множество других устройств, транслирующих) вы обычно видите задержки более 100 мс, поскольку вашим устройствам приходится ждать свободного радиослота.

При этом ожидаемая задержка приема-передачи с полезной нагрузкой 512 Б на достаточно свободном канале обычно составляет менее 10 мс, но не всегда. Тестирование с помощью ICMP PING с интервалом в 1 с (энергосбережение WiFi отключено в ESP32):

$ ping -s 512 192.168.199.124
PING 192.168.199.124 (192.168.199.124) 512(540) bytes of data.
520 bytes from 192.168.199.124: icmp_seq=1 ttl=255 time=8.89 ms
520 bytes from 192.168.199.124: icmp_seq=2 ttl=255 time=2.02 ms
520 bytes from 192.168.199.124: icmp_seq=3 ttl=255 time=7.50 ms
520 bytes from 192.168.199.124: icmp_seq=4 ttl=255 time=2.17 ms
520 bytes from 192.168.199.124: icmp_seq=5 ttl=255 time=2.17 ms
520 bytes from 192.168.199.124: icmp_seq=6 ttl=255 time=4.42 ms
520 bytes from 192.168.199.124: icmp_seq=7 ttl=255 time=1.90 ms
520 bytes from 192.168.199.124: icmp_seq=8 ttl=255 time=1.72 ms
520 bytes from 192.168.199.124: icmp_seq=9 ttl=255 time=27.8 ms
520 bytes from 192.168.199.124: icmp_seq=10 ttl=255 time=1.67 ms
520 bytes from 192.168.199.124: icmp_seq=11 ttl=255 time=1.73 ms
520 bytes from 192.168.199.124: icmp_seq=12 ttl=255 time=1.91 ms
520 bytes from 192.168.199.124: icmp_seq=13 ttl=255 time=10.7 ms
520 bytes from 192.168.199.124: icmp_seq=14 ttl=255 time=1.84 ms
520 bytes from 192.168.199.124: icmp_seq=15 ttl=255 time=1.80 ms
520 bytes from 192.168.199.124: icmp_seq=16 ttl=255 time=1.77 ms
520 bytes from 192.168.199.124: icmp_seq=17 ttl=255 time=3.93 ms
^C
--- 192.168.199.124 ping statistics ---
17 packets transmitted, 17 received, 0% packet loss, time 16020ms
rtt min/avg/max/mdev = 1.673/4.936/27.766/6.327 ms

Однако нет никакой гарантии задержки при использовании Wi-Fi - в тот момент, когда кто-либо в радиусе покрытия вашего сигнала решит загрузить большой файл на том же канале (или достаточно соседнем канале) или разогнать бутерброд в микроволновке, вам будет плохо.

Во-вторых, обработка данных с малой задержкой обычно выполняется с помощью UDP. TCP - надежный протокол, и он будет повторять попытки передачи до тех пор, пока все отсутствующие пакеты не будут повторно отправлены и не будут упорядочены пакеты, не соответствующие порядку. Сокет-получатель просто ждет, пока TCP завершит все это, прежде чем он получит свои данные, доставленные без потерь и в порядке. Это означает, что несколько потерянных пакетов могут вызвать значительную задержку для всех последующих данных до тех пор, пока пробка не будет разряжена. Если вы можете терпеть потерю пакетов и доставку с нарушением порядка, используйте вместо этого UDP.

person Tarmo    schedule 12.01.2021