Page 1 of 1

Can we use WiFiServer to accept connections in blocking mode?

Posted: Sun Jul 14, 2024 10:18 am
by jhsrennie
In unix we would typically code a server using something like:

Code: Select all

  listen(sock_srv, 1);
  while (true) {
    sockaddr addr_client;
    int sock_client = accept(sock_srv, &addr_client, NULL);
The call to accept() blocks until a connection is received, at which point it springs back into life and returns a socket we can use for communicating with the client. The equivalent when coding for an ESP32 seems to be something like:

Code: Select all

  // Listen on port 23
  WiFiServer server(23);
  server.begin();

  // Loop to handle connections
  while (true) {
    // Wait for a connection
    WiFiClient client = server.available();
    if (client) {
      ...
This works fine, but the call to server.available() is non-blocking so the loop is running continuously and using 100% CPU. On unix calling accept() would suspend the thread until data was available, which is good because it's pretty antisocial to consume 100% CPU on a multitasking OS.

My question is whether there is a way to make server.available() behave like the sockets accept() function and block until a connection is available?

I realise that a microcontroller is very different from the sort of computer we'd typically use to run unix, and keeping a thread spinning may simply not be an issue. However if I want to create a new task to handle the client (using vTaskCreate()) then keeping the main thread spinning will be taking CPU away from the new task(s). It seems more elegant so have the available() function block.

I realise that I can just use the same sockets API that is used on unix, and in fact I have this working nicely on my ESP32-WROOM-32. However the sockets API is not especially entertaining to code for and the WiFiServer and WiFiClient provided in WiFi.h are much nicer to use.

Re: Can we use WiFiServer to accept connections in blocking mode?

Posted: Mon Jul 15, 2024 4:51 pm
by lbernstone
Calling delay allows the cpu to context switch. Just drop a delay of ~50-100 ms in your loop (or whatever is appropriate for your background tasks), and you will no longer be blocking other tasks.
Note that esp32 can also use posix sockets, so your original code works as well, if you add a delay in the loop.

Re: Can we use WiFiServer to accept connections in blocking mode?

Posted: Tue Jul 16, 2024 4:59 am
by jhsrennie
Thanks :-)

A delay will certainly stop the task calling WiFiServer.available() from using 100% CPU so that would be a solution, but is there no way to make WiFiServer.available() block?

Re: Can we use WiFiServer to accept connections in blocking mode?

Posted: Tue Jul 16, 2024 5:37 pm
by lbernstone
arduino-esp32 is set up to emulate the Arduino environment for AVR devices. Those devices don't have an OS or embedded radio, and so polling is really the only way to handle listening. Hence, WiFi.server uses polling.
The underlying ESP-IDF framework has the ability to use sockets, and can attach an interrupt to the listening socket which will trigger through a socket select(). If you create a new task, you can separate this pretty completely out of your main program flow. arduino-esp32 is built as a HAL over the IDF, so all that functionality is available to you in arduino-esp32 as well.
Here's an example. Create a new task with the socket init+select stuff in it to get your blocking listener.

Re: Can we use WiFiServer to accept connections in blocking mode?

Posted: Wed Jul 17, 2024 3:38 am
by jhsrennie
Thanks again.

It turns out you can use sockets even in Arduino mode. If anyone is interested, this code works:

Code: Select all

#include <lwip/sockets.h>
#include "WiFi.h"

void ConnectWiFi() {
  WiFi.begin("myssid", "mypassword", 6);
  int loopcnt = 0;
  while (WiFi.status() != WL_CONNECTED) {
    Serial.printf("Connecting: time %d, WiFi status = %d, signal = %d\n", loopcnt++, WiFi.status(), WiFi.RSSI());
    delay(1000);
  }
  Serial.printf("Connected: %s\n", WiFi.localIP().toString().c_str());
}

void setup() {
  Serial.begin(115200);
  ConnectWiFi();
}

void loop() {
  // Create, bind and listen on port 23
  int sock_srv = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  
  struct sockaddr addr_in;
  memset(&addr_in, 0, sizeof(addr_in));
  struct sockaddr_in* p_addr_in = (struct sockaddr_in*) &addr_in;
  p_addr_in->sin_family = PF_INET;
  p_addr_in->sin_port = htons(23);
  p_addr_in->sin_addr.s_addr = INADDR_ANY;
  bind(sock_srv, &addr_in, sizeof(addr_in));

  listen(sock_srv, 1);
 
  // Loop to handle connections
  while (true) {
    // Wait for a connection
    sockaddr addr_client;
    memset(&addr_client, 0, sizeof(addr_client));
    int sock_client = accept(sock_srv, &addr_client, NULL);
    Serial.println("Accepted connection");

    // Just end a message then close the socket
    String s = "Welcome and goodbye!\r\n";
    send(sock_client, s.c_str(), s.length(), 0);
    closesocket(sock_client);
  }   
}