0

I've been working on a HTML / websocket server on a Wiznet W5100S-EVB-Pico, programmed in the Arduino IDE. It all worked fine up until now but I'm running into, I think, a string size limit. I guess it is in the way the code handles the const char but I don't know how to do it properly.

I hope someone is willing to help :)

Let me explain:

  • I convert the index.html to a index_html.h file containing a const char array:
const char c_index_html[] = {
0x3c,0x21,0x44,0x4f,0x43,..., ..., 0x6d,0x6c,0x3e};
  • In my code I include the index_html.h file:
#include "index_html.h"

Now the code that actually serves the "HTML"

if (web_client){
    
    Serial.println("New client");
    // an http request ends with a blank line
    bool currentLineIsBlank = true;

    while (web_client.connected()){
      
      if (web_client.available()){
        
        char c = web_client.read();
        
        if (c == '\n' && currentLineIsBlank)                // if you've gotten to the end of the line (received a newline
        {                                                   // character) and the line is blank, the http request has ended,
          Serial.println(F("Sending response"));            // so you can send a reply

          String strData;
          strData = c_index_html;
          web_client.println(strData);
          break;
        }

        if (c == '\n')
        {
          // you're starting a new line
          currentLineIsBlank = true;
        }
        else if (c != '\r')
        {
          // you've gotten a character on the current line
          currentLineIsBlank = false;
        }
      }
    }

This is not the prettiest code, it's smashed together from examples and now the main culprit seems to be:

String strData;
strData = c_index_html;
web_client.println(strData);

When I add extra code to the HTML and view the page source, the code is incomplete. I tested reducing the HTML to a minimum and that solves the problem.

So my main question is:

  • How do I serve the 'const char c_index_html' without use of 'String'?

But also:

  • How could I prettify the whole 'if (web_client)' serving function?

Thank you very much for making it all the way through this post and if you have a suggestion I would very much appreciate it ;)

MrExplore
  • 57
  • 1
  • 8
  • 2
    TCP is a byte stream, there is no guarantee that a single `print` can take all of the data at one time. Typically, you have to loop a print to make sure everything gets printed correctly. Does `web_client.println(strData)` return back a value indicating how many bytes were actually printed? If so, it is likely less than you are expecting. – Remy Lebeau May 10 '22 at 18:47
  • It does: "*Returns: byte: return the number of bytes written, though reading that number is optional*" – Remy Lebeau May 10 '22 at 19:25
  • To avoid using a `String`, use `write()` instead of `println()`. Then you can pass in a byte buffer and a buffer size. – Remy Lebeau May 10 '22 at 19:26
  • 1
    At this moment with a stripped down HTML it returns 1493. By stating "it is likely less than you are expecting", you mean that the "HTML" const char is not being sent completely? That is indeed what's happening, the HTML in the "view source" is incomplete. Should I loop through the const char array and print each element using web_client.print(c_index_html[i]) ? – MrExplore May 10 '22 at 19:33
  • Ok, thanks for your replies - I will have to look into that and post back when I learned a bit more ok? – MrExplore May 10 '22 at 19:36
  • "*you mean that the "HTML" const char is not being sent completely?*" - yes. You did not say how large the HTML was, but even a trivial HTML page likely can't be written out in a single send. "*Should I loop through the const char array*" - yes. "*print each element*" - no. Use `write()` instead, eg: `const char c_index_html[] = {...}; const int c_html_size = sizeof(c_index_html); int num_sent = 0; while (num_sent < c_html_size) { num_sent += web_client.write(&c_index_html[num_sent], c_html_size - num_sent); }` – Remy Lebeau May 10 '22 at 19:46

2 Answers2

1

Edit: There is a bug in the ethernet library shown in this post.

I don't know if it affects you; you should look at your library implementation.


I'm assuming that web_client is an instance of EthernetClient from the Arduino libraries.

EthernetClient::println is inherited from Print via Stream and is defined in terms of write, which is:

size_t EthernetClient::write(const uint8_t *buf, size_t size)
{
    if (_sockindex >= MAX_SOCK_NUM) return 0;
    // This library code is not correct:
    if (Ethernet.socketSend(_sockindex, buf, size)) return size;
    setWriteError();
    return 0;
}

So we see that it asks the socket to send the buffer up to some size. The socket can respond with a size or 0 (see edit); if it responds with 0 then there's an error condition to check.

Edit: This is how it's supposed to work. Since write is always returning the requested size and not telling you how much was written, you can't fix your problem using the print/write facilities and need to directly use socketSend.

You're not checking the result of this write (which is supposed to come through println) so you don't know whether the socket sent size bytes, 0 bytes, or some number in between.

In EthernetClient::connect we see that it's opening a TCP stream:

_sockindex = Ethernet.socketBegin(SnMR::TCP, 0);

When you call socketSend you're actually just copying your buffer into a buffer in the network stack. The TCP driver writes out that buffer when it can. If you're writing into that buffer faster than it's being flushed to the network then you'll fill it up and your socketSend calls will start returning < size bytes. See Does send() always send whole buffer?.

So you're probably right that your string is too long. What you need to do is spread your writes out. There are countless tutorials covering this on the web; it's roughly like this in your example:

...

size_t bytesRemaining = 0;

while (web_client.connected()){

  if (bytesRemaining > 0) {
    // Still responding to the last request
    char const* const cursor = c_index_html 
                             + sizeof(c_index_html) 
                             - bytesRemaining;
    size_t const bytesWritten = web_client.write(cursor, bytesRemaining);

    if (!bytesWritten) {
      // check for error
    }
    bytesRemaining -= bytesWritten;

    if (bytesRemaining == 0) {
      // End the message. This might not write! 
      // We should add the '\n' to the source array so 
      // it's included in our write-until-finished routine.
      web_client.println();
      // Stop listening
      break;
    }

  } else if (web_client.available()){
    // Time for a new request
    
    char c = web_client.read();
    
    if (c == '\n' && currentLineIsBlank)               
    {                                                   
      Serial.println(F("Sending response"));            
      // Start responding to this request
      bytesRemaining = sizeof(c_index_html);
      continue;
    }

    ...
  • 1
    Thanks for your reply, I'll surely look into it. Time for bed now ;) – MrExplore May 10 '22 at 20:17
  • Looks like I'll be banging my head over this one this weekend... I've been trying some things to no avail so far -- I can't seem to find those "countless tutorials", I think I don't know what to search for. Could you let me know what to search for? I'm willing to learn and find out myself – MrExplore May 11 '22 at 18:09
  • @MrExplore Any TCP socket programming tutorial will explain how to do buffered reads and writes like this; Beej's Guide to Network Programming is a famous one. But my example pretty much covers it. You can also try just throwing web_client.flush() before break. These libraries are open source so you can often find the problem by reading the code you're using from them; for example, you'd look at whether they handle oversized data for you or if you're supposed to be doing this buffering yourself. – Matt Murphy May 12 '22 at 04:29
  • @MrExplore looking at your new answer, I wonder if you're expecting too much from the library. The 2k buffer isn't really a "limitation," it's just something that you should expect to have to deal with. You're using a TCP socket library; you'll have to provide your own windowing because TCP doesn't do that. Meaning, you shouldn't expect the library to tell you when your message begins or ends. Usually you would write the length first (length-prefixed encoding), then write your data in `buffer_size` chunks (2kb here) until it's done. There's no reason to expect it to buffer your whole message. – Matt Murphy May 13 '22 at 21:35
  • 1
    Thanks for the additional info, for now it kinda does what I would like it to. But I'll look into on the topic of TCP and sockets some more. – MrExplore May 17 '22 at 03:32
  • @MrExplore sounds good! If my answer is enough for now I’ll appreciate if you accept it, otherwise let me know what else I can address – Matt Murphy May 18 '22 at 17:43
  • 1
    Yeah good enough for now, it points my in the right direction, thanks. I do have some learning to do but that's up to me. – MrExplore May 18 '22 at 18:30
0

This is what I think is going on. I'm not an expert so I might be wrong, but it seems to make sense.

This is not an answer as in "solution" but I found out there is a 2k buffer size limit using the W5100S-EVB-Pico. And indeed, if I keep the HTML below 2k it works. Turns out that I actually got Matt Murphy's suggestion to work but the 2k limit was the problem. It looks like a hardware/library limitation, not completely sure on that.

For now I'll shrink my HTML and Javascript to a minimum and compact it even more with for example textfixer.com. I think I might write some python code to do that

Maybe there is a path to a solution in the link below but at this moment I'll try to live with the limitations

Link: https://github.com/khoih-prog/EthernetWebServer/issues/7

MrExplore
  • 57
  • 1
  • 8