I’m not sure why your test is crashing, but here’s what I tried:
The Photon code is a simple web server that returns an index.html file. The server side code makes a new HTTP request to the Photon every second. I would disconnect the Ethernet to the access point for about 5 seconds, long enough to stop incoming connections but not go to blinking cyan. Then wait for things to stabilize again, then disconnect for long enough to go to blinking cyan. I repeated this 5 times, which was about 330 connections. I didn’t run into any trouble.
That’s not to say a bug doesn’t exist, but I haven’t seen it happen yet. I tested with 0.5.3.
Here’s the Photon code:
#include "Particle.h"
SYSTEM_THREAD(ENABLED);
// Pages
// [start a87cffa2-e342-4f2c-9070-72b710d606c3]
// name=/index.html contentType=text/html size=293 modified=2016-11-08 12:50:47
const char fileData0[] =
"<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"
"<!DOCTYPE html>\n"
"<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"
"<head>\n"
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n"
"\n"
"<title>Test Page</title>\n"
"\n"
"</head>\n"
"<body>\n"
"\n"
"<div id=\"main\">\n"
"\n"
"<p>Test Page</p>\n"
"\n"
"</div> <!-- main -->\n"
"\n"
"</body>\n"
"</html>\n"
"";
typedef struct {
const char *name;
const char *mimeType;
const uint8_t *data;
size_t dataSize;
unsigned long modified;
bool isBinary;
} FileInfo;
const FileInfo fileInfo[] = {
{"/index.html", "text/html", (const uint8_t *)fileData0, sizeof(fileData0) - 1, 1478627447, FALSE},
{NULL, NULL, 0, 0, FALSE}
};
// [end a87cffa2-e342-4f2c-9070-72b710d606c3]
static const char *dateFormatStr = "%a, %d %b %Y %T %z";
enum State {
FREE_STATE,
READ_REQUEST_STATE,
WRITE_HEADER_STATE,
WRITE_RESPONSE_STATE
};
const int MAX_CLIENTS = 5;
const int LISTEN_PORT = 7123;
const int CLIENT_BUF_SIZE = 1024;
const int MAX_TO_WRITE = 1024;
const unsigned long INACTIVITY_TIMEOUT_MS = 30000;
class ClientConnection {
public:
ClientConnection();
virtual ~ClientConnection();
void loop();
bool accept();
protected:
void clear();
void readRequest();
void generateResponseHeader();
void writeResponse();
private:
uint8_t clientBuf[CLIENT_BUF_SIZE+1];
State state;
int clientId;
TCPClient client;
int readOffset;
int writeOffset;
unsigned long lastUse;
time_t startTime;
// Response data
int responseCode;
String responseStr;
const FileInfo *fileToSend;
const uint8_t *sendBuf;
size_t sendOffset;
size_t sendLen;
};
String localIP;
TCPServer server(LISTEN_PORT);
ClientConnection clients[MAX_CLIENTS];
int nextClientId = 1;
void setup() {
Serial.begin(9600);
waitUntil(Particle.connected);
// From CLI, use something like:
// particle get test5 localip
// to get the IP address of the Photon (replace "test5" with your device name)
localIP = WiFi.localIP(); // localIP must be a global variable
Particle.variable("localip", localIP);
Serial.printlnf("server=%s:%d", localIP.c_str(), LISTEN_PORT);
server.begin();
}
void loop() {
// Handle any existing connections
for(int ii = 0; ii < MAX_CLIENTS; ii++) {
clients[ii].loop();
}
// Accept a new one if there is one waiting (and we have a free client)
for(int ii = 0; ii < MAX_CLIENTS; ii++) {
if (clients[ii].accept()) {
break;
}
}
}
ClientConnection::ClientConnection() : state(FREE_STATE) {
clear();
}
ClientConnection::~ClientConnection() {
}
void ClientConnection::loop() {
if (state == FREE_STATE) {
return;
}
if (client.connected()) {
switch(state) {
case READ_REQUEST_STATE:
readRequest();
break;
case WRITE_HEADER_STATE:
case WRITE_RESPONSE_STATE:
writeResponse();
break;
}
if (millis() - lastUse > INACTIVITY_TIMEOUT_MS) {
Serial.printlnf("%d: inactivity timeout", clientId);
client.stop();
clear();
}
}
else {
Serial.printlnf("%d: client disconnected", clientId);
client.stop();
clear();
}
}
bool ClientConnection::accept() {
if (state != FREE_STATE) {
return false;
}
client = server.available();
if (client.connected()) {
lastUse = millis();
state = READ_REQUEST_STATE;
clientId = nextClientId++;
startTime = Time.now();
Serial.printlnf("%d: connection accepted", clientId);
}
return true;
}
void ClientConnection::clear() {
lastUse = 0;
readOffset = 0;
writeOffset = 0;
state = FREE_STATE;
fileToSend = 0;
}
void ClientConnection::readRequest() {
// Note: client.read returns -1 if there is no data; there is no need to call available(),
// which basically does the same check as the one inside read().
size_t toRead = CLIENT_BUF_SIZE - readOffset;
if (toRead == 0) {
// Didn't get end of header
Serial.printlnf("%d: didn't receive end-of-header", clientId);
client.stop();
return;
}
int count = client.read(&clientBuf[readOffset], toRead);
if (count > 0) {
readOffset += count;
clientBuf[readOffset] = 0;
if (strstr((const char *)clientBuf, "\015\012\015\012")) {
// Ignore the actual request and just return the index.html data
responseCode = 200;
responseStr = "OK";
fileToSend = &fileInfo[0];
Serial.printlnf("%d: sending %s", clientId, fileToSend->name);
generateResponseHeader();
}
lastUse = millis();
}
}
void ClientConnection::generateResponseHeader() {
char *dst = (char *)clientBuf;
char *end = &dst[CLIENT_BUF_SIZE];
// Generate HTTP response header
// HTTP/1.0 200 OK
dst += snprintf(dst, end - dst, "HTTP/1.0 %d %s\r\n", responseCode, responseStr.c_str());
// Date
String s = Time.format(Time.now(), dateFormatStr);
dst += snprintf(dst, end - dst, "Date: %s\r\n", s.c_str());
if (responseCode == 200 && fileToSend) {
// Content-Type
if (fileToSend->mimeType) {
dst += snprintf(dst, end - dst, "Content-Type: %s\r\n", fileToSend->mimeType);
}
// Content-Length is the length if known. contentLength is initialized to -1 (not known)
// but it's good to set it if you know, because not settings a content length means keepalive
// cannot be used.
// For HEAD, Content-Length is the length the body would be, not the actual length (0 for HEAD).
if (fileToSend->dataSize >= 0) {
dst += snprintf(dst, end - dst, "Content-Length: %d\r\n", fileToSend->dataSize);
}
// Last-Modified
if (fileToSend->modified != 0) {
s = Time.format(fileToSend->modified, dateFormatStr);
dst += snprintf(dst, end - dst, "Last-Modified: %s\r\n", s.c_str());
}
}
// End of header
dst += snprintf(dst, end - dst, "\r\n");
// Now send
sendBuf = clientBuf;
sendOffset = 0;
sendLen = dst - (char *)clientBuf;
state = WRITE_HEADER_STATE;
}
void ClientConnection::writeResponse() {
if (sendOffset == sendLen) {
if (state == WRITE_HEADER_STATE && fileToSend) {
// Write body now
sendOffset = 0;
sendBuf = fileToSend->data;
sendLen = fileToSend->dataSize;
}
else {
// Done
Serial.printlnf("%d: send complete", clientId);
client.stop();
return;
}
}
size_t bytesToWrite = sendLen - sendOffset;
if (bytesToWrite >= MAX_TO_WRITE) {
bytesToWrite = MAX_TO_WRITE;
}
int count = client.write(&sendBuf[sendOffset], bytesToWrite);
if (count == -16) {
// Special case on Photon; buffer is full, retry later
}
else
if (count > 0) {
sendOffset += count;
}
else {
Serial.printlnf("%d: error writing %d", clientId, count);
client.stop();
}
}