1

I'm using the following class to create a stream from a WiFi (server) connection. The client (a PC application) connects to the ESP32 and then issues commands over this connection. This works fine. When the client disconnects gracefully, the connection is reset and the ESP32 is ready for another connection. Only one client can connect at a time, but that's ok for now.

The problem arises when the client disconnects unexpectedly (e.g. if I just kill the process on the PC). The connection remains open for a very long time (10 minutes or so) until the ESP32 detects that the connection died. Within that time, no new connection can be established. In that state, I can still see it printing "Still connected" messages to the serial console, with reducing pace (the number of messages per second drops significantly until it finally gives up).

Is there a way to reduce the timeout on a dead connection? The (working) connection itself might be idle for long times as well, so just because no messages are received doesn't mean we can drop the connection from the ESP32 end.

The Connect() function below is called from loop() and in normal mode, it just prints "New client connected" and "client disconnected" messages.


class WifiCachingStream : public Stream
{
private:
    WiFiServer _server;
    WiFiClient _activeClient;
    bool _hasActiveClient;
public:
    WifiCachingStream(int port) :
    _server(port), _activeClient()
    {
        _hasActiveClient = false;
    }

    void Init()
    {
        _server.begin();
    }

    bool Connect();

    int read() override;

    size_t write(byte b) override;

    void maintain();

    int available() override
    {
        return _activeClient.available();
    }

    int peek() override
    {
        return _activeClient.peek();
    }

    void flush() override
    {
        // Do nothing. The implementation of WifiClient clears the _INPUT_ queue instead of the output queue!
        // _activeClient.flush();
    }

    bool isConnected() const
    {
        return _hasActiveClient;
    }
};

int idx = 0; // for testing only
bool WifiCachingStream::Connect()
{
    if (_activeClient.available() || _activeClient.connected())
    {
        if ((idx ++) % 10000 == 0)
        {
            // For testing: This is printed to the console, even after the communication has been killed on the PC
            Serial.println("Still connected");
            // This (attempts to) send a message over the link, which I would expect to cause the implementation to detect that the connection was dropped.
            Firmata.sendString(F("Still connected"));
        }
        return true;
    }

    _activeClient.stop();
    _activeClient = _server.accept();

    if (_activeClient)
    {
        Serial.println("New client connected");
        WiFi.setSleep(false);
        _hasActiveClient = true;
        return true;
    }

    if (_hasActiveClient)
    {
        _hasActiveClient = false;
        Serial.println("Client disconnected - entering WiFi low-power mode");
        // Low-power mode significantly increases round-trip time, but when nobody
        // is connected, that's ok.
        WiFi.setSleep(true);
    }
    
    return false;
}

// Other functions irrelevant

1 Answer 1

1

It's more or less a fact of life that having TCP detect a lost connection (ungraceful disconnect) takes a long time when nothing is being sent over it.

There may be some quaint and curious keepalive and time-out settings in the protocol itself, but implementing a heartbeat (call it an application-level keep-alive) that is sent by the client, and closing the connection from the ESP32-end when no heartbeat/keep-alive message has been received for a certain period, is probably your best bet and it will also give you precise control over the time-out time.

1
  • I'll consider this, but I don't really have control over the protocol. Adding a heartbeat message has been discussed, but not approved yet. I need to check that thread.
    – PMF
    Commented Nov 20, 2021 at 9:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.