/* Реализовать приложение сетевой чат. Сервер должен использовать неблокирующие сокеты, как способ параллельной обработки и передачи данных клиентам. Сетевой сервер должен быть реализован ввиде демона, с контролем и журналированием ошибок через syslog. Сервер должен обеспечивать одноадресную и широковещательную отправку текстовых сообщений, одному или всем участникам соответственно. */ #include "daemon.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #define MAX_CLIENTS 100 #define BUFFER_SIZE 1024 #define NAME_SIZE 32 typedef struct { int socket; char name[NAME_SIZE]; int has_name; } Client; static int stop = 0; static Client clients[MAX_CLIENTS]; static int client_count = 0; void signal_handler(int sig) { switch (sig) { case SIGTERM: case SIGINT: syslog(LOG_INFO, "Terminate signal catched. Stopping chat server..."); stop = 1; break; } } void get_timestamp(char *buffer, size_t size) { time_t now = time(NULL); struct tm *tm_info = localtime(&now); strftime(buffer, size, "%H:%M:%S", tm_info); } void send_user_list(int client_socket) { char list[BUFFER_SIZE * 2]; int offset = 0; int count = 0; offset += snprintf(list + offset, sizeof(list) - offset, "=== Online users (%d) ===\n", client_count); for (int i = 0; i < MAX_CLIENTS; i++) { if (clients[i].socket != -1 && clients[i].has_name) { offset += snprintf(list + offset, sizeof(list) - offset, " - %s\n", clients[i].name); count++; } } if (count == 0) { offset += snprintf(list + offset, sizeof(list) - offset, " (no other users yet)\n"); } offset += snprintf(list + offset, sizeof(list) - offset, "========================\n"); send(client_socket, list, strlen(list), 0); } void init_clients() { for (int i = 0; i < MAX_CLIENTS; i++) { clients[i].socket = -1; clients[i].has_name = 0; memset(clients[i].name, 0, NAME_SIZE); } client_count = 0; } int add_client(int sock) { for (int i = 0; i < MAX_CLIENTS; i++) { if (clients[i].socket == -1) { clients[i].socket = sock; clients[i].has_name = 0; client_count++; syslog(LOG_INFO, "New client connected (slot %d, total: %d)", i, client_count); return i; } } return -1; } void remove_client(int index) { if (clients[index].socket != -1) { syslog(LOG_INFO, "Client '%s' disconnected (slot %d)", clients[index].name, index); // Уведомляем других пользователей об отключении if (clients[index].has_name) { char notification[256]; snprintf(notification, sizeof(notification), "*** %s left the chat ***\n", clients[index].name); for (int i = 0; i < MAX_CLIENTS; i++) { if (clients[i].socket != -1 && i != index && clients[i].has_name) { send(clients[i].socket, notification, strlen(notification), 0); } } } close(clients[index].socket); clients[index].socket = -1; clients[index].has_name = 0; memset(clients[index].name, 0, NAME_SIZE); client_count--; } } Client *find_client_by_name(const char *name) { for (int i = 0; i < MAX_CLIENTS; i++) { if (clients[i].socket != -1 && clients[i].has_name && strcmp(clients[i].name, name) == 0) { return &clients[i]; } } return NULL; } void broadcast_message(const char *message, int sender_index) { char timestamp[16]; char formatted[BUFFER_SIZE + NAME_SIZE + 32]; get_timestamp(timestamp, sizeof(timestamp)); snprintf(formatted, sizeof(formatted), "[%s] %s: %s\n", timestamp, clients[sender_index].name, message); for (int i = 0; i < MAX_CLIENTS; i++) { if (clients[i].socket != -1 && i != sender_index && clients[i].has_name) { send(clients[i].socket, formatted, strlen(formatted), 0); } } syslog(LOG_INFO, "Broadcast from '%s': %s", clients[sender_index].name, message); } void send_private_message(const char *message, int sender_index, const char *recipient_name) { Client *recipient = find_client_by_name(recipient_name); if (recipient == NULL) { char error_msg[BUFFER_SIZE]; snprintf(error_msg, sizeof(error_msg), "Error: User '%s' not found\n", recipient_name); send(clients[sender_index].socket, error_msg, strlen(error_msg), 0); return; } char timestamp[16]; char formatted[BUFFER_SIZE + NAME_SIZE + 32]; get_timestamp(timestamp, sizeof(timestamp)); snprintf(formatted, sizeof(formatted), "[%s] %s -> you: %s\n", timestamp, clients[sender_index].name, message); send(recipient->socket, formatted, strlen(formatted), 0); // Отправляем подтверждение отправителю snprintf(formatted, sizeof(formatted), "[%s] you -> %s: %s\n", timestamp, recipient_name, message); send(clients[sender_index].socket, formatted, strlen(formatted), 0); syslog(LOG_INFO, "Private message from '%s' to '%s': %s", clients[sender_index].name, recipient_name, message); } void send_group_message(const char *message, int sender_index, const char *recipients_list) { char recipients_copy[BUFFER_SIZE]; strncpy(recipients_copy, recipients_list, BUFFER_SIZE - 1); recipients_copy[BUFFER_SIZE - 1] = '\0'; char *token = strtok(recipients_copy, ","); while (token != NULL) { while (*token == ' ') token++; char *end = token + strlen(token) - 1; while (end > token && *end == ' ') { *end = '\0'; end--; } if (strlen(token) > 0) { send_private_message(message, sender_index, token); } token = strtok(NULL, ","); } } void handle_message(int client_index, char *message) { // Убираем перевод строки char *newline = strchr(message, '\n'); if (newline) *newline = '\0'; newline = strchr(message, '\r'); if (newline) *newline = '\0'; if (strlen(message) == 0) return; // Если у клиента еще нет имени, первое сообщение - это его имя if (!clients[client_index].has_name) { // Проверяем, не занято ли это имя for (int i = 0; i < MAX_CLIENTS; i++) { if (clients[i].socket != -1 && clients[i].has_name && i != client_index && strcmp(clients[i].name, message) == 0) { // Имя уже занято char error_msg[256]; snprintf( error_msg, sizeof(error_msg), "Error: Name '%s' is already taken. Please choose another name:\n", message); send(clients[client_index].socket, error_msg, strlen(error_msg), 0); syslog(LOG_WARNING, "Client in slot %d tried to use taken name: %s", client_index, message); return; } } strncpy(clients[client_index].name, message, NAME_SIZE - 1); clients[client_index].has_name = 1; char welcome[256]; snprintf(welcome, sizeof(welcome), "Welcome to chat, %s! Total users: %d\n", message, client_count); send(clients[client_index].socket, welcome, strlen(welcome), 0); // Отправляем список пользователей send_user_list(clients[client_index].socket); // Уведомляем других о новом пользователе char notification[256]; snprintf(notification, sizeof(notification), "*** %s joined the chat ***\n", message); for (int i = 0; i < MAX_CLIENTS; i++) { if (clients[i].socket != -1 && i != client_index && clients[i].has_name) { send(clients[i].socket, notification, strlen(notification), 0); } } syslog(LOG_INFO, "Client in slot %d set name: %s", client_index, message); return; } // Проверяем, не является ли это личным сообщением (@username: message) if (message[0] == '@') { char *colon = strchr(message, ':'); if (colon != NULL) { *colon = '\0'; char *recipients = message + 1; // Пропускаем '@' char *msg_text = colon + 1; /* Пропускаем пробелы после двоеточия */ while (*msg_text == ' ') msg_text++; // Групповые сообщения if (strchr(recipients, ',') != NULL) { send_group_message(msg_text, client_index, recipients); } else { send_private_message(msg_text, client_index, recipients); } return; } } /* Обычное широковещательное сообщение */ broadcast_message(message, client_index); } int create_server_socket(int port) { struct sockaddr_in addr; int listener = socket(AF_INET, SOCK_STREAM, 0); if (listener < 0) { syslog(LOG_ERR, "Unable to create server socket"); exit(1); } // Устанавливаем неблокирующий режим if (fcntl(listener, F_SETFL, O_NONBLOCK) < 0) { syslog(LOG_ERR, "Unable to set non-blocking mode"); exit(1); } int opt = 1; if (setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) { syslog(LOG_WARNING, "Unable to set SO_REUSEADDR"); } addr.sin_family = AF_INET; addr.sin_port = htons(port); addr.sin_addr.s_addr = INADDR_ANY; if (bind(listener, (struct sockaddr *)&addr, sizeof(addr)) < 0) { syslog(LOG_ERR, "Unable to bind socket to port %d", port); exit(1); } if (listen(listener, 10) < 0) { syslog(LOG_ERR, "Unable to listen"); exit(1); } return listener; } int chat_server(int port) { openlog("CHAT-SERVER", 0, LOG_DAEMON); daemonize(); signal(SIGTERM, signal_handler); signal(SIGINT, signal_handler); init_clients(); int listener = create_server_socket(port); syslog(LOG_INFO, "Chat server started on port %d", port); while (!stop) { fd_set readset; FD_ZERO(&readset); FD_SET(listener, &readset); int max_fd = listener; // Добавляем все активные клиентские сокеты for (int i = 0; i < MAX_CLIENTS; i++) { if (clients[i].socket != -1) { FD_SET(clients[i].socket, &readset); if (clients[i].socket > max_fd) { max_fd = clients[i].socket; } } } // Устанавливаем таймаут для периодической проверки флага stop struct timeval timeout; timeout.tv_sec = 1; timeout.tv_usec = 0; int activity = select(max_fd + 1, &readset, NULL, NULL, &timeout); if (activity < 0 && errno != EINTR) { syslog(LOG_ERR, "Select error: %s", strerror(errno)); continue; } if (activity == 0) { // Таймаут, просто продолжаем continue; } // Проверяем, есть ли новое подключение if (FD_ISSET(listener, &readset)) { int new_socket = accept(listener, NULL, NULL); if (new_socket >= 0) { // Устанавливаем неблокирующий режим для клиентского сокета if (fcntl(new_socket, F_SETFL, O_NONBLOCK) < 0) { syslog(LOG_WARNING, "Unable to set non-blocking mode for client"); } int index = add_client(new_socket); if (index == -1) { syslog(LOG_WARNING, "Maximum clients reached, rejecting connection"); const char *msg = "Server full, please try again later\n"; send(new_socket, msg, strlen(msg), 0); close(new_socket); } else { const char *greeting = "Connected to chat server. Please enter your " "name:\n"; send(new_socket, greeting, strlen(greeting), 0); } } } // Проверяем данные от клиентов for (int i = 0; i < MAX_CLIENTS; i++) { if (clients[i].socket == -1) continue; if (FD_ISSET(clients[i].socket, &readset)) { char buffer[BUFFER_SIZE]; int bytes_read = recv(clients[i].socket, buffer, BUFFER_SIZE - 1, 0); if (bytes_read <= 0) { // Клиент отключился remove_client(i); } else { buffer[bytes_read] = '\0'; handle_message(i, buffer); } } } } // Закрываем все соединения close(listener); for (int i = 0; i < MAX_CLIENTS; i++) { if (clients[i].socket != -1) { close(clients[i].socket); } } syslog(LOG_INFO, "Chat server stopped"); closelog(); return 0; }