use poll() to support multiplexing concurrent clients
This commit is contained in:
@@ -30,8 +30,13 @@ ErrorCode TcpServer::init() {
|
||||
int receiveBytes;
|
||||
int sendBytes;
|
||||
|
||||
unsigned int addressSize = sizeof(struct sockaddr);
|
||||
|
||||
struct sockaddr_in serverAddress; // ip address of the server
|
||||
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
|
||||
// AF_INET: address-family: internet (alias for IPV4, AF_INET6 for ipv6)
|
||||
@@ -63,60 +68,108 @@ ErrorCode TcpServer::init() {
|
||||
|
||||
// configure for listening
|
||||
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);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
|
||||
// connnect to client when requested. successful connection creates the client socket
|
||||
unsigned int clientAddressSize = sizeof(struct sockaddr);
|
||||
clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddress, &clientAddressSize);
|
||||
// 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
|
||||
// wait until there's activity on a socket
|
||||
if(poll(sockets, clientCount + 1, -1) < 0) { // -1 for infinite timeout
|
||||
logger_->log("TcpServer", LogFlag::Error, "Poll 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
|
||||
while(1) {
|
||||
// receive messages from the client. blocks until there's a message to receive
|
||||
receiveBytes = tcpRead(clientSocket, buffer, sizeof(buffer));
|
||||
// check for events on the server socket
|
||||
if(sockets[0].revents & POLLIN) {
|
||||
clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddress, &addressSize);
|
||||
if (clientSocket < 0) {
|
||||
logger_->log("TcpServer", LogFlag::Error, "Unable to accept client socket.");
|
||||
return ErrorCode::Error;
|
||||
}
|
||||
|
||||
// add clientSocket to the clients list
|
||||
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;
|
||||
|
||||
// record client address
|
||||
if(getsockname(sockets[i].fd, (struct sockaddr *)&serverAddress, &addressSize) == 0) {
|
||||
struct sockaddr_in* s = (struct sockaddr_in *)&clientAddress;
|
||||
inet_ntop(AF_INET, &s->sin_addr, clientIp, sizeof(clientIp));
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
logger_->log("TcpClient", LogFlag::Debug, "Received {} bytes from client {}: {}",
|
||||
receiveBytes, clientAddresses[i], 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
|
||||
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 the client.", sendBytes);
|
||||
|
||||
} // TODO: configure a sigevent to catch a process exit signal into manually exiting this while loop
|
||||
// so resources can be properly cleaned up by destructors
|
||||
|
||||
// error check receive
|
||||
if(receiveBytes == 0) {
|
||||
logger_->log("TcpClient", LogFlag::Info, "Client disconnected.");
|
||||
} else if(receiveBytes < 0) {
|
||||
logger_->log("TcpClient", LogFlag::Error, "Read from client failed.");
|
||||
logger_->log("TcpClient", LogFlag::Debug, "Echoed {} bytes back to client {}",
|
||||
sendBytes, clientAddresses[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
close(clientSocket);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h> // TODO replace with chrono
|
||||
#include <poll.h>
|
||||
|
||||
#include "common/config/ConfigService.hpp"
|
||||
#include "common/LoggerService.hpp"
|
||||
@@ -28,4 +29,9 @@ private:
|
||||
|
||||
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