Greetings! If you are finding this writeup useful, please leave a comment. I originally shared these modifications in April 8, 2013. In February 2015, I went through them again using the current Arduino 1.6.0. They still work, but I did tweak the source code notes a bit to be clearer. – Allen
- 2014/04/03: I just started using GitHub and found all the Arduino sources there, including this bug report which seems to discuss and address this issue. I will be reviewing it when I have time to see if those fixes take care of this bug.
- 2014/04/08: This is, by far, the most viewed article on this site. I suppose I should do more posts about Arduino Ethernet.
- 2013/04/09: I have done some more tweaks to the code listed in this article, and will try to update them when I have a chance. I will probably spin it off in to a whole new article on an easy “drop in” telnet server I am working on.
- 2014/04/14: Fixed some HTML escape codes in the source code. (Thanks, Matt!)
- 2014/04/16: You can now get an Ethernet shield shipped from the US for $11.49.
- 2014/04/19: In the comments, Petr Stehlík pointed out that it doesn’t look like a check against incoming IP address is done, meaning that if any packet was received with the same port, it would just be accepted. I will need to investigate the rest of the code and see if it does that check. If not, you could blast a packet to an Arduino and as long as you match the port, it would accept it. That seems real bad, but should be very easy to fix, if needed.
- 2015/02/15: I just checked these updates against the Arduino 1.6.0 release, and they still work. I am updating the notes on this page to note where the libraries folder is found on Mac OS X, and to clarify where one of the changes goes. I have zipped up my changed and places them here: EthernetMultiServer.zip This may allow a clean 1.6.0 install to “Import Library” and work, but I have not tested that yet. Also, Arduino forum user SurferTim has contributed another way to accomplish this without fixing the library. He has posted a Telnet example in the Playground that talks directly to the Wiznet 5100 chip to keep the incoming connections straight. Very clever (and similar to what I had to do in the library to track the remote ports and keep them separated). Cool
- 2015/05/22: Similar to the SurferTim approach, a comment by Gene provides another standalone way to do this in code without having to modify the library. He includes a full example in his comment.
NOTE: The links and prices given below may be out of date. Since then. I discovered this seller (kbellenterprises) on e-Bay. They offer some low-cost Arduino clone items. They have always been responsive, and ship very fast. They currently have an UNO clone for $8.49, and a Wiznet 5100 Ethernet shield for $11.49.
The Arduino Ethernet shield (or the $17.99 workalike made by SainSmart) adds internet support to the Arduino. The limited memory of the Arduino does not have to run a full TCP/IP stack. Instead, the Ethernet shield uses a Wizpro chip that handles Ethernet, TCP, UDP and IP protocols. This particular chip, Wizpro W5100, supports four simultaneous connections. This means you could have an Arduino sketch that opens four different websites at the same time, or you could run a server that allows up to four simultaneous users to connect.
At least, you could if the Ethernet library worked right. The way it was designed (I call it a bug or at least an oversight), the existing library would only allow four incoming connections if each one was listening on a different port. For example, if you have port 23 set up to monitor incoming TELNET connections, and one user was connected, any other attempts to connect would be refused until the first connection was closed. I wanted to use the “up to four connections” part to still allow other users to connect, and then tell them “The system is busy. Go away.”
My first attempt to do this was to just create two server instances:
EthernetServer server1(23);
EthernetServer server2(23);
I then modified the WebServer example and trimmed everything out except what I wanted. My main addition was this second server instance, and a check for connections to it while processing the primary connection. It looks like this (see notes afterwards):
// MultiServer Demo
#include <SPI.h>
#include <Ethernet.h>
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(192, 168, 0, 100);
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 A connected.");
client1.println("Greetings, program!");
while(client1.available()>0) client1.read(); // Gobble
// Loop while client is connected.
while (client1.connected())
{
client2 = server2.available();
if (client2.connected()) {
Serial.println("Client B connected. Getting rid of them...");
client2.println("nThe system is busy. Try back later.n");
delay(1);
client2.stop();
Serial.println("Client B 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();
if (c>0) 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 A disconnected.");
}
}
My plans was to wait for a connection. Once I had one, I would sit in a loop (as long as they remained connected) and check a second server instance to see if an additional connection attempt was made. If a second attempt was made, I’d send them a quick status message, then shut them down and go back to monitoring the main connection.
This should work, but doesn’t. It produced unexpected results:
Server Output:
Serving at 10.0.0.42
Server is listening at 10.0.0.42
Client 1 connected.
Client 2 connected. Getting rid of them…
Client 2 disconnected.
Client 1 disconnected.
Client (telent) Output:
alsmb:~ allenh$ telnet 10.0.0.42
Trying 10.0.0.42…
Connected to 10.0.0.42.
Escape character is ‘^]’.
Greetings, program!
The system is busy. Try back later.
Connection closed by foreign host.
alsmb:~ allenh$
It seemed the Ethernet could not distinguish between the two connections. I altered the server config to use different ports:
EthernetServer server1(23);
EthernetServer server2(2323);
Perhaps I could let 23 (telnet) be the main port, and 2323 be the status port. This provided much better results.
Server Output:
Server is listening at 10.0.0.42
Client 1 connected.
hello (I typed this from telnet)
(then, I made a second connection from another terminal, to port 2323)
Client 2 connected. Getting rid of them…
Client 2 disconnected.
On the first terminal, I was able to type “hello” and continue my connection, while the second terminal connected, then received the “Go away” message (after pressing some keys to generate some data to wake up the Ethernet code).
So it could work, but not with the same port.
Thanks to a forum post I found earlier explaining how to obtain the remote connection’s IP address, I was aware that the source code to the Ethernet libraries was part of the Arduino IDE, and that it was easy to make changes.
I decided to take a look and see if I could figure out how this Wiznet chip works.
Wiznet W5100
The Wiznet chip is an independent device. The Arduino sends it commands via the SPI bus (like sending byte commands to it) and then data can be read and written back, similarly to talking to a serial port. The Wiznet chip can be programmed to listen to up to four different socket connections, and then it takes care of the rest. The Ardunio basically says “Hey, Wiznet… Listen for connections on Port 23 of Socket 0” and then the Arduino code will query the Wiznet chip saying “Is anyone there?” and if so, handle accordingly.
Inside the Ethernet library code, I saw that it had an array to hold the ports the user was configuring. When you do EthernetServer begin(23), it puts a 23 in the first available slot then programs the Wiznet chip accordingly. These slots are how the Arduino knows which socket of the Wiznet to query. And that is where the problem is.
If you do “EthernetServer server1(23)” followed by “server1.being()”, slot 0 is set up with 23 and Wiznet socket 0 is programmed to listen for port 23 connections. If you then do “EthernetServer server2(23)” followed by “server2.begin()”, then slot 1 is set up with 23 and Wiznet socket 1 is programed to listen to port 23 connections. The Wiznet hardware is fine even with all four of it’s sockets listening to the same port. It tracks the actual connection internally.
But the Arduino code ONLY tracks the port number. So, if someone connects to the first socket 0 port, and is using it, then someone tries to connect to port 23 again, the Wiznet will hook them up to socket 1. The Arduino code makes a mistake, and when it checks for data, it grabs the first slot that matches the desired port. So, it keeps reading and writing data to slot 0 (socket 0) and never sees the second port 23 connection.
To resolve this, I made a few minor changes to the Arduino ethernet library code. First, I added secondary storage to track four remote ports (the ports used on the connecting client), and then added a bit of code that walked through all the available sockets trying to match up local server port number AND remote client port…
And it worked the first time, much to my amazement!
Here are my notes and modifications. There are a few things I did which I am not certain are correct, but they worked so I am sharing them. I will make a note of the parts I am unclear on.
Ethernet Library Modifications:
/--------------------------------------------------------------------------/
// 2015-02-15: Verified against Arduino 1.6.0.
//
// 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/src/Ethernet.h
// libraries/Ethernet/src/Ethernet.cpp
//
// libraries/Ethernet/src/EthernetClient.cpp
// libraries/Ethernet/src/EthernetClient.h
//
// libraries/Ethernet/src/EthernetServer.cpp
//
// On Mac OS X, these are embedded inside the Arduino.app package. Browse
// to that in the Finder, then right-click and select "Show Package
// Contents" and then you can go to:
//
// Contents/Resources/Java/libraries
//
// Is there a better way?
/*
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 stopped, it resets the
_server_port to zero. We should probably do this for the new _client_port.
In void EthernetClient::stop():
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;
return client;
}
if (EthernetClass::_client_port[sock] != client._dstport) {
// Not us!
continue;
}
// ACH - end of additions
//if (client.available()) { // ACH - comment out
// XXX: don't always pick the lowest numbered socket.
return client;
//} // ACH - comment out
}
}
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;
}
/
/--------------------------------------------------------------------------*/
During this research, I also found this part to be bothersome:
if (client.available()) {
// XXX: don't always pick the lowest numbered socket.
return client;
}
Unlike the EthernetClient.available(), the one for EthernetServer really shouldn’t be checking for data. It’s checking to see if a brand new connection has been made, then the code will be checking for data available elsewhere. For this to not just always return client would mean you would get a connection, but never see it until there is data. You couldn’t use this code to write a server that someone telnets to and it just spits out something (like a status or a time). It looks like it would force the remote user to send data first before the Arduino code even knows the connection is there.
I commented out the if so it always returns client, since it will bypass that if it finds no matching socket. It seems to work.
Bug?
Coming soon… Multi user BBS for Arduino!