From b5dbeccdf5df95791ba0022fb470af03fb253d5d Mon Sep 17 00:00:00 2001 From: Markus Sattler Date: Sat, 23 May 2015 23:51:32 +0200 Subject: [PATCH] first steps to Client --- src/WebSockets.cpp | 5 + src/WebSockets.h | 8 + src/WebSocketsClient.cpp | 320 +++++++++++++++++++++++++++++++++++++++ src/WebSocketsClient.h | 56 +++++++ src/WebSocketsServer.cpp | 6 +- src/WebSocketsServer.h | 9 -- 6 files changed, 394 insertions(+), 10 deletions(-) diff --git a/src/WebSockets.cpp b/src/WebSockets.cpp index 9497ef6..021997c 100644 --- a/src/WebSockets.cpp +++ b/src/WebSockets.cpp @@ -38,6 +38,7 @@ extern "C" { * @param reasonLen */ void WebSockets::clientDisconnect(WSclient_t * client, uint16_t code, char * reason, size_t reasonLen) { + DEBUG_WEBSOCKETS("[WS-Server][%d][handleWebsocket] clientDisconnect code: %u\n", client->num, code); if(client->status == WSC_CONNECTED && code) { if(reason) { sendFrame(client, WSop_close, (uint8_t *) reason, reasonLen); @@ -60,6 +61,10 @@ void WebSockets::clientDisconnect(WSclient_t * client, uint16_t code, char * rea */ void WebSockets::sendFrame(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length) { + if(!client->tcp.connected()) { + return; + } + uint8_t buffer[16] = { 0 }; uint8_t i = 0; diff --git a/src/WebSockets.h b/src/WebSockets.h index 0b243a1..c57f262 100644 --- a/src/WebSockets.h +++ b/src/WebSockets.h @@ -52,6 +52,14 @@ typedef enum { WSC_CONNECTED } WSclientsStatus_t; +typedef enum { + WStype_ERROR, + WStype_DISCONNECTED, + WStype_CONNECTED, + WStype_TEXT, + WStype_BIN +} WStype_t; + typedef enum { WSop_continuation = 0x00, ///< %x0 denotes a continuation frame WSop_text = 0x01, ///< %x1 denotes a text frame diff --git a/src/WebSocketsClient.cpp b/src/WebSocketsClient.cpp index 053f6ca..ef84074 100644 --- a/src/WebSocketsClient.cpp +++ b/src/WebSocketsClient.cpp @@ -25,3 +25,323 @@ #include "WebSockets.h" #include "WebSocketsClient.h" +WebSocketsClient::WebSocketsClient() { + _cbEvent = NULL; + _client.num = 0; + +} + +WebSocketsClient::~WebSocketsClient() { + disconnect(); +} + +/** + * calles to init the Websockets server + */ +void WebSocketsClient::begin(const char *host, uint16_t port, const char * url) { + _host = host; + _port = port; + + _client.status = WSC_NOT_CONNECTED; + _client.cUrl = url; + _client.cIsUpgrade = false; + _client.cIsWebsocket = true; + _client.cKey = ""; + _client.cProtocol = ""; + _client.cExtensions = ""; + _client.cVersion = 0; +} + +void WebSocketsClient::begin(String host, uint16_t port, String url) { + begin(host.c_str(), port, url.c_str()); +} + +/** + * called in arduino loop + */ +void WebSocketsClient::loop(void) { + if(!clientIsConnected(&_client)) { + if(_client.tcp.connect(_host.c_str(), _port)) { + DEBUG_WEBSOCKETS("[WS-Client] connected to %s:%u\n", _host.c_str(), _port); + + _client.tcp.setNoDelay(true); + // set Timeout for readBytesUntil and readStringUntil + _client.tcp.setTimeout(WEBSOCKETS_TCP_TIMEOUT); + _client.status = WSC_HEADER; + } else { + DEBUG_WEBSOCKETS("[WS-Client] connection to %s:%u Faild\n", _host.c_str(), _port); + delay(10); //some litle delay to not flood the server + } + } else { + handleClientData(); + } +} + +/** + * set callback function + * @param cbEvent WebSocketServerEvent + */ +void WebSocketsClient::onEvent(WebSocketClientEvent cbEvent) { + _cbEvent = cbEvent; +} + +/** + * send text data to client + * @param num uint8_t client id + * @param payload uint8_t * + * @param length size_t + */ +void WebSocketsClient::sendTXT(uint8_t * payload, size_t length) { + if(length == 0) { + length = strlen((const char *) payload); + } + if(clientIsConnected(&_client)) { + sendFrame(&_client, WSop_text, payload, length); + } +} + +void WebSocketsClient::sendTXT(const uint8_t * payload, size_t length) { + sendTXT((uint8_t *) payload, length); +} + +void WebSocketsClient::sendTXT(char * payload, size_t length) { + sendTXT((uint8_t *) payload, length); +} + +void WebSocketsClient::sendTXT(const char * payload, size_t length) { + sendTXT((uint8_t *) payload, length); +} + +void WebSocketsClient::sendTXT(String payload) { + sendTXT((uint8_t *) payload.c_str(), payload.length()); +} + +/** + * send binary data to client + * @param num uint8_t client id + * @param payload uint8_t * + * @param length size_t + */ +void WebSocketsClient::sendBIN(uint8_t * payload, size_t length) { + if(clientIsConnected(&_client)) { + sendFrame(&_client, WSop_binary, payload, length); + } +} + +void WebSocketsClient::sendBIN(const uint8_t * payload, size_t length) { + sendBIN((uint8_t *) payload, length); +} + +/** + * disconnect one client + * @param num uint8_t client id + */ +void WebSocketsClient::disconnect(void) { + if(clientIsConnected(&_client)) { + WebSockets::clientDisconnect(&_client, 1000); + } +} + +//################################################################################# +//################################################################################# +//################################################################################# + +/** + * + * @param client WSclient_t * ptr to the client struct + * @param opcode WSopcode_t + * @param payload uint8_t * + * @param lenght size_t + */ +void WebSocketsClient::messageRecived(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t lenght) { + WStype_t type = WStype_ERROR; + + switch(opcode) { + case WSop_text: + type = WStype_TEXT; + break; + case WSop_binary: + type = WStype_BIN; + break; + } + + if(_cbEvent) { + _cbEvent(type, payload, lenght); + } +} + +/** + * Disconnect an client + * @param client WSclient_t * ptr to the client struct + */ +void WebSocketsClient::clientDisconnect(WSclient_t * client) { + + if(client->tcp) { + client->tcp.flush(); + client->tcp.stop(); + } + + client->cKey = ""; + client->cProtocol = ""; + client->cVersion = 0; + client->cIsUpgrade = false; + client->cIsWebsocket = false; + + client->status = WSC_NOT_CONNECTED; + + DEBUG_WEBSOCKETS("[WS-Client] client disconnected.\n"); + + if(_cbEvent) { + _cbEvent(WStype_DISCONNECTED, NULL, 0); + } +} + +/** + * get client state + * @param client WSclient_t * ptr to the client struct + * @return true = conneted + */ +bool WebSocketsClient::clientIsConnected(WSclient_t * client) { + + if(client->status != WSC_NOT_CONNECTED && client->tcp.connected()) { + return true; + } + + if(client->status != WSC_NOT_CONNECTED) { + // cleanup + clientDisconnect(&_client); + } + return false; +} + +/** + * Handel incomming data from Client + */ +void WebSocketsClient::handleClientData(void) { + int len = _client.tcp.available(); + if(len > 0) { + switch(_client.status) { + case WSC_HEADER: + handleHeader(&_client); + break; + case WSC_CONNECTED: + WebSockets::handleWebsocket(&_client); + break; + default: + WebSockets::clientDisconnect(&_client, 1002); + break; + } + } + delay(0); +} + +/** + * handle the WebSocket header reading + * @param client WSclient_t * ptr to the client struct + */ +void WebSocketsClient::handleHeader(WSclient_t * client) { + + String headerLine = client->tcp.readStringUntil('\n'); + headerLine.trim(); // remove \r + + if(headerLine.length() > 0) { + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] RX: %s\n", headerLine.c_str()); + + // websocket request starts allways with GET see rfc6455 + if(headerLine.startsWith("GET ")) { + // cut URL out + client->cUrl = headerLine.substring(4, headerLine.indexOf(' ', 4)); + } else if(headerLine == "Connection: Upgrade") { + client->cIsUpgrade = true; + } else if(headerLine == "Upgrade: websocket") { + client->cIsWebsocket = true; + } else if(headerLine.startsWith("Sec-WebSocket-Version: ")) { + // 23 = lenght of "Sec-WebSocket-Version: " + client->cVersion = headerLine.substring(23).toInt(); + } else if(headerLine.startsWith("Sec-WebSocket-Key: ")) { + // 19 = lenght of "Sec-WebSocket-Key: " + client->cKey = headerLine.substring(19); + client->cKey.trim(); // see rfc6455 + } else if(headerLine.startsWith("Sec-WebSocket-Protocol: ")) { + // 24 = lenght of "Sec-WebSocket-Protocol: " + client->cProtocol = headerLine.substring(24); + } else if(headerLine.startsWith("Sec-WebSocket-Extensions: ")) { + // 26 = lenght of "Sec-WebSocket-Extensions: " + client->cExtensions = headerLine.substring(26); + } + + } else { + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Header read fin.\n"); + + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cURL: %s\n", client->cUrl.c_str()); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cIsUpgrade: %d\n", client->cIsUpgrade); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cIsWebsocket: %d\n", client->cIsWebsocket); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cKey: %s\n", client->cKey.c_str()); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cProtocol: %s\n", client->cProtocol.c_str()); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cExtensions: %s\n", client->cExtensions.c_str()); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cVersion: %d\n", client->cVersion); + + bool ok = (client->cIsUpgrade && client->cIsWebsocket); + + if(ok) { + if(client->cUrl.length() == 0) { + ok = false; + } + if(client->cKey.length() == 0) { + ok = false; + } + if(client->cVersion != 13) { + ok = false; + } + } + + if(ok) { + + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Websocket connection incomming.\n"); + + // generate Sec-WebSocket-Accept key + String sKey = acceptKey(client->cKey); + + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - sKey: %s\n", sKey.c_str()); + + client->status = WSC_CONNECTED; + + client->tcp.write("HTTP/1.1 101 Switching Protocols\r\n" + "Server: ESP8266-WebSocketsClient\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Version: 13\r\n" + "Sec-WebSocket-Accept: "); + client->tcp.write(sKey.c_str(), sKey.length()); + client->tcp.write("\r\n"); + + if(client->cProtocol.length() > 0) { + // todo add api to set Protocol of Server + client->tcp.write("Sec-WebSocket-Protocol: esp8266\r\n"); + } + + // header end + client->tcp.write("\r\n"); + + // send ping + WebSockets::sendFrame(client, WSop_ping); + + if(_cbEvent) { + _cbEvent(WStype_CONNECTED, (uint8_t *) client->cUrl.c_str(), client->cUrl.length()); + } + + } else { + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] no Websocket connection close.\n"); + client->tcp.write("HTTP/1.1 400 Bad Request\r\n" + "Server: ESP8266-WebSocketsClient\r\n" + "Content-Type: text/plain\r\n" + "Content-Length: 32\r\n" + "Connection: close\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n" + "This is a Websocket server only!"); + clientDisconnect(client); + } + } +} + diff --git a/src/WebSocketsClient.h b/src/WebSocketsClient.h index c4704bd..970119a 100644 --- a/src/WebSocketsClient.h +++ b/src/WebSocketsClient.h @@ -25,8 +25,64 @@ #ifndef WEBSOCKETSCLIENT_H_ #define WEBSOCKETSCLIENT_H_ +#include +#ifdef ESP8266 +#include +#else +#include +#ifndef UIPETHERNET_H +#include +#include +#endif +#endif +#include "WebSockets.h" +class WebSocketsClient: private WebSockets { + public: + + typedef void (*WebSocketClientEvent)(WStype_t type, uint8_t * payload, size_t length); + + WebSocketsClient(void); + ~WebSocketsClient(void); + + void begin(const char *host, uint16_t port, const char * url); + void begin(String host, uint16_t port, String url); + + void loop(void); + + void onEvent(WebSocketClientEvent cbEvent); + + void sendTXT(uint8_t * payload, size_t length = 0); + void sendTXT(const uint8_t * payload, size_t length = 0); + void sendTXT(char * payload, size_t length = 0); + void sendTXT(const char * payload, size_t length = 0); + void sendTXT(String payload); + + void sendBIN(uint8_t * payload, size_t length); + void sendBIN(const uint8_t * payload, size_t length); + + void disconnect(void); + + private: + String _host; + uint16_t _port; + + WSclient_t _client; + + WebSocketClientEvent _cbEvent; + + void messageRecived(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length); + + void clientDisconnect(WSclient_t * client); + bool clientIsConnected(WSclient_t * client); + + void handleNewClients(void); + void handleClientData(void); + + void handleHeader(WSclient_t * client); + +}; #endif /* WEBSOCKETSCLIENT_H_ */ diff --git a/src/WebSocketsServer.cpp b/src/WebSocketsServer.cpp index 7045fa2..080f4d1 100644 --- a/src/WebSocketsServer.cpp +++ b/src/WebSocketsServer.cpp @@ -74,7 +74,7 @@ void WebSocketsServer::loop(void) { * @param cbEvent WebSocketServerEvent */ void WebSocketsServer::onEvent(WebSocketServerEvent cbEvent) { - WebSocketsServer::_cbEvent = cbEvent; + _cbEvent = cbEvent; } /** @@ -122,11 +122,13 @@ void WebSocketsServer::broadcastTXT(uint8_t * payload, size_t length) { if(length == 0) { length = strlen((const char *) payload); } + for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) { client = &_clients[i]; if(clientIsConnected(client)) { sendFrame(client, WSop_text, payload, length); } + delay(0); } } @@ -178,6 +180,7 @@ void WebSocketsServer::broadcastBIN(uint8_t * payload, size_t length) { if(clientIsConnected(client)) { sendFrame(client, WSop_binary, payload, length); } + delay(0); } } @@ -263,6 +266,7 @@ void WebSocketsServer::messageRecived(WSclient_t * client, WSopcode_t opcode, ui void WebSocketsServer::clientDisconnect(WSclient_t * client) { if(client->tcp) { + client->tcp.flush(); client->tcp.stop(); } diff --git a/src/WebSocketsServer.h b/src/WebSocketsServer.h index b24afb4..48259d2 100644 --- a/src/WebSocketsServer.h +++ b/src/WebSocketsServer.h @@ -42,13 +42,6 @@ #define WEBSOCKETS_SERVER_CLIENT_MAX (5) -typedef enum { - WStype_ERROR, - WStype_DISCONNECTED, - WStype_CONNECTED, - WStype_TEXT, - WStype_BIN -} WStype_t; class WebSocketsServer: private WebSockets { @@ -106,7 +99,6 @@ private: WebSocketServerEvent _cbEvent; void messageRecived(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length); - void clientConnected(WSclient_t * client); void clientDisconnect(WSclient_t * client); bool clientIsConnected(WSclient_t * client); @@ -115,7 +107,6 @@ private: void handleClientData(void); void handleHeader(WSclient_t * client); - void handleWebsocket(WSclient_t * client); };