Files
CS-LABS/mine/lab_5/server.c
2025-12-10 16:50:28 +07:00

231 lines
11 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#define _GNU_SOURCE // для определения расширенных возможностей glibc
#include <errno.h>
#include <mqueue.h> // POSIX очереди сообщений: mq_open, mq_send, mq_receive, mq_timedreceive
#include <signal.h> // sig_atomic_t, signal, SIGINT
#include <stdbool.h> // bool, true/false
#include <stdint.h>
#include <stdio.h> // printf, fprintf, perror
#include <stdlib.h> // atoi, malloc/free при желании
#include <string.h> // memset, strncpy, strncmp
#include <sys/stat.h> // константы прав доступа к объектам ФС
#include <time.h> // clock_gettime, nanosleep, struct timespec
#include "common.h" // описания REQ_QUEUE, NAME_MAXLEN, req_msg_t, rep_msg_t и т.п.
#ifndef MAX_CLIENTS
#define MAX_CLIENTS 128 // максимальное число клиентов, чьи очереди мы запомним
#endif
// Глобальный флаг остановки сервера по сигналу
static volatile sig_atomic_t stop_flag = 0;
// Обработчик SIGINT: просто выставляет флаг
static void on_sigint(int) { stop_flag = 1; }
// Удобная функция "уснуть" на ms миллисекунд
static void msleep(int ms) {
struct timespec ts = {
.tv_sec = ms / 1000,
.tv_nsec = (ms % 1000) * 1000000L
};
nanosleep(&ts, NULL);
}
/* Простейший реестр клиентов: массив имён их ответных очередей */
static char clients[MAX_CLIENTS][NAME_MAXLEN];
static int client_count = 0;
// Добавляет имя очереди клиента в список, если его там ещё нет
static void add_client(const char *name) {
if (!name || name[0] == '\0') return;
// Проверка на дубликат
for (int i = 0; i < client_count; ++i) {
if (strncmp(clients[i], name, NAME_MAXLEN) == 0) return;
}
// Если есть место — копируем имя в массив
if (client_count < MAX_CLIENTS) {
strncpy(clients[client_count], name, NAME_MAXLEN - 1);
clients[client_count][NAME_MAXLEN - 1] = '\0';
client_count++;
}
}
/* Рассылает всем запомненным клиентам сообщение STOP (granted=0, remain=0) */
static void send_stop_to_clients(void) {
rep_msg_t stoprep;
memset(&stoprep, 0, sizeof(stoprep));
stoprep.granted = 0;
stoprep.remain = 0;
for (int i = 0; i < client_count; ++i) {
// Открываем очередь ответа клиента только на запись
mqd_t q = mq_open(clients[i], O_WRONLY);
if (q == -1) {
// Лучшая попытка: если открыть не удалось — просто пропускаем
continue;
}
// Отправляем структуру-ответ без приоритета (0)
mq_send(q, (const char *) &stoprep, sizeof(stoprep), 0);
mq_close(q);
}
}
int main(int argc, char **argv) {
// Ожидается: total_honey, portion, period_ms, starvation_ms
if (argc != 5) {
fprintf(stderr, "Usage: %s <total_honey> <portion> <period_ms> <starvation_ms>\n", argv[0]);
return 2;
}
// Разбираем параметры симуляции
int remain = atoi(argv[1]); // сколько "мёда" всего
int portion = atoi(argv[2]); // сколько Винни ест за один подход
int period_ms = atoi(argv[3]); // период "еды" в миллисекундах
int starvation_ms = atoi(argv[4]); // через сколько мс без еды считаем, что Винни умер с голоду
if (remain < 0 || portion <= 0 || period_ms <= 0 || starvation_ms < 0) {
fprintf(stderr, "Bad args\n");
return 2;
}
// На всякий случай удаляем старую очередь запросов, если осталась
mq_unlink(REQ_QUEUE);
// Настроиваем атрибуты очереди запросов
struct mq_attr attr;
memset(&attr, 0, sizeof(attr));
attr.mq_maxmsg = 10; // максимум 10 сообщений в очереди
attr.mq_msgsize = sizeof(req_msg_t); // размер одного сообщения = размер структуры запроса
// Открываем общую очередь запросов: создаём и даём читать/писать
// (читаем заявки и также сможем через неё послать STOP при желании)
mqd_t qreq = mq_open(REQ_QUEUE, O_CREAT | O_RDWR, 0666, &attr);
if (qreq == (mqd_t) -1) {
perror("mq_open qreq");
fprintf(stderr, "Hint: ensure msg_max>=10 and msgsize_max>=%zu\n", sizeof(req_msg_t));
return 1;
}
// Для отладки выводим реальные атрибуты очереди
struct mq_attr got;
if (mq_getattr(qreq, &got) == 0) {
fprintf(stderr, "Server: q=%s maxmsg=%ld msgsize=%ld cur=%ld\n",
REQ_QUEUE, got.mq_maxmsg, got.mq_msgsize, got.mq_curmsgs);
}
// Обработчик Ctrl+C: аккуратное завершение
signal(SIGINT, on_sigint);
fprintf(stderr, "Server: started remain=%d portion=%d period=%dms starve=%dms\n",
remain, portion, period_ms, starvation_ms);
// Инициализируем "текущее время" и моменты следующего приёма пищи / последней еды
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
long long now_ns = (long long) now.tv_sec * 1000000000LL + now.tv_nsec;
long long next_eat_ns = now_ns + (long long) period_ms * 1000000LL; // когда Винни в следующий раз ест
long long last_feed_ns = now_ns; // когда он ел в последний раз
req_msg_t req; // буфер для входящего запроса
bool need_stop_broadcast = false; // флаг: нужно ли разослать клиентам STOP
// Главный цикл сервера: обрабатываем запросы и "еду" до сигнала или окончания мёда
while (!stop_flag) {
// Обновляем текущее время
clock_gettime(CLOCK_MONOTONIC, &now);
now_ns = (long long) now.tv_sec * 1000000000LL + now.tv_nsec;
// Сколько мс до следующего приёма пищи
int sleep_ms = (int) ((next_eat_ns - now_ns) / 1000000LL);
if (sleep_ms < 0) sleep_ms = 0;
// Дедлайн для mq_timedreceive: ждём сообщение не дольше sleep_ms
struct timespec deadline = {
.tv_sec = now.tv_sec + sleep_ms / 1000,
.tv_nsec = now.tv_nsec + (sleep_ms % 1000) * 1000000L
};
if (deadline.tv_nsec >= 1000000000L) {
deadline.tv_sec++;
deadline.tv_nsec -= 1000000000L;
}
// Пытаемся принять запрос до наступления дедлайна
ssize_t rd = mq_timedreceive(qreq, (char *) &req, sizeof(req), NULL, &deadline);
if (rd >= 0) {
// Успешно прочитали структуру запроса
if (req.want != 0) {
// Регистрируем очередь ответа клиента, чтобы уметь послать ему STOP
add_client(req.replyq);
rep_msg_t rep;
if (remain > 0) {
int grant = req.want;
if (grant > remain) grant = remain;
remain -= grant;
rep.granted = grant; // сколько реально дали
rep.remain = remain; // сколько осталось
} else {
// Мёд закончился — ничего не даём
rep.granted = 0;
rep.remain = 0;
}
// Открываем очередь ответа клиента и отправляем ему ответ
mqd_t qrep = mq_open(req.replyq, O_WRONLY);
if (qrep != (mqd_t) -1) {
mq_send(qrep, (const char *) &rep, sizeof(rep), 0);
mq_close(qrep);
}
}
} else if (errno != ETIMEDOUT && errno != EAGAIN) {
// Любая ошибка, кроме "таймаут" или "временно нет сообщений", логируется
perror("mq_timedreceive");
}
// После приёма (или таймаута) обновляем текущее время и проверяем, пора ли Винни есть
clock_gettime(CLOCK_MONOTONIC, &now);
now_ns = (long long) now.tv_sec * 1000000000LL + now.tv_nsec;
if (now_ns >= next_eat_ns) {
if (remain > 0) {
// Винни ест свою порцию (или остаток, если мёда меньше порции)
int eat = portion;
if (eat > remain) eat = remain;
remain -= eat;
last_feed_ns = now_ns;
fprintf(stderr, "Winnie eats %d, remain=%d\n", eat, remain);
} else {
// Мёда нет: проверяем, не умер ли Винни с голоду (starvation_ms)
if (starvation_ms > 0 &&
(now_ns - last_feed_ns) / 1000000LL >= starvation_ms) {
fprintf(stderr, "Winnie starved, stopping\n");
need_stop_broadcast = true;
break;
}
}
// Планируем следующий приём пищи через period_ms
next_eat_ns = now_ns + (long long) period_ms * 1000000LL;
}
// Если мёд закончился, надо будет всем сообщить STOP и завершаться
if (remain <= 0) {
need_stop_broadcast = true;
break;
}
}
// При нормальном окончании (мёд закончился или Винни умер с голоду)
// посылаем всем клиентам STOP
if (need_stop_broadcast) {
fprintf(stderr, "Server: broadcasting STOP to clients\n");
send_stop_to_clients();
msleep(100); // даём клиентам время получить сообщение
}
// Закрываем и удаляем очередь запросов
mq_close(qreq);
mq_unlink(REQ_QUEUE);
fprintf(stderr, "Server: finished\n");
return 0;
}