diff --git a/server/src/TcpServer.cpp b/server/src/TcpServer.cpp index 2a33fa4..8fdf4e4 100644 --- a/server/src/TcpServer.cpp +++ b/server/src/TcpServer.cpp @@ -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 - logger_->log("TcpServer", LogFlag::Debug, "Server listening on port {}...", configuration_.port); + 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)); - 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); - - // 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."); + // 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; } - 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 + // 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; - // 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."); + // 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); + } } - 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]); + } + } + } } diff --git a/server/src/TcpServer.hpp b/server/src/TcpServer.hpp index d85c244..2815b8c 100644 --- a/server/src/TcpServer.hpp +++ b/server/src/TcpServer.hpp @@ -8,6 +8,7 @@ #include #include #include // TODO replace with chrono +#include #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]; + };