#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Configuration */ // Name of the pipe used to transfer logs to the log server std::string LOG_FIFO_NAME = "./tmp/log"; // Type of the server: server1 - last error & cursor position, server2 - mem % & vmem % std::string SERVER_TYPE = "server2"; // Port to run server int PORT = 9002; // Maximum amount of socket connections int MAX_CONNECTIONS = 128; // Amount of log messages displayed on screen const int CMD_LOG_BUFFER = 10; // Amount of milliseconds between screen rerenders int CMD_REFRESH_TIME = 500; /* Current status to display */ // Buffer with recent log messages std::string last_log_messages[CMD_LOG_BUFFER]; // Amount of active client sessions served by different threads int active_connections = 0; // Total amount of handled requests int handndled_requests = 0; // Amount of active timers for sending messages int active_timer_threads = 0; // Initializes ncurses lib void init_ncurses() { initscr(); cbreak(); noecho(); clear(); } // Returns info about last error std::string get_last_error() { const int error_code = errno; const char* error_description = std::strerror(errno); return "Last error: " + std::to_string(error_code) + " - " + error_description + "\n"; } // void disable_buffering() { // struct termios t; // tcgetattr(STDIN_FILENO, &t); // Get the terminal settings // t.c_lflag &= ~ICANON; // Disable canonical mode (no buffering) // t.c_lflag &= ~ECHO; // Disable echo (no terminal output) // t.c_cc[VMIN] = 1; // Set the minimum number of characters to read (1 char) // t.c_cc[VTIME] = 0; // Disable timeout // tcsetattr(STDIN_FILENO, TCSANOW, &t); // Apply the new settings // } // void restore_buffering() { // struct termios t; // tcgetattr(STDIN_FILENO, &t); // Get the terminal settings // t.c_lflag |= ICANON; // Enable canonical mode (buffering) // t.c_lflag |= ECHO; // Enable echo // tcsetattr(STDIN_FILENO, TCSANOW, &t); // Apply the restored settings // } // Returns info about current cursor position in console std::string get_cursor_position() { int x, y; // // Disable buffering // disable_buffering(); // printf("\033[6n"); // Request cursor position // scanf("\033[%d;%dR", &x, &y); // Read cursor position // // Print the cursor position // printf("Cursor position: (%d, %d)\n", x, y); // // Restore the terminal settings // restore_buffering(); getyx(stdscr, y, x); return "Cursor position: y: " + std::to_string(y) + " x: " + std::to_string(x) + "\n"; } // Returns info about used physical memory std::string get_mem_percent() { std::ifstream meminfo("/proc/meminfo"); if(!meminfo) { return "Error: failed to open /proc/meminfo\n"; } std::string line; long free_mem = -1, total_mem = -1; while((std::getline(meminfo, line))) { if(line.find("MemAvailable:") == 0) { std::sscanf(line.c_str(), "MemAvailable: %ld kB", &free_mem); } else if(line.find("MemTotal:") == 0) { std::sscanf(line.c_str(), "MemTotal: %ld kB", &total_mem); } if(free_mem != -1 && total_mem != -1) { break; } } meminfo.close(); if(total_mem == -1 || free_mem == -1) { return "Error: failed to read info from /proc/meminfo\n"; } return "Physical memory: " + std::to_string(100 * (total_mem - free_mem)/total_mem) + "%\n"; } // Returns message to send according to the server type std::string get_sysinfo() { if (SERVER_TYPE == "server1") { return '\t' + get_last_error() + "\t\t\t" + get_cursor_position(); } return '\t' + get_mem_percent() + "\t\t\t" + "WIP\n"; // TODO: vmem } // Writes log message on screen and passes it to the logging pipe void log(std::string msg) { // Add log message to the screen buffer for (int i = CMD_LOG_BUFFER - 1; i > 0; i--) { last_log_messages[i] = last_log_messages[i-1]; } time_t now = time(0); struct tm tstruct; tstruct = *localtime(&now); char timestamp[32]; std::strftime(timestamp, 32, "%H:%M:%S ", &tstruct); last_log_messages[0] = timestamp + std::string(" ") + msg; // Write message to the logging pipe TODO // int fd = open(LOG_FIFO_NAME.c_str(), O_WRONLY); // if(fd != -1) { // write(fd, msg.c_str(), strlen(msg.c_str()) + 1); // close(fd); // } } // Opens file descriptor for logging pipe TODO void init_log_pipe() { // mkfifo(LOG_FIFO_NAME.c_str(), 0666); } // Returns file descriptor for server socket int start_socket() { log("Creating socket..."); int server_socket = socket(AF_INET, SOCK_STREAM, 0); sockaddr_in server_address; server_address.sin_family = AF_INET; server_address.sin_port = htons(PORT); server_address.sin_addr.s_addr = INADDR_ANY; bind(server_socket, (struct sockaddr*)&server_address, sizeof(server_address)); if (errno == 98) { log("Error: " + std::string(std::strerror(errno))); return -1; } log("Socket created"); int status = listen(server_socket, MAX_CONNECTIONS); if (status != 0) { log("Error: can't listen with socket"); return -1; } log("Listening for connections..."); return server_socket; } // Handles one connection in sepparate thread void* handle_connection(void* arg) { // Cast client socket descriptor int client_socket = *(int*)arg; delete (int*)arg; std::string info = ""; log("New thread created for socket " + std::to_string(client_socket)); while (true) { // Recieve message from client char buffer[5]; ssize_t bytes_recieved = recv(client_socket, buffer, sizeof(buffer), 0); // If connection closed if (bytes_recieved == 0) { log("Connection with client " + std::to_string(client_socket) + " closed"); active_connections--; break; } if (bytes_recieved == -1) { log("Error while recieving message: " + std::to_string(errno) + " - " + std::strerror(errno)); break; } log("Got new message from client " + std::to_string(client_socket) + ": " + buffer); std::string new_info = get_sysinfo(); if (info != new_info) { log("Client " + std::to_string(client_socket) + " info is not up to date"); info = new_info; time_t now = time(0); struct tm tstruct; tstruct = *localtime(&now); char timestamp[32]; std::strftime(timestamp, 32, "%Y/%m/%d %H:%M:%S ", &tstruct); std::string msg = timestamp + info; send(client_socket, msg.c_str(), strlen(msg.c_str()) + 1, 0); log("Sent new info to client " + std::to_string(client_socket)); } else { log("Actual info already on client " + std::to_string(client_socket)); } } close(client_socket); return nullptr; } // Refreshes console output void* draw(void* arg) { while (true) { // Update info clear(); // Print configuration printw( "LOG_FIFO_NAME=%s\nSERVER_TYPE=%s\nPORT=%i\nMAX_CONNECTIONS=%i\nCMD_LOG_BUFFER=%i\nCMD_REFRESH_TIME=%i\n\n", LOG_FIFO_NAME.c_str(), SERVER_TYPE.c_str(), PORT, MAX_CONNECTIONS, CMD_LOG_BUFFER, CMD_REFRESH_TIME ); // Print statistics printw("Active connections: %i\nHandled messages: %i\nActive timers: %i\n", active_connections, handndled_requests, active_timer_threads); // Print logs for (int i = CMD_LOG_BUFFER-1; i >= 0; i--) { printw("\n%s", last_log_messages[i].c_str()); } refresh(); // Wait to update again std::this_thread::sleep_for(std::chrono::milliseconds(CMD_REFRESH_TIME)); } } int main() { init_ncurses(); init_log_pipe(); // Create thread to redraw console output pthread_t tid; if (pthread_create(&tid, nullptr, &draw, nullptr) != 0) { std::cout << ("Failed to create drawing thread") << std::endl; // endwin(); return 2; } pthread_detach(tid); int server_socket = start_socket(); while (true) { int client_socket = accept(server_socket, nullptr, nullptr); if (client_socket == -1) { continue; } log("New connection accepted"); active_connections++; // Create thread for each connection int* client_socket_copy = new int(client_socket); pthread_t tid; if (pthread_create(&tid, nullptr, &handle_connection, client_socket_copy) != 0) { log("Failed to create thread for the connection"); delete client_socket_copy; } pthread_detach(tid); } return 0; }