Skip to content
126 changes: 126 additions & 0 deletions libraries/Ethernet/examples/AdvancedChatServer/AdvancedChatServer.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*
Advanced Chat Server

A more advanced server that distributes any incoming messages
to all connected clients but the client the message comes from.
To use, telnet to your device's IP address and type.

Usage:
1. Upload this sketch to your board.
2. Make sure your board is connected to the network and note its IP address.
3. From a computer on the same network, open a terminal and connect via Telnet:

- On macOS or Linux (using netcat if telnet is not available):
telnet <board_ip> 23
# or, if telnet is missing:
nc <board_ip> 23

- On Windows (Command Prompt):
telnet <board_ip> 23
# If 'telnet' is not recognized, enable it in "Windows Features".

4. Type a message and press Enter.
Your message will be broadcast to all connected clients except you.

Example:
telnet 192.168.1.177 23

Press CTRL + ] then 'quit' to exit Telnet.

*/

#include "ZephyrServer.h"
#include "ZephyrClient.h"
#include "ZephyrEthernet.h"

// The IP address will be dependent on your local network.
// gateway and subnet are optional:
IPAddress ip(192, 168, 1, 177);
IPAddress myDns(192, 168, 1, 1);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);


// telnet defaults to port 23
ZephyrServer server(23);

ZephyrClient clients[8];

void setup() {
// start serial port:
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}

// in Zephyr system check if Ethernet is ready before proceeding to initialize
Serial.print("Waiting for link on");
while (Ethernet.linkStatus() != LinkON) {
Serial.print(".");
delay(100);
}
Serial.println();

// initialize the Ethernet device
Ethernet.begin(ip, myDns, gateway, subnet);

// Check for Ethernet hardware present
if (Ethernet.hardwareStatus() == EthernetNoHardware) {
Serial.println("Ethernet shield was not found. Sorry, can't run without hardware. :(");
while (true) {
delay(1); // do nothing, no point running without Ethernet hardware
}
}
if (Ethernet.linkStatus() == LinkOFF) {
Serial.println("Ethernet cable is not connected.");
}

// start listening for clients
server.begin();

Serial.print("Chat server address:");
Serial.println(Ethernet.localIP());
}

void loop() {
// check for any new client connecting, and say hello (before any incoming data)
ZephyrClient newClient = server.accept();
if (newClient) {
for (byte i=0; i < 8; i++) {
if (!clients[i]) {
Serial.print("We have a new client #");
Serial.println(i);
newClient.print("Hello, client number: ");
newClient.println(i);
// Once we "accept", the client is no longer tracked by EthernetServer
// so we must store it into our list of clients
clients[i] = newClient;
break;
}
}
}

// check for incoming data from all clients
for (byte i=0; i < 8; i++) {
if (clients[i] && clients[i].available() > 0) {
// read bytes from a client
byte buffer[80];
int count = clients[i].read(buffer, 80);
// write the bytes to all other connected clients
for (byte j=0; j < 8; j++) {
if (j != i && clients[j].connected()) {
clients[j].write(buffer, count);
}
}
}
}

// stop any clients which disconnect
for (byte i=0; i < 8; i++) {
if (clients[i] && !clients[i].connected()) {
Serial.print("disconnect client #");
Serial.println(i);
clients[i].stop();
}
}
}
118 changes: 66 additions & 52 deletions libraries/SocketWrapper/SocketWrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,39 @@
#endif

#include <zephyr/net/socket.h>
#include <memory>
#include <cstring>

class ZephyrSocketWrapper {
protected:
int sock_fd;
std::shared_ptr<int> sock_fd;
bool is_ssl = false;
int ssl_sock_temp_char = -1;

public:
ZephyrSocketWrapper() : sock_fd(-1) {
// custom deleter for shared_ptr to close automatically the socket
static void socket_deleter(int *fd) {
if (fd && *fd != -1) {
::close(*fd);
}
delete fd;
}

ZephyrSocketWrapper(int sock_fd) : sock_fd(sock_fd) {
}
public:
ZephyrSocketWrapper() = default;

~ZephyrSocketWrapper() {
if (sock_fd != -1) {
::close(sock_fd);
}
ZephyrSocketWrapper(int fd)
: sock_fd(std::shared_ptr<int>(fd < 0 ? nullptr : new int(fd), socket_deleter)) {
}

~ZephyrSocketWrapper() = default; // socket close managed by shared_ptr

bool connect(const char *host, uint16_t port) {

// Resolve address
struct addrinfo hints = {0};
struct addrinfo *res = nullptr;
bool rv = true;
int raw_sock_fd;

hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
Expand All @@ -55,16 +62,17 @@ class ZephyrSocketWrapper {
goto exit;
}

sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock_fd < 0) {
raw_sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
sock_fd =
std::shared_ptr<int>(raw_sock_fd < 0 ? nullptr : new int(raw_sock_fd), socket_deleter);
if (!sock_fd) {
rv = false;

goto exit;
}

if (::connect(sock_fd, res->ai_addr, res->ai_addrlen) < 0) {
::close(sock_fd);
sock_fd = -1;
if (::connect(*sock_fd, res->ai_addr, res->ai_addrlen) < 0) {
sock_fd = nullptr;
rv = false;
goto exit;
}
Expand All @@ -81,20 +89,22 @@ class ZephyrSocketWrapper {
bool connect(IPAddress host, uint16_t port) {

const char *_host = host.toString().c_str();
int raw_sock_fd;

struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
inet_pton(AF_INET, _host, &addr.sin_addr);

sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock_fd < 0) {
raw_sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
sock_fd =
std::shared_ptr<int>(raw_sock_fd < 0 ? nullptr : new int(raw_sock_fd), socket_deleter);
if (!sock_fd) {
return false;
}

if (::connect(sock_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
::close(sock_fd);
sock_fd = -1;
if (::connect(*sock_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
sock_fd = nullptr;
return false;
}

Expand All @@ -114,6 +124,7 @@ class ZephyrSocketWrapper {
int resolve_attempts = 100;
int ret;
bool rv = false;
int raw_sock_fd;

sec_tag_t sec_tag_opt[] = {
CA_CERTIFICATE_TAG,
Expand Down Expand Up @@ -146,18 +157,20 @@ class ZephyrSocketWrapper {
}
}

sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TLS_1_2);
if (sock_fd < 0) {
raw_sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TLS_1_2);
sock_fd =
std::shared_ptr<int>(raw_sock_fd < 0 ? nullptr : new int(raw_sock_fd), socket_deleter);
if (!sock_fd) {
goto exit;
}

if (setsockopt(sock_fd, SOL_TLS, TLS_HOSTNAME, host, strlen(host)) ||
setsockopt(sock_fd, SOL_TLS, TLS_SEC_TAG_LIST, sec_tag_opt, sizeof(sec_tag_opt)) ||
setsockopt(sock_fd, SOL_SOCKET, SO_RCVTIMEO, &timeout_opt, sizeof(timeout_opt))) {
if (setsockopt(*sock_fd, SOL_TLS, TLS_HOSTNAME, host, strlen(host)) ||
setsockopt(*sock_fd, SOL_TLS, TLS_SEC_TAG_LIST, sec_tag_opt, sizeof(sec_tag_opt)) ||
setsockopt(*sock_fd, SOL_SOCKET, SO_RCVTIMEO, &timeout_opt, sizeof(timeout_opt))) {
goto exit;
}

if (::connect(sock_fd, res->ai_addr, res->ai_addrlen) < 0) {
if (::connect(*sock_fd, res->ai_addr, res->ai_addrlen) < 0) {
goto exit;
}

Expand All @@ -170,9 +183,8 @@ class ZephyrSocketWrapper {
res = nullptr;
}

if (!rv && sock_fd >= 0) {
::close(sock_fd);
sock_fd = -1;
if (!rv && *sock_fd >= 0) {
sock_fd = nullptr;
}
return rv;
}
Expand All @@ -191,9 +203,9 @@ class ZephyrSocketWrapper {
if (ssl_sock_temp_char != -1) {
return 1;
}
count = ::recv(sock_fd, &ssl_sock_temp_char, 1, MSG_DONTWAIT);
count = ::recv(*sock_fd, &ssl_sock_temp_char, 1, MSG_DONTWAIT);
} else {
zsock_ioctl(sock_fd, ZFD_IOCTL_FIONREAD, &count);
zsock_ioctl(*sock_fd, ZFD_IOCTL_FIONREAD, &count);
}
if (count <= 0) {
delay(1);
Expand All @@ -203,87 +215,89 @@ class ZephyrSocketWrapper {
}

int recv(uint8_t *buffer, size_t size, int flags = MSG_DONTWAIT) {
if (sock_fd == -1) {
if (sock_fd == nullptr || *sock_fd == -1) {
return -1;
}

// TODO: see available()
if (ssl_sock_temp_char != -1) {
int ret = ::recv(sock_fd, &buffer[1], size - 1, flags);
int ret = ::recv(*sock_fd, &buffer[1], size - 1, flags);
buffer[0] = ssl_sock_temp_char;
ssl_sock_temp_char = -1;
return ret + 1;
}
return ::recv(sock_fd, buffer, size, flags);
return ::recv(*sock_fd, buffer, size, flags);
}

int send(const uint8_t *buffer, size_t size) {
if (sock_fd == -1) {
if (sock_fd == nullptr || *sock_fd == -1) {
return -1;
}
return ::send(sock_fd, buffer, size, 0);

return ::send(*sock_fd, buffer, size, 0);
}

void close() {
if (sock_fd != -1) {
::close(sock_fd);
sock_fd = -1;
if (sock_fd) {
sock_fd = nullptr;
}
}

bool bind(uint16_t port) {
struct sockaddr_in addr;
int raw_sock_fd;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;

sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock_fd < 0) {
raw_sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
sock_fd =
std::shared_ptr<int>(raw_sock_fd < 0 ? nullptr : new int(raw_sock_fd), socket_deleter);
if (!sock_fd) {
return false;
}

zsock_ioctl(sock_fd, ZFD_IOCTL_FIONBIO);
zsock_ioctl(*sock_fd, ZFD_IOCTL_FIONBIO);

if (::bind(sock_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
::close(sock_fd);
sock_fd = -1;
if (::bind(*sock_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
sock_fd = nullptr;
return false;
}

return true;
}

bool listen(int backlog = 5) {
if (sock_fd == -1) {
if (sock_fd == nullptr || *sock_fd == -1) {
return false;
}

if (::listen(sock_fd, backlog) < 0) {
::close(sock_fd);
sock_fd = -1;
if (::listen(*sock_fd, backlog) < 0) {
sock_fd = nullptr;
return false;
}

return true;
}

int accept() {
if (sock_fd == -1) {
if (sock_fd == nullptr || *sock_fd == -1) {
return -1;
}

return ::accept(sock_fd, nullptr, nullptr);
return ::accept(*sock_fd, nullptr, nullptr);
}

String remoteIP() {
if (sock_fd == -1) {
if (sock_fd == nullptr || *sock_fd == -1) {
return {};
}

struct sockaddr_storage addr;
socklen_t addr_len = sizeof(addr);
char ip_str[INET6_ADDRSTRLEN] = {0};

if (::getpeername(sock_fd, (struct sockaddr *)&addr, &addr_len) == 0) {
if (::getpeername(*sock_fd, (struct sockaddr *)&addr, &addr_len) == 0) {
if (addr.ss_family == AF_INET) {
struct sockaddr_in *s = (struct sockaddr_in *)&addr;
::inet_ntop(AF_INET, &s->sin_addr, ip_str, sizeof(ip_str));
Expand Down
Loading