首页 > 解决方案 > 尝试减少 ESP32 上的 TCP 套接字延迟

问题描述

我目前正在开发我的 ESP-WROVER-KIT 和我的 PC 之间的 WIFI 连接原型。我对我的方法和结果有一些疑问。

这个原型的目的是让 ESP32 和 PC 之间的网络延迟尽可能低(大约 6-10 毫秒会很好),并且数据包大小保持 512 字节。目前PC通过以太网电缆连接到WIFI路由器。由于这是我第一次涉足网络,我做了一些研究并得出结论,TCP套接字连接将是一种有据可查的方法,有可能获取数据需要延迟。我使用了 ESP32 的 esp-idf 套接字示例和我的 PC 的 Windows 窗体应用程序的在线示例代码作为我的代码库,并进行了一些修改以测量返回时间和发送所需的数据包大小。

第一个测试将 ESP32 作为服务器作为回显,Windows 窗体作为客户端,发送一个 512 字节的数据包并计算返回所需的时间。本次交易记录的回程时间约为 40-50 毫秒。该值已使用 Wireshark 确认。

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 "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 窗体客户端发送/接收/时间

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 客户端回声


   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 窗体服务器发送/接收/时间

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-10ms(回程时间值在下一个数据包中发送)。在测量从 ESP32 (ESP32-PC-ESP32) 点返回的行程时,使用 Wireshark 确认了该值。但是,如果从 PC(PC-ESP32-PC)的角度在 Wireshark 中测量,返回时间约为 40-50ms。

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 窗体回显

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的情况下,用wireshark测量的回程时间是否表明最大的延迟来自通过ESP32处理数据?还是我误解了数据?我在阻塞调用中的 Windows 表单应用程序(秒表)和 ESP32(向上计数的计时器)中测试回程时间的方法是否准确?由于从 PC 到 ESP32 并返回的事务平均返回时间为 40-50 毫秒,因此我应该注意哪些设置或代码优化有助于减少此时间?如果这是我可以期待的最佳时间,您会推荐任何其他协议来实现更好的回程时间吗?请注意,我在每种情况下都禁用了 TCP 套接字 (TCP_NODELAY) 上的 Nagle 算法,但这并不影响结果。

谢谢你的时间

标签: socketstcpesp32latency

解决方案


连接到 ESP32 的延迟和抖动在很大程度上取决于所选通道上免费 WiFi 以太网的可用性。在拥挤的无线频道(意味着许多其他设备广播)上,您通常会看到 100 多毫秒的延迟,因为您的设备必须等待空闲的无线电插槽。

话虽如此,512 B 有效载荷在合理空闲信道上的预期往返延迟通常低于 10 毫秒,但并非总是如此。每隔 1 秒使用 ICMP PING 进行测试(ESP32 中禁用 WiFi 省电):

$ 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

但是,在使用 WiFi 时根本无法保证延迟 - 当您的信号覆盖范围内的任何人决定在同一频道(或足够相邻的频道)上下载大文件或微波三明治时,您就会被搞砸。

其次,低延迟的东西通常是用 UDP 完成的。TCP 是一种可靠的协议,它会重试传输,直到所有丢失的数据包都被重新发送并且乱序数据包被排序。接收方套接字只是等待,直到 TCP 完成所有这些,然后才能获取其数据,无损失地按顺序交付。这意味着一些丢失的数据包可能会导致所有后续数据的显着延迟,直到交通拥堵被耗尽。如果您可以容忍数据包丢失和无序交付,请改用 UDP。


推荐阅读