Linux: Fix Ethernet client and server (#836)

- Fix documentation.
- Add function close() to client class.
- Function connected() now return true if the connection is in
  stablished state or there is still unread data.
- Update hasClient() to check if any client has disconnected.
- Fix _accept() to better handle the case of maximum clients reached.
- Fix peek() to return the next byte of the read queue.
- Simplifies write() of the server class.
- Implements flush().
- Updates read() to non-blocking type, the same way as in Arduino.
This commit is contained in:
Marcelo Aquino
2017-05-13 09:49:16 +02:00
committed by Henrik Ekblad
parent 95f719b604
commit a4c7b249c1
4 changed files with 83 additions and 77 deletions
+33 -22
View File
@@ -19,9 +19,11 @@
* Based on Arduino ethernet library, Copyright (c) 2010 Arduino LLC. All right reserved. * Based on Arduino ethernet library, Copyright (c) 2010 Arduino LLC. All right reserved.
*/ */
#include "EthernetClient.h"
#include <cstdio> #include <cstdio>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <linux/sockios.h>
#include <netdb.h> #include <netdb.h>
#include <arpa/inet.h> #include <arpa/inet.h>
#include <cstring> #include <cstring>
@@ -30,7 +32,6 @@
#include <netinet/tcp.h> #include <netinet/tcp.h>
#include <errno.h> #include <errno.h>
#include "log.h" #include "log.h"
#include "EthernetClient.h"
EthernetClient::EthernetClient() : _sock(-1) EthernetClient::EthernetClient() : _sock(-1)
{ {
@@ -67,7 +68,7 @@ int EthernetClient::connect(const char* host, uint16_t port)
} }
if (::connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) { if (::connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
close(sockfd); ::close(sockfd);
logError("connect: %s\n", strerror(errno)); logError("connect: %s\n", strerror(errno));
continue; continue;
} }
@@ -113,8 +114,7 @@ size_t EthernetClient::write(const uint8_t *buf, size_t size)
int rc = send(_sock, buf + bytes, size, MSG_NOSIGNAL | MSG_DONTWAIT); int rc = send(_sock, buf + bytes, size, MSG_NOSIGNAL | MSG_DONTWAIT);
if (rc == -1) { if (rc == -1) {
logError("send: %s\n", strerror(errno)); logError("send: %s\n", strerror(errno));
close(_sock); close();
_sock = -1;
break; break;
} }
bytes += rc; bytes += rc;
@@ -141,16 +141,16 @@ int EthernetClient::available()
int count = 0; int count = 0;
if (_sock != -1) { if (_sock != -1) {
ioctl(_sock, FIONREAD, &count); ioctl(_sock, SIOCINQ, &count);
return count;
} }
return 0;
return count;
} }
int EthernetClient::read() int EthernetClient::read()
{ {
uint8_t b; uint8_t b;
if ( recv(_sock, &b, 1, 0) > 0 ) { if ( recv(_sock, &b, 1, MSG_DONTWAIT) > 0 ) {
// recv worked // recv worked
return b; return b;
} else { } else {
@@ -159,22 +159,35 @@ int EthernetClient::read()
} }
} }
int EthernetClient::read(uint8_t *buf, size_t size) int EthernetClient::read(uint8_t *buf, size_t bytes)
{ {
return recv(_sock, buf, size, 0); return recv(_sock, buf, bytes, MSG_DONTWAIT);
} }
int EthernetClient::peek() int EthernetClient::peek()
{ {
uint8_t b; uint8_t b;
return recv(_sock, &b, 1, MSG_PEEK | MSG_DONTWAIT); if (recv(_sock, &b, 1, MSG_PEEK | MSG_DONTWAIT) > 0) {
return b;
} else {
return -1;
}
} }
void EthernetClient::flush() void EthernetClient::flush()
{ {
// There isn't much we can do here int count = 0;
if (_sock != -1) {
while (true) {
ioctl(_sock, SIOCOUTQ, &count);
if (count == 0) {
return; return;
}
usleep(1000);
}
}
} }
void EthernetClient::stop() void EthernetClient::stop()
@@ -203,7 +216,7 @@ void EthernetClient::stop()
// if it hasn't closed, close it forcefully // if it hasn't closed, close it forcefully
if (s != ETHERNETCLIENT_W5100_CLOSED) { if (s != ETHERNETCLIENT_W5100_CLOSED) {
close(_sock); ::close(_sock);
} }
_sock = -1; _sock = -1;
} }
@@ -249,17 +262,15 @@ uint8_t EthernetClient::status()
uint8_t EthernetClient::connected() uint8_t EthernetClient::connected()
{ {
if (_sock == -1) { return status() == ETHERNETCLIENT_W5100_ESTABLISHED || available();
return 0; }
}
if (peek() < 0) { void EthernetClient::close()
if (errno == EAGAIN) { {
return 1; if (_sock != -1) {
::close(_sock);
_sock = -1;
} }
return 0;
}
return 1;
} }
int EthernetClient::getSocketNumber() int EthernetClient::getSocketNumber()
+19 -14
View File
@@ -57,7 +57,7 @@ public:
/** /**
* @brief Initiate a connection with host:port. * @brief Initiate a connection with host:port.
* *
* @param host host name to resolve or a stringified dotted IP address. * @param host name to resolve or a stringified dotted IP address.
* @param port to connect to. * @param port to connect to.
* @return 1 if SUCCESS or -1 if FAILURE. * @return 1 if SUCCESS or -1 if FAILURE.
*/ */
@@ -101,7 +101,7 @@ public:
*/ */
size_t write(const char *buffer, size_t size); size_t write(const char *buffer, size_t size);
/** /**
* @brief Checks if new data is available. * @brief Returns the number of bytes available for reading.
* *
* @return number of bytes available. * @return number of bytes available.
*/ */
@@ -109,27 +109,25 @@ public:
/** /**
* @brief Read a byte. * @brief Read a byte.
* *
* @return 0 if FAILURE or 1 if SUCCESS. * @return -1 if no data, else the first byte available.
* @note This function will block (until data becomes available or timeout is reached).
*/ */
virtual int read(); virtual int read();
/** /**
* @brief Read a number of bytes and store in a buffer. * @brief Read a number of bytes and store in a buffer.
* *
* @return -1 if FAILURE or number of read bytes. * @param buf buffer to write to.
* @note This function will block (until data becomes available or timeout is reached). * @param bytes number of bytes to read.
* @return -1 if no data or number of read bytes.
*/ */
virtual int read(uint8_t *buf, size_t size); virtual int read(uint8_t *buf, size_t bytes);
/** /**
* @brief Check if new data are available. * @brief Returns the next byte of the read queue without removing it from the queue.
* *
* @return 0 if no data, else number of bytes available. * @return -1 if no data, else the first byte of incoming data available.
*/ */
virtual int peek(); virtual int peek();
/** /**
* @brief Flushes the network buffers. * @brief Waits until all outgoing bytes in buffer have been sent.
*
* Empty function due to the way TCP works.
*/ */
virtual void flush(); virtual void flush();
/** /**
@@ -145,11 +143,18 @@ public:
*/ */
uint8_t status(); uint8_t status();
/** /**
* @brief Checks whether the socket is alive. * @brief Whether or not the client is connected.
* *
* @return 0 if disconnected or 1 if connected. * Note that a client is considered connected if the connection has been closed but
* there is still unread data.
*
* @return 1 if the client is connected, 0 if not.
*/ */
virtual uint8_t connected(); virtual uint8_t connected();
/**
* @brief Close the connection.
*/
void close();
/** /**
* @brief Get the internal socket file descriptor. * @brief Get the internal socket file descriptor.
* *
+27 -37
View File
@@ -19,6 +19,7 @@
* Based on Arduino ethernet library, Copyright (c) 2010 Arduino LLC. All right reserved. * Based on Arduino ethernet library, Copyright (c) 2010 Arduino LLC. All right reserved.
*/ */
#include "EthernetServer.h"
#include <cstdio> #include <cstdio>
#include <sys/socket.h> #include <sys/socket.h>
#include <cstring> #include <cstring>
@@ -30,7 +31,6 @@
#include <fcntl.h> #include <fcntl.h>
#include "log.h" #include "log.h"
#include "EthernetClient.h" #include "EthernetClient.h"
#include "EthernetServer.h"
EthernetServer::EthernetServer(uint16_t port, uint16_t max_clients) : port(port), EthernetServer::EthernetServer(uint16_t port, uint16_t max_clients) : port(port),
max_clients(max_clients), sockfd(-1) max_clients(max_clients), sockfd(-1)
@@ -108,6 +108,24 @@ void EthernetServer::begin(IPAddress address)
bool EthernetServer::hasClient() bool EthernetServer::hasClient()
{ {
// Check if any client has disconnected
for (size_t i = 0; i < clients.size(); ++i) {
EthernetClient client(clients[i]);
if (!client.connected()) {
// Checks if this disconnected client is also on the new clients list
for (std::list<int>::iterator it = new_clients.begin(); it != new_clients.end(); ++it) {
if (*it == clients[i]) {
new_clients.erase(it);
break;
}
}
client.stop();
clients[i] = clients.back();
clients.pop_back();
logDebug("Ethernet client disconnected.\n");
}
}
_accept(); _accept();
return !new_clients.empty(); return !new_clients.empty();
@@ -136,14 +154,9 @@ size_t EthernetServer::write(const uint8_t *buffer, size_t size)
while (i < clients.size()) { while (i < clients.size()) {
EthernetClient client(clients[i]); EthernetClient client(clients[i]);
if (client.connected()) { if (client.status() == ETHERNETCLIENT_W5100_ESTABLISHED) {
n += client.write(buffer, size); n += client.write(buffer, size);
i++; i++;
} else if (!client.available()) {
client.stop();
clients[i] = clients.back();
clients.pop_back();
logDebug("Client disconnected.\n");
} }
} }
@@ -170,36 +183,6 @@ void EthernetServer::_accept()
struct sockaddr_storage client_addr; struct sockaddr_storage client_addr;
char ipstr[INET_ADDRSTRLEN]; char ipstr[INET_ADDRSTRLEN];
if (new_clients.size() + clients.size() == max_clients) {
// no free slots, search for a dead client
bool no_free_slots = true;
for (size_t i = 0; i < clients.size();) {
EthernetClient client(clients[i]);
if (client.connected() || client.available()) {
i++;
} else {
clients[i] = clients.back();
clients.pop_back();
no_free_slots = false;
break;
}
}
if (no_free_slots) {
for (std::list<int>::iterator it = new_clients.begin(); it != new_clients.end(); ++it) {
EthernetClient client(*it);
if (!client.connected() && !client.available()) {
new_clients.erase(it);
no_free_slots = false;
break;
}
}
}
if (no_free_slots) {
logDebug("Max number of ethernet clients reached.\n");
return;
}
}
sin_size = sizeof client_addr; sin_size = sizeof client_addr;
new_fd = accept(sockfd, (struct sockaddr *)&client_addr, &sin_size); new_fd = accept(sockfd, (struct sockaddr *)&client_addr, &sin_size);
if (new_fd == -1) { if (new_fd == -1) {
@@ -209,6 +192,13 @@ void EthernetServer::_accept()
return; return;
} }
if (clients.size() == max_clients) {
// no free slots, search for a dead client
close(new_fd);
logDebug("Max number of ethernet clients reached.\n");
return;
}
new_clients.push_back(new_fd); new_clients.push_back(new_fd);
clients.push_back(new_fd); clients.push_back(new_fd);
+3 -3
View File
@@ -70,7 +70,7 @@ public:
/** /**
* @brief Get the new connected client. * @brief Get the new connected client.
* *
* @return client class object if a new client has connected else -1. * @return a EthernetClient object; if no new client has connected, this object will evaluate to false.
*/ */
EthernetClient available(); EthernetClient available();
/** /**
@@ -106,8 +106,8 @@ public:
private: private:
uint16_t port; //!< @brief Port number for the network socket. uint16_t port; //!< @brief Port number for the network socket.
std::list<int> new_clients; //!< Socket list of new clients. std::list<int> new_clients; //!< Socket list of new connected clients.
std::vector<int> clients; //!< @brief Socket list of clients. std::vector<int> clients; //!< @brief Socket list of connected clients.
uint16_t max_clients; //!< @brief The maximum number of allowed clients. uint16_t max_clients; //!< @brief The maximum number of allowed clients.
int sockfd; //!< @brief Network socket used to accept connections. int sockfd; //!< @brief Network socket used to accept connections.