Active TCPServer / Client appears to crash Photon when WiFi disconnects

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();
	}

}