#include #include #include #include #include #include #include "server/network/Server.h" // the empty function ensures the thread is joinable Server::ConnectedClient::ConnectedClient() : th([]() {}), socket(-1) { } Server::Server(uint16_t port, uint16_t maxClients, const IServerListener& listener) : shouldRun(false), port(port), maxClients(maxClients), serverListener(listener), listenerSocket(-1), clientAmount(0), clients(nullptr) { // create socket for incoming connections // domain - AF_INET - IPv4 Internet protocols // type - SOCK_STREAM - two-way, connection-based byte streams // protocol - 0 - use standard protocol for the given socket type listenerSocket = socket(AF_INET, SOCK_STREAM, 0); if(listenerSocket == -1) { printError("cannot create listener socket"); return; } // prevents clients from blocking the port if the server exits // this is useful if server and client run on the same system struct linger sl; sl.l_onoff = 1; // nonzero to linger on close sl.l_linger = 0; // time to linger // sockfd - listenerSocket - modified socket // level - SOL_SOCKET - manipulate options at the sockets API level // optname - SO_LINGER - identifier of the option if(setsockopt(listenerSocket, SOL_SOCKET, SO_LINGER, &sl, sizeof(struct linger)) == -1) { printError("cannot set non lingering"); return; } // specify binding data struct sockaddr_in connectSocketData; // clear padding memset(&connectSocketData, 0, sizeof(struct sockaddr_in)); // IPv4 Internet protocols connectSocketData.sin_family = AF_INET; // port in network byte order connectSocketData.sin_port = htons(port); // address in network byte order, accept any incoming messages connectSocketData.sin_addr.s_addr = htons(INADDR_ANY); // bind the socket if(bind(listenerSocket, (struct sockaddr*) &connectSocketData, sizeof(struct sockaddr_in)) == -1) { printError("cannot bind listener socket"); return; } // mark the socket as handler for connection requests // backlog - 5 - max queue length of pending connections if(listen(listenerSocket, 5) == -1) { printError("cannot start listening on socket"); return; } shouldRun = true; listenerThread = std::thread(&Server::listenForClients, this); clients = new ConnectedClient[maxClients]; } Server::~Server() { shouldRun = false; listenerThread.join(); if(listenerSocket != -1) { if(close(listenerSocket) == -1) { printError("cannot close listener socket"); } } if(clients != nullptr) { for(uint16_t i = 0; i < maxClients; i++) { if(clients[i].socket != -1) { if(shutdown(clients[i].socket, SHUT_RDWR)) { printError("cannot shutdown client socket"); } if(close(clients[i].socket) == -1) { printError("cannot close client socket"); } } if(clients[i].th.joinable()) { clients[i].th.join(); } else { std::cout << "cannot join client connection thread " << std::endl; } } delete[] clients; } } bool Server::isRunning() const { return shouldRun; } void Server::printError(const char* message) const { std::cout << message << ": " << strerror(errno) << std::endl; } void Server::listenForClients() { while(shouldRun) { // wait until a connection arrives with timeout, this prevents being // stuck in accept struct pollfd fds; fds.fd = listenerSocket; // file descriptor for polling fds.events = POLLIN; // wait until data is ready to read fds.revents = 0; // return events - none // nfds_t - 1 - amount of passed in structs // timeout - 100 - milliseconds to wait until an event occurs // returns 0 on timeout, -1 on error, and >0 on success int pollData = poll(&fds, 1, 100); if(pollData > 0) { struct sockaddr_in clientSocketData; // accepts an incoming client connection and stores the data in the // given struct, returns a nonnegative file descriptor on success socklen_t addrlen = sizeof(struct sockaddr_in); int clientSocket = accept(listenerSocket, (struct sockaddr*) &clientSocketData, &addrlen); if(clientSocket >= 0) { //std::cout << inet_ntoa(clientSocketData.sin_addr) << ":" << (int) ntohs(clientSocketData.sin_port) << std::endl; if(addClient(clientSocket)) { if(close(clientSocket) == -1) { printError("cannot close client socket"); } } } else { printError("accept error"); break; } } else if(pollData == -1) { printError("poll error"); break; } } } bool Server::addClient(int clientSocket) { std::lock_guard lg(clientMutex); if(clientAmount >= maxClients) { serverListener.onFullServerClientConnect(clientSocket); return true; } else { // search for free slot uint16_t index = 0; while(index < maxClients) { if(clients[index].socket == -1) { break; } index++; } if(index >= maxClients) { std::cout << "cannot find free slot - even if there should be one" << std::endl; return true; } //ensure old thread has ended if(!clients[index].th.joinable()) { std::cout << "cannot join thread of non used client connection" << std::endl; return true; } clients[index].th.join(); clients[index].socket = clientSocket; clients[index].th = std::thread(&Server::listenOnClient, this, std::ref(clients[index])); clientAmount++; return false; } } void Server::listenOnClient(ConnectedClient& cc) { // poll data struct pollfd fds; fds.fd = cc.socket; // file descriptor for polling fds.events = POLLIN; // wait until data is ready to read fds.revents = 0; // return events - none serverListener.onClientConnect(cc.socket); Stream st; while(shouldRun) { // nfds_t - 1 - amount of passed in structs // timeout - 100 - milliseconds to wait until an event occurs // returns 0 on timeout, -1 on error, and >0 on success int pollData = poll(&fds, 1, 100); if(pollData > 0) { st.readSocket(cc.socket); if(st.hasData()) { serverListener.onClientPackage(cc.socket, st); } else { // client closed connection break; } } else if(pollData == -1) { printError("cannot poll from client"); break; } } serverListener.onClientDisconnect(cc.socket); // reset slot for another client if(shouldRun) { std::lock_guard lg(clientMutex); if(close(cc.socket) == -1) { printError("cannot close socket of client"); } cc.socket = -1; clientAmount--; } }