diff --git a/lib/socketserver/GwSocketServer.cpp b/lib/socketserver/GwSocketServer.cpp index d30e7b2..c53a5ee 100644 --- a/lib/socketserver/GwSocketServer.cpp +++ b/lib/socketserver/GwSocketServer.cpp @@ -4,65 +4,6 @@ #include "GwBuffer.h" #include "GwSocketConnection.h" -class GwTcpClient -{ - GwSocketConnection *gwClient = NULL; - IPAddress remoteAddress; - uint16_t port = 0; - GwLog *logger; - -public: - typedef enum - { - C_DISABLED = 0, - C_INITIALIZED = 1, - C_CONNECTING = 2, - C_CONNECTED = 3 - } State; - -private: - State state = C_DISABLED; - void stop() - { - if (gwClient->hasClient()) - { - LOG_DEBUG(GwLog::DEBUG, "stopping tcp client"); - gwClient->stop(); - } - state = C_DISABLED; - } - void startConnection() - { - //TODO - state = C_CONNECTING; - } - void checkConnection() - { - } - -public: - GwTcpClient(GwLog *logger, GwSocketConnection *gwClient) - { - this->logger = logger; - this->gwClient = gwClient; - } - void begin(IPAddress address, uint16_t port) - { - stop(); - this->remoteAddress = address; - this->port = port; - state = C_INITIALIZED; - startConnection(); - } - void loop() - { - if (state == C_CONNECTING) - { - checkConnection(); - } - } -}; - GwSocketServer::GwSocketServer(const GwConfigHandler *config, GwLog *logger, int minId) { this->config = config; diff --git a/lib/socketserver/GwTcpClient.cpp b/lib/socketserver/GwTcpClient.cpp new file mode 100644 index 0000000..44c2d1e --- /dev/null +++ b/lib/socketserver/GwTcpClient.cpp @@ -0,0 +1,164 @@ +#include "GwTcpClient.h" + +bool GwTcpClient::hasConfig(){ + return configured; +} +bool GwTcpClient::isConnected(){ + return state == C_CONNECTED; +} +void GwTcpClient::stop() +{ + if (connection->hasClient()) + { + LOG_DEBUG(GwLog::DEBUG, "stopping tcp client"); + connection->stop(); + } + state = C_DISABLED; +} +void GwTcpClient::startConnection() +{ + //TODO + state = C_INITIALIZED; + connectStart=millis(); + int sockfd = socket(AF_INET, SOCK_STREAM, 0); + if (sockfd < 0) { + LOG_DEBUG(GwLog::ERROR,"unable to create socket: %d", errno); + return; + } + fcntl( sockfd, F_SETFL, fcntl( sockfd, F_GETFL, 0 ) | O_NONBLOCK ); + + uint32_t ip_addr = this->remoteAddress; + struct sockaddr_in serveraddr; + memset((char *) &serveraddr, 0, sizeof(serveraddr)); + serveraddr.sin_family = AF_INET; + memcpy((void *)&serveraddr.sin_addr.s_addr, (const void *)(&ip_addr), 4); + serveraddr.sin_port = htons(port); + int res = lwip_connect_r(sockfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)); + if (res < 0 ) { + if (errno != EINPROGRESS){ + LOG_DEBUG(GwLog::ERROR,"connect on fd %d, errno: %d, \"%s\"", sockfd, errno, strerror(errno)); + close(sockfd); + return; + } + state=C_CONNECTING; + connection->setClient(sockfd); + } + else{ + state=C_CONNECTED; + connection->setClient(sockfd); + } +} +void GwTcpClient::checkConnection() +{ + unsigned long now=millis(); + if (! connection->hasClient()){ + state = hasConfig()?C_INITIALIZED:C_DISABLED; + } + if (state == C_INITIALIZED){ + if ((now - connectStart) > CON_TIMEOUT){ + LOG_DEBUG(GwLog::LOG,"retry connect to %s",remoteAddress.toString().c_str()); + startConnection(); + } + return; + } + if (state != C_CONNECTING){ + return; + } + fd_set fdset; + struct timeval tv; + FD_ZERO(&fdset); + int sockfd=connection->fd; + FD_SET(connection->fd, &fdset); + tv.tv_sec = 0; + tv.tv_usec = 0; + int res = select(sockfd + 1, nullptr, &fdset, nullptr, &tv); + if (res < 0) { + LOG_DEBUG(GwLog::ERROR,"select on fd %d, errno: %d, \"%s\"", sockfd, errno, strerror(errno)); + connection->stop(); + return; + } else if (res == 0) { + //still connecting + if ((now - connectStart) >= CON_TIMEOUT){ + LOG_DEBUG(GwLog::ERROR,"connect timeout to %s, retry",remoteAddress.toString().c_str()); + connection->stop(); + return; + } + } else { + int sockerr; + socklen_t len = (socklen_t)sizeof(int); + res = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &sockerr, &len); + + if (res < 0) { + LOG_DEBUG(GwLog::ERROR,"getsockopt on fd %d, errno: %d, \"%s\"", sockfd, errno, strerror(errno)); + connection->stop(); + return; + } + if (sockerr != 0) { + LOG_DEBUG(GwLog::ERROR,"socket error on fd %d, errno: %d, \"%s\"", sockfd, sockerr, strerror(sockerr)); + connection->stop(); + return; + } + } + LOG_DEBUG(GwLog::LOG,"connected to %s",remoteAddress.toString().c_str()); + state=C_CONNECTED; +} + +GwTcpClient::GwTcpClient(GwLog *logger) +{ + this->logger = logger; +} +GwTcpClient::~GwTcpClient(){ + if (connection) + delete connection; +} +void GwTcpClient::begin(int sourceId,IPAddress address, uint16_t port,bool allowRead) +{ + stop(); + this->sourceId=sourceId; + this->remoteAddress = address; + this->port = port; + configured=true; + state = C_INITIALIZED; + this->connection = new GwSocketConnection(logger,0,allowRead); + startConnection(); +} +void GwTcpClient::loop(bool handleRead,bool handleWrite) +{ + checkConnection(); + if (state != C_CONNECTED){ + return; + } + if (handleRead){ + if (connection->hasClient()){ + if (! connection->connected()){ + LOG_DEBUG(GwLog::ERROR,"connection closed on %s",connection->remoteIpAddress.c_str()); + connection->stop(); + } + else{ + connection->read(); + } + } + } + if (handleWrite){ + if (connection->hasClient()){ + GwBuffer::WriteStatus rt = connection->write(); + if (rt == GwBuffer::ERROR) + { + LOG_DEBUG(GwLog::ERROR, "write error on %s, closing", connection->remoteIpAddress.c_str()); + connection->stop(); + } + } + } +} + +void GwTcpClient::sendToClients(const char *buf,int sourceId){ + if (sourceId == this->sourceId) return; + if (state != C_CONNECTED) return; + if (! connection->hasClient()) return; + connection->enqueue((uint8_t*)buf,strlen(buf)); +} +bool GwTcpClient::readMessages(GwMessageFetcher *writer){ + if (state != C_CONNECTED) return false; + if (! connection->hasClient()) return false; + return connection->messagesFromBuffer(writer); +} \ No newline at end of file diff --git a/lib/socketserver/GwTcpClient.h b/lib/socketserver/GwTcpClient.h new file mode 100644 index 0000000..36f017c --- /dev/null +++ b/lib/socketserver/GwTcpClient.h @@ -0,0 +1,38 @@ +#pragma once +#include "GwSocketConnection.h" +class GwTcpClient +{ + static const unsigned long CON_TIMEOUT=10; + GwSocketConnection *connection = NULL; + IPAddress remoteAddress; + uint16_t port = 0; + unsigned long connectStart=0; + GwLog *logger; + int sourceId; + bool configured=false; + +public: + typedef enum + { + C_DISABLED = 0, + C_INITIALIZED = 1, + C_CONNECTING = 2, + C_CONNECTED = 3 + } State; + +private: + State state = C_DISABLED; + void stop(); + void startConnection(); + void checkConnection(); + bool hasConfig(); + +public: + GwTcpClient(GwLog *logger); + ~GwTcpClient(); + void begin(int sourceId,IPAddress address, uint16_t port,bool allowRead); + void loop(bool handleRead=true,bool handleWrite=true); + void sendToClients(const char *buf,int sourceId); + bool readMessages(GwMessageFetcher *writer); + bool isConnected(); +}; \ No newline at end of file diff --git a/web/config.json b/web/config.json index 3c3cb17..2e087a9 100644 --- a/web/config.json +++ b/web/config.json @@ -376,7 +376,7 @@ "type": "number", "default": "10110", "description": "the TCP port we listen on", - "category": "TCP port" + "category": "TCP server" }, { "name": "maxClients", @@ -387,7 +387,7 @@ "min": 0, "max": 6, "description": "the number of clients we allow to connect to us", - "category": "TCP port" + "category": "TCP server" }, { "name": "sendTCP", @@ -395,7 +395,7 @@ "type": "boolean", "default": "true", "description": "send out NMEA data to connected TCP clients", - "category": "TCP port" + "category": "TCP server" }, { "name": "readTCP", @@ -403,7 +403,7 @@ "type": "boolean", "default": "true", "description": "receive NMEA data from connected TCP clients", - "category": "TCP port" + "category": "TCP server" }, { "name": "tcpToN2k", @@ -411,7 +411,7 @@ "type": "boolean", "default": "true", "description": "convert NMEA0183 from TCP clients to NMEA2000", - "category": "TCP port" + "category": "TCP server" }, { "name": "tcpReadFilter", @@ -419,7 +419,7 @@ "type": "filter", "default": "", "description": "filter for NMEA0183 data when reading from TCP\nselect aison|aisoff, set a whitelist or a blacklist with NMEA sentences like RMC,RMB", - "category": "TCP port" + "category": "TCP server" }, { "name": "tcpWriteFilter", @@ -427,7 +427,7 @@ "type": "filter", "default": "", "description": "filter for NMEA0183 data when writing to TCP\nselect aison|aisoff, set a whitelist or a blacklist with NMEA sentences like RMC,RMB", - "category": "TCP port" + "category": "TCP server" }, { "name": "sendSeasmart", @@ -435,7 +435,80 @@ "type": "boolean", "default": "false", "description": "send NMEA2000 as seasmart to connected TCP clients", - "category": "TCP port" + "category": "TCP server" + }, + { + "name": "tclEnabled", + "label": "enable TCP client", + "type": "boolean", + "default": "false", + "description":"enable the TCP client", + "category":"TCP client" + }, + { + "name": "remotePort", + "label": "remote port", + "type": "number", + "default": "10110", + "description": "the TCP port we connect to", + "category": "TCP client" + }, + { + "name": "remoteAddress", + "label": "remote address", + "type": "string", + "default": "", + "check": "checkIpAddress", + "description": "the IP address we connect to", + "category": "TCP client" + }, + { + "name": "sendTCL", + "label": "NMEA to TCP client", + "type": "boolean", + "default": "true", + "description": "send out NMEA data to remote TCP server", + "category": "TCP client" + }, + { + "name": "readTCL", + "label": "NMEA from TCP client", + "type": "boolean", + "default": "true", + "description": "receive NMEA data from remote TCP server", + "category": "TCP client" + }, + { + "name": "tclToN2k", + "label": "TCP client to NMEA2000", + "type": "boolean", + "default": "true", + "description": "convert NMEA0183 from remote TCP server to NMEA2000", + "category": "TCP client" + }, + { + "name": "tclReadFilter", + "label": "TCP client read Filter", + "type": "filter", + "default": "", + "description": "filter for NMEA0183 data when reading from remote TCP server\nselect aison|aisoff, set a whitelist or a blacklist with NMEA sentences like RMC,RMB", + "category": "TCP client" + }, + { + "name": "tclWriteFilter", + "label": "TCP client write Filter", + "type": "filter", + "default": "", + "description": "filter for NMEA0183 data when writing to remote TCP server\nselect aison|aisoff, set a whitelist or a blacklist with NMEA sentences like RMC,RMB", + "category": "TCP client" + }, + { + "name": "tclSeasmart", + "label": "Seasmart to TCP client", + "type": "boolean", + "default": "false", + "description": "send NMEA2000 as seasmart to remote TCP server", + "category": "TCP client" }, { "name": "wifiClient",