use poll() to support multiplexing concurrent clients

This commit is contained in:
2026-07-19 14:48:23 -05:00
parent 001bd8e629
commit 799521f51f
2 changed files with 101 additions and 42 deletions

View File

@@ -30,8 +30,13 @@ ErrorCode TcpServer::init() {
int receiveBytes; int receiveBytes;
int sendBytes; int sendBytes;
unsigned int addressSize = sizeof(struct sockaddr);
struct sockaddr_in serverAddress; // ip address of the server struct sockaddr_in serverAddress; // ip address of the server
struct sockaddr_in clientAddress; // ip address of the client struct sockaddr_in clientAddress; // ip address of the client
char clientIp[20];
int clientPort;
uint32_t clientCount = 0;
serverSocket = socket(AF_INET, SOCK_STREAM, 0); // create the server's endpoint serverSocket = socket(AF_INET, SOCK_STREAM, 0); // create the server's endpoint
// AF_INET: address-family: internet (alias for IPV4, AF_INET6 for ipv6) // AF_INET: address-family: internet (alias for IPV4, AF_INET6 for ipv6)
@@ -63,60 +68,108 @@ ErrorCode TcpServer::init() {
// configure for listening // configure for listening
memset(buffer, 0x0, sizeof(buffer)); // clear our buffer so we know for sure any data in it is the client's memset(buffer, 0x0, sizeof(buffer)); // clear our buffer so we know for sure any data in it is the client's
listen(serverSocket, 10); // configure the socket as a passive server with a fifo queue 10 requests long if(listen(serverSocket, 10) < 0) { // configure the socket as a passive server with a fifo queue 10 requests long
logger_->log("TcpServer", LogFlag::Debug, "Server listening on port {}...", configuration_.port); logger_->log("TcpServer", LogFlag::Debug, "Server listening on port {}...", configuration_.port);
}
// configure pollfds
memset(sockets, 0, sizeof(sockets)); // clear all
// polls[0] is reserved for the listener
sockets[0].fd = serverSocket;
sockets[0].events = POLLIN; // monitor for incoming connections
// de-initialize all client poll file descriptors
for(size_t i = 1; i <= kMaxClients; i++) {
sockets[i].fd = -1;
}
while(true) { while(true) {
// connnect to client when requested. successful connection creates the client socket // wait until there's activity on a socket
unsigned int clientAddressSize = sizeof(struct sockaddr); if(poll(sockets, clientCount + 1, -1) < 0) { // -1 for infinite timeout
clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddress, &clientAddressSize); logger_->log("TcpServer", LogFlag::Error, "Poll error.");
// accept() blocks until connected
// the sockaddr_in must be cast to the generic sockaddr struct
// accept() also writes the size of the incoming clientAddress
if(clientSocket < 0) { // error check that guy
logger_->log("TcpServer", LogFlag::Error, "Unable to accept client socket.");
// TODO: more robust errorchecking with errnos
return ErrorCode::Error; return ErrorCode::Error;
} }
// TODO: because accept blocks, the server can't handle parallel clients.
logger_->log("TcpServer", LogFlag::Info
, "Client connected from {}:{}",
inet_ntoa(clientAddress.sin_addr), ntohs(clientAddress.sin_port));
// receive loop // check for events on the server socket
while(1) { if(sockets[0].revents & POLLIN) {
// receive messages from the client. blocks until there's a message to receive clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddress, &addressSize);
receiveBytes = tcpRead(clientSocket, buffer, sizeof(buffer)); if (clientSocket < 0) {
std::string message(buffer, receiveBytes); logger_->log("TcpServer", LogFlag::Error, "Unable to accept client socket.");
// everything below is a simulated "processMessage()"
logger_->log("TcpClient", LogFlag::Debug, "Received {} bytes from client {}:{}: {}",
receiveBytes, inet_ntoa(clientAddress.sin_addr), ntohs(clientAddress.sin_port), message);
// echo back received data back to the client
sendBytes = tcpSend(clientSocket, buffer, receiveBytes);
// TODO: send size is not guarenteed
// the solution might be a standard header that contains message type, checksum, and message length
// i.e. first four bytes are the total length of the message
if(sendBytes < 0) {
logger_->log("TcpServer", LogFlag::Error, "Unable to send to client.");
return ErrorCode::Error; return ErrorCode::Error;
} }
logger_->log("TcpClient", LogFlag::Debug, "Echoed {} bytes back to the client.", sendBytes);
} // TODO: configure a sigevent to catch a process exit signal into manually exiting this while loop // add clientSocket to the clients list
// so resources can be properly cleaned up by destructors bool success = 0;
for(size_t i = 1; i <= kMaxClients; i++) {
if (sockets[i].fd == -1) {
sockets[i].fd = clientSocket;
sockets[i].events = POLLIN;
if (i > clientCount) {
clientCount = i;
}
success = 1;
// error check receive // record client address
if(receiveBytes == 0) { if(getsockname(sockets[i].fd, (struct sockaddr *)&serverAddress, &addressSize) == 0) {
logger_->log("TcpClient", LogFlag::Info, "Client disconnected."); struct sockaddr_in* s = (struct sockaddr_in *)&clientAddress;
} else if(receiveBytes < 0) { inet_ntop(AF_INET, &s->sin_addr, clientIp, sizeof(clientIp));
logger_->log("TcpClient", LogFlag::Error, "Read from client failed."); clientPort = ntohs(s->sin_port);
clientAddresses[i] = std::format("{}:{}", clientIp, clientPort);
} else {
logger_->log("TcpServer", LogFlag::Error, "Unable to parse client address.");
return ErrorCode::Error;
}
logger_->log("TcpServer", LogFlag::Info , "Client connected from {}", clientAddresses[i]);
break;
}
}
if(!success) {
logger_->log("TcpServer", LogFlag::Error , "Unable to add client: server full.");
close(clientSocket);
}
} }
close(clientSocket); // for each connected client...
for(size_t i = 1; i <= clientCount; i++) {
if(sockets[i].fd == -1) continue;
if(sockets[i].revents & POLLIN) { // this socket was marked as being triggered by an action
// receive messages from the client
ssize_t receiveBytes = tcpRead(sockets[i].fd, buffer, sizeof(buffer));
if (receiveBytes == 0) { // client disconnected
logger_->log("TcpClient", LogFlag::Info, "Client {} disconnected.", clientAddresses[i]);
// free socket
close(sockets[i].fd);
sockets[i].fd = -1;
} else if (receiveBytes < 0) { // other receive error
logger_->log("TcpClient", LogFlag::Error, "Read from client {} failed.", clientAddresses[i]);
// free socket
close(sockets[i].fd);
sockets[i].fd = -1;
} else {
// receive success, everything below is a simulated "processMessage()"
std::string message(buffer, receiveBytes);
logger_->log("TcpClient", LogFlag::Debug, "Received {} bytes from client {}: {}",
receiveBytes, clientAddresses[i], message);
// echo back received data back to the client
ssize_t sendBytes = tcpSend(sockets[i].fd, buffer, receiveBytes);
if(sendBytes < 0) {
logger_->log("TcpServer", LogFlag::Error, "Unable to send to client.");
return ErrorCode::Error;
}
logger_->log("TcpClient", LogFlag::Debug, "Echoed {} bytes back to client {}",
sendBytes, clientAddresses[i]);
}
}
}
} }

View File

@@ -8,6 +8,7 @@
#include <arpa/inet.h> #include <arpa/inet.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/time.h> // TODO replace with chrono #include <sys/time.h> // TODO replace with chrono
#include <poll.h>
#include "common/config/ConfigService.hpp" #include "common/config/ConfigService.hpp"
#include "common/LoggerService.hpp" #include "common/LoggerService.hpp"
@@ -28,4 +29,9 @@ private:
ServerParams configuration_; ServerParams configuration_;
static constexpr size_t kMaxClients = 65536;
struct pollfd sockets[kMaxClients + 1];
std::string clientAddresses[kMaxClients + 1];
}; };