use poll() to support multiplexing concurrent clients
This commit is contained in:
@@ -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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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];
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user