Teensy 4.1: can't get combined UDP/TCP server to work (now with example code)

flok

Well-known member
Hi,

I'm trying to port an application of mine to the teensy 4.1. Compiling and linking is no problem but networking is problematic (for me at least :) ).

I have two sockets:
  • one listens on a UDP port
  • one listens on a TCP port

What I then do is:

C++:
EthernetServer *server = new EthernetServer(port);
        server->begin();


        EthernetUDP *handle = new EthernetUDP();
        handle->begin(161);


        for(;;) {
                int rc = handle->parsePacket();
                if (rc) {
                        // handle udp packet
                }


                Ethernet.maintain();


                auto c = server->accept();
                if (c) {
                        // handle tcp session
                }
        }

The UDP handling works fine: in 1 a millisecond I prepare a response and send it back. Using lots of Serial.println() I verified that my UDP code is non-blocking.
Now the tcp part on the other hand doesn't work at all. It listens for a connection, connection gets setup but it never reaches the accept() part it looks like: if (c) never evaluates to true.

What can it be?

(p.s. the whole project can be found at https://github.com/folkertvanheusden/iESP/blob/teensy4.1/ESP32/com-arduino.cpp#L89 )
 
Last edited:
I shrunk the program a bit and it still shows the problem:

C++:
#include <NativeEthernet.h>
#include <NativeEthernetUdp.h>




EthernetServer *server { nullptr };
EthernetUDP    *handle { nullptr };


uint8_t mac[6] { 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc };


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


        if (Ethernet.begin(mac) == 0) {
                Serial.println(F("Failed to configure Ethernet using DHCP"));


                if (Ethernet.hardwareStatus() == EthernetNoHardware)
                        Serial.println(F("Ethernet shield was not found"));
                else if (Ethernet.linkStatus() == LinkOFF)
                        Serial.println(F("Ethernet cable is not connected"));
        }
        else {
                Serial.print(F("Ethernet initialized ("));
                Serial.print(Ethernet.localIP());
                Serial.println(F(")"));
        }


    server = new EthernetServer(3260);
    server->begin();


        handle = new EthernetUDP();
        handle->begin(161);


    Serial.println(F("Go!"));
}


void loop() {
    for(;;) {
        if (handle->parsePacket()) {
            Serial.println(F("UDP"));
            char buffer[128];
            handle->read(buffer, sizeof buffer);
        }


        Ethernet.maintain();


        auto c = server->accept();
        if (c) {
            Serial.println(F("TCP"));
            c.close();
        }
    }
}

What you'll see with this is; sending UDP packets triggers the text "UDP" to be send to the serial port, yet connecting to the tcp port (using e.g. telnet) connects but the "TCP" text never shows up on serial and the connection is also not closed.
 
Last edited:
Found something: this problem does not happen (with the little example program in my 2nd post) in teensyduino 1.158.0, only in 1.159.0.
 
You might want to try QNEthernet instead of NativeEthernet, which is no longer supported.

That's problematic:

Code:
.pio/libdeps/Teensy4_1/QNEthernet/src/lwipopts.h:208:36: error: expected identifier before numeric constant
  208 | #define TCP_MSS                    1460  /* 536 */
      |                                    ^~~~
 
No idea what you're doing. My point was that QNEthernet is supported and NativeEthernet not so much. You may also want to try building your simple program in the Arduino IDE. There have been many posts here where PIO tools configuration/settings were different from Arduino and possibly incorrect.
 
No idea what you're doing. My point was that QNEthernet is supported and NativeEthernet not so much. You may also want to try building your simple program in the Arduino IDE. There have been many posts here where PIO tools configuration/settings were different from Arduino and possibly incorrect.

I fixed it: the compile problems with QNEthernet were due to conflicts with a board-package version.

Yes, with QNEthernet the networking problems are gone. Nice!
Please close the ticket :)

(now the SD card does not seem to work?!)
 
Whatever issue you're having, your best bet for getting help here is to create a small test program that shows the issue and builds in Arduino IDE.
 
Whatever issue you're having, your best bet for getting help here is to create a small test program that shows the issue and builds in Arduino IDE.

I did in my second post (the reply to myself).
Looks like something still goes wrong (when doing tcp/udp interleaved), but I'm investigating it.
 
A few comments:

1. No need to use dynamic memory to create EthernetServer and EthernetUDP. Simply declare them like EthernetServer server{3260}; and EthernetUDP handle;.

2. For EthernetServer specifically, QNEthernet improves the Arduino-style API by adding the ability to set the port separately; you can declare as EthernetServer server; if you like and then set the port later.

3. handle.parsePacket() will return a negative value if there’s no packet, meaning if you treat it as a Boolean as you did in the code, it will evaluate to true. This means the contents of that if-statement will always get executed unless you receive a zero-length packet. What you want instead is if (handle.parsePacket() >= 0). Or, if you always expect packets of at least a certain size, use that size instead of zero. (Note: I’m not sure what other libraries do for zero, but zero-length UDP packets are a thing.)

4. Calling maintain() is not necessary. (It does nothing for the stacks available for Teensy 4.1’s native Ethernet support.)

5. Because the Teensy has its own MAC address, you don’t need to specify one unless you really want to. This is another QNEthernet feature, to have the work done internal to the library to retrieve that MAC address. Simply use Ethernet.begin() instead. (This is the DHCP versions.)

I encourage you to check out some of the QNEthernet library examples for some better and different ways to do things. I find the Arduino examples sometimes incorrect, inconsistent, or too simple.
 
Last edited:
(Note: I’m not sure what other libraries do for zero, but zero-length UDP packets are a thing.)
I recently stumbled over std::optional which comes in handy for such situations. Of course, changing an already published API is a no-go, but std::optional seems to be a nice tool for the toolbox...
C++:
#include <optional>

std::optional<unsigned> parsePacket()
{
  bool gotSomePacket = true;
  unsigned packetSize = 42;
  //unsigned packetSize = 0;   

  if (gotSomePacket)
    return packetSize;  // any value 
  else
    return std::nullopt;
}

void setup()
{
  while (!Serial){}

  if (!parsePacket())
    Serial.println("parse error");
  else
    Serial.println(*parsePacket());
}

void loop() {}

See https://www.cppstories.com/2018/05/using-optional/ for more information
 
Indeed. See also C++23’s std::expected and C++11’s std::exception_ptr for some additional fun. Next, look up “monad”…

I struggle with having/wanting to support these very old APIs. I have some thoughts on API layering so I don’t have to be constrained…
 
I’ll also point out a potential gotcha with optionals. If using them to hold booleans, it’s easy to confuse using the implicit presence-testing for accessing the value, if you’re not careful to use the accessor operators or functions. Instead, use has_value() and value() to be crystal clear. Whenever I use them and test for presence or access the value, just as a habit, I often use has_value() and value(), no matter the content. (Unfortunately, that means the code is a little more verbose.)
 
Last edited:
Back
Top