Monday, October 5, 2015

Arduino Ethernet Library Modification

I was recently working on a piece of test/demo code running on an Arduino that talked through a Seeed Studio Ethernet shield.  The standard Ethernet library will not acknowledge a connection back to the host code until it receives something from the client.  For most things, this works out just fine (HTTP, chat servers, etc) but in some cases, like for RFB/VNC servers, it must first send out something itself to the client.  The as-shipped library does not allow for this, so it required a minor hack to the library code to make this option available.

in (Libraries)/Ethernet/src/EthernetServer.cpp, we can see the following code:
  01 EthernetClient EthernetServer::available()
  02 {
  03   accept();
  04   for (int sock = 0; sock < MAX_SOCK_NUM; sock++) {
  05     EthernetClient client(sock);
  06     if (EthernetClass::_server_port[sock] == _port) {
  07       uint8_t s = client.status();
  08       if (s == SnSR::ESTABLISHED || s == SnSR::CLOSE_WAIT) {
  09         if (client.available()) {
  10         return client;
  11         }
  12       }
  13     }
  14   }
  15   return EthernetClient(MAX_SOCK_NUM);
  16 }

As you can see, it checks to see if there is a connection established (line 08), then it checks to see if there's any content available from the client (bolded line 09), and only then will it return the handle to the newly connected client. This will be called in your Arduino code like this (from the WebServer example, packed with the Ethernet library)
void loop() {
  // listen for incoming clients
  EthernetClient client = server.available();
  if (client) {
    Serial.println("new client");
    // do stuff here...
    client.stop();
    Serial.println("client disconnected");
  }
}

This loop will only make and break connections if the client is actually connected AND it has data available. For most servers, this is sufficient.  However, if you want to allow for clients that connect and then just listen, you need to do the following...

In EthernetServer.cpp, create a copy of the ::available() method, eliminating lines 9 and 11 above. I call this function ::connectionEstablished(), plopped right into the EthernetServer.cpp file, just after the ::available() method, and it is implemented as follows:

EthernetClient EthernetServer::connectionEstablished()
{
  accept();
  for (int sock = 0; sock < MAX_SOCK_NUM; sock++) {
    EthernetClient client(sock);
    if (EthernetClass::_server_port[sock] == _port) {
      uint8_t s = client.status();
      if (s == SnSR::ESTABLISHED || s == SnSR::CLOSE_WAIT) {
        // "if" statement removed
        return client;
      }
    }
  }
  return EthernetClient(MAX_SOCK_NUM);
}

Next, you need to add the method to the header file, EthernetServer.h, as seen in this snippet
class EthernetServer :
  public Server {
    private: 
    uint16_t _port;
    void accept();
  public: 
    EthernetServer(uint16_t); 
    EthernetClient connectionEstablished();
    EthernetClient available();
    virtual void begin();
    virtual size_t write(uint8_t);
    virtual size_t write(const uint8_t *buf, size_t size);
    using Print::write;
};


Now, you can call this new method like this:
void loop() {
  // listen for incoming clients
  EthernetClient client = server.connectionEstablished();
  if (client) {
    Serial.println("new client has connected");
    // do stuff here...
    client.println( "Hello, goodbye!" ); 
    client.stop();
    Serial.println("client disconnected");
  }
}

1 comment:

  1. I just realized a much more simplified way of doing this. Don't duplicate the entire function, instead add a parameter to the available() function like so:

    in EthernetServer.h:

    #define kEthernetWaitForRX (true)
    #define kEthernetNoWaitForRX (false)

    EthernetClient available( bool waitForRX = kEthernetWaitForRX );

    Then in EthernetServer.cpp:

    EthernetClient EthernetServer::available( bool waitForRX )

    if (!waitForRX || client.available()) { // this is the only modification in the method necessary

    ReplyDelete