Ethernet Shield and multiple incoming connections of the same port? (BBS)

I have a small BBS program I wrote back in 1983 that I have ported to Arduino. I wanted to allow Telnet access to the system, so I added that and was surprised at how easy it was.

But, when someone is connected and using the BBS, other attempts will connect but sit there. I want to be able to send responses back saying "The system is busy. Please try later."

Thinking back to when I used to program BSD style socket() code, a server would listen on the port (80, 23, etc.) and then hand off the connection to a different port, leaving the original port open for more connections.

Is this possible with the Whiznet Ethernet? From looking at some specs, it seems it creates connections, and you just talk to them 1-4. I am wondering if there is a way to change the port on connection like BSD style code does.

Thoughts?

I have been looking through the source code and it seems with the Wiznet chip, all we have access to is 1-n connections and the port they connected on. So, if we listen on (23) and get a connection, we can write to it, but if another socket is also listening on (23), it seems it can indeed connect to it, but the library just finds the first (23) and returns that.

It does not look like this code could support multiple incoming connections unless they are on different ports. If it wasn't for NAT, I would think just tracking the remote IP would suffice, but it seems we would need more than that.

Has anyone done this? If not, I'll try to implement something and post it back.

Okay, that was a bit easier than I though it would be. I made a few minor additions to the Ethernet library, so now when a server connection is made, it records the remote client's port. Then instead of just snagging the first Wiz ethernet socket that matches the port (useless if you have more than one listening on the same port), it will check to see if it matches.

Now I can telnet in to my system, and it can be handling data, and I insert a second check inside that loop listening for another connection. If it gets one, it can send a "System is busy. Please try back later." message back.

I will clean this up then post it here, and maybe someone can verify that I am doing it the best way.

The following source contains a server example that should work, but does not because of a flaw in the Ethernet library. In the comments after it, I have a few changes you can make to the Ethernet library to allow multiple incoming connections to your server. Right now, the code just allows sending a rejection notice to additional connections, but I have some code I am working on that would allow multiple telnet sessions happening at the same time.

Questions:

  1. Should my new _dstport variable in EthernetClient.h also be static, like the _srcport? (Whis is _srcport static?)

  2. In EthernetServer.cpp, the available() routine serves to establish a new connection, but you only know the connection is there if there is data to read. This requires the new telnet session to have to press Enter or something, which I do not think is proper behavior for telnet. In my “new connection” code, I tried just returning client and it worked like I expected (without needing to press Enter to make data for the server to see). I suspect there should really be a call to accept() or something first, before entering the available() loop.

More to come…

// MultiServer Demo

#include <SPI.h>
#include <Ethernet.h>

byte      mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(10, 0, 0, 42);

EthernetServer server1(23);
EthernetServer server2(23);

void setup()
{
  Serial.begin(9600);
  while (!Serial);

  // start the Ethernet connection and the server:
  Ethernet.begin(mac, ip);
  server1.begin();
  server2.begin();
  Serial.print("\nServer is listening at ");
  Serial.println(Ethernet.localIP());
}


void loop()
{
  EthernetClient client2;
  
  // listen for incoming clients
  EthernetClient client1 = server1.available();
  if (client1) {
    Serial.println("Client 1 connected.");
    client1.println("Greetings, program!");
    while(client1.available()>0) client1.read(); // Gobble

    // Loop while client is connected.
    while (client1.connected())
    {
      // While we are connected, inform other attempts to connect that we are busy.
      client2 = server2.available();
      if (client2) {
        Serial.println("Client 2 connected. Getting rid of them...");
        client2.println("The system is busy. Try back later.");
        delay(1);
        client2.stop();
        Serial.println("Client 2 disconnected.");
      }
      
      // Then handle the actual client.      
      
      // If data is available, just read it and write it back to the user.
      if (client1.available())
      {
        char c = client1.read();
        Serial.write(c);
      }
    } // end of while... go back and do it again.
    
    // If here, we must no longer be connected.
    delay(1);
    // close the connection:
    client1.stop();
    Serial.println("Client 1 disconnected.");
  }
}

/*--------------------------------------------------------------------------*/
// To fix the Ethernet library so it correctly allows multiple connections
// to the same port, the following files will need to be modified:
//
// libraries/Ethernet/Ethernet.h
// libraries/Ethernet/Ethernet.cpp
//
// libraries/Ethernet/EthernetClient.cpp
// libraries/Ethernet/EthernetClient.h
//
// libraries/Ethernet/EthernetServer.cpp

/*

Modify the following files:

1) Ethernet.h: The Ethernet object currently only tracks which Port the
socket is listening to. Add the following array to hold the remote Port. 

Add this after static "uint16_t _server_port[MAX_SOCK_NUM];"

  // ACH - added
  static uint16_t _client_port[MAX_SOCK_NUM]; // ACH

2) Ethernet.cpp: Add the declaraction of the new array.

Add this after "uint16_t EthernetClass::_server_port[MAX_SOCK_NUM]"

// ACH - added
uint16_t EthernetClass::_client_port[MAX_SOCK_NUM] = { 0, 0, 0, 0 }; // ACH
 
3) EthernetClient.h: Add prototypes for the new functions, and declare a
new local variable that will track the destination port of this client.

Add this in the private: section
 
  // ACH - added
  uint16_t _dstport; // ACH

  // ACH - added
  uint8_t *getRemoteIP(uint8_t remoteIP[]); // ACH
  uint16_t getRemotePort(); // ACH
 
4)  EthernetClient.cpp: When the Client object is created, it initializes the
_server_port to zero. We should probably do this for the new _client_port.

Add after this: EthernetClass::_server_port[_sock] = 0;

  // ACH - added
  EthernetClass::_client_port[_sock] = 0; // ACH

Add these two functions at the bottom of the file:

// ACH - added
uint8_t *EthernetClient::getRemoteIP(uint8_t remoteIP[]) // ACH
{
  W5100.readSnDIPR(_sock, remoteIP);
  return remoteIP;
}
 
uint16_t EthernetClient::getRemotePort() // ACH
{
  return W5100.readSnDPORT(_sock);
}
 
5) EthernetServer.cpp: This code has to be modified so when it checks for
a connection, it checks both the Port (existing code) AND the remote
client's port (new code). If the connection has never been made, it will
initialize the remote port varaible correctly.

Add the following code to available()

EthernetClient EthernetServer::available()
{
  accept();
 
  for (int sock = 0; sock < MAX_SOCK_NUM; sock++) {
    EthernetClient client(sock);
    if (EthernetClass::_server_port[sock] == _port &&
        (client.status() == SnSR::ESTABLISHED ||
         client.status() == SnSR::CLOSE_WAIT)) {
 
      // ACH - added
      // See if we have identified this one before
      if (EthernetClass::_client_port[sock] == 0 ) {
          client._dstport = client.getRemotePort();
          EthernetClass::_client_port[sock] = client._dstport;
      }
      if (EthernetClass::_client_port[sock] != client._dstport) {
        // Not us!
        continue;
      }
      // ACH - end of additions
      if (client.available()) {
        // XXX: don't always pick the lowest numbered socket.
        return client;
      }
    }
  }
 
  return EthernetClient(MAX_SOCK_NUM);
}
 
...and code to write():

size_t EthernetServer::write(const uint8_t *buffer, size_t size)
{
  size_t n = 0;
 
  accept();
 
  for (int sock = 0; sock < MAX_SOCK_NUM; sock++) {
    EthernetClient client(sock);
 
    if (EthernetClass::_server_port[sock] == _port &&
        // ACH - added
        EthernetClass::_client_port[sock] == client._srcport && // ACH
      client.status() == SnSR::ESTABLISHED) {
      n += client.write(buffer, size);
    }
  }
 
  return n;
}

*/
/*--------------------------------------------------------------------------*/

I have tested this with the current Arduino 1.6.0 release and it still works. There must be much interest in this. The article I posted about my Ethernet library hack is still the #1 most viewed article on my site.

http://subethasoftware.com/2013/04/09/arduino-ethernet-and-multiple-socket-server-connections/

This does also. It handles 4 simultaneous connections. http://playground.arduino.cc/Code/Telnet

Very cool! I see you also had to work around the Ethernet library bug by talking to the Wiznet directly. Good job! I like the debugging information.

The existing library would only work (using API calls) if each server listened on a different port, which of course is not the way real internet works (telnetd, httpd, etc.).

Working within the API of the library, my bug fix requires the server app to create a Sever desired instance for each connection:

EthernetServer server1(23); EthernetServer server2(23); EthernetServer server3(23); EthernetServer server4(23);

This is not ideal, but since we are not dealing with a standard socket() type implementation, that would be the only way to do it and get a different path for each connection. (Beyond poking about directly to the WizNet like you accomplished.)

There are socket-style library implementations for the Wiznet, but when I looked at them, the code was much bulkier. Ideally, we'd get some updates to the Ethernet library with a minor addition to allow only one server instance and then to be able to detect which connection it was from, perhaps, available(). I did not explore that, since I was trying to simple bug fix the existing API to make it work like it should

SurferTim: This does also. It handles 4 simultaneous connections. http://playground.arduino.cc/Code/Telnet

I added this example to my article. Very nice.

hi allenHuffman, I've tried what you do, it managed to compile with no errors, but not running properly, I use arduino sketch V 1.6.5 ... is there any other solution ... ?? Thanx .

"...but not running properly" is a bit vague. What do you expect, and what does it do?

I just want a second connection and so canceled by sending some code like “client2.println(“NoAccess”);” to the client,and the client received it and disconnect to the server

I don't understand what you are doing or what you expect. Post your code and explain what application you are using as a client..

rosiscafidel: hi allenHuffman, I've tried what you do, it managed to compile with no errors, but not running properly, I use arduino sketch V 1.6.5 ... is there any other solution ... ?? Thanx .

Here is my code, that worked on an earlier Arduino IDE version (with a link to my website article that I have updated a number of times since 2013):

https://github.com/allenhuffman/sesTelnetServer

The article on my website was the #1 most viewed article I had for almost two years, but it suddenly stopped being viewed sometime last year. I was assuming they finally fixed the official Ethernet code so my hacks were no longer necessary. I have not touched it in over a year, so I do not know. I am still running the fixed version I had from an older IDE version.

Hey, can anyone confirm if the AdvancedChatServer sketch actually works if you telnet in to the Arduino multiple times from the same system (so each connection has the same IP address)? I don't have access to my Arduino Ethernet at the moment.

Yes, it works. The same client IP but different ports.

SurferTim: Yes, it works. The same client IP but different ports.

Has this always worked? If so, why did you, I, and several others have to come up with custom ways to make this work?

I'm not sure what you mean. I haven't really played a lot with the chat server example, so I don't know when it did or didn't work. I have a telnet (persistent tcp connection) example in the playground. It doesn't work like a chat server. It communicates with each client separately.