diff --git a/vlad/lab_1/in.txt b/vlad/lab_1/in.txt new file mode 100644 index 0000000..d31d5e0 --- /dev/null +++ b/vlad/lab_1/in.txt @@ -0,0 +1,8 @@ +aaaaaa +aaaaaaaaaaaa +aaaaaaaaaaaa +aaaaaaaaaaaa + 2 +aaaaaaaaaaaa +aaaaaaaaaaaa +aaaaaa \ No newline at end of file diff --git a/vlad/lab_1/task11.c b/vlad/lab_1/task11.c new file mode 100644 index 0000000..db1bfd4 --- /dev/null +++ b/vlad/lab_1/task11.c @@ -0,0 +1,170 @@ +#include +#include +#include +#include +#include +#include +#include + +#define RBUFSZ 4096 +#define WBUFSZ 4096 + +static void die_perror(const char *what, const char *path, int exit_code) { + int saved = errno; + char msg[512]; + int n; + if (path) { + n = snprintf(msg, sizeof(msg), "%s: %s: %s\n", what, path, strerror(saved)); + } else { + n = snprintf(msg, sizeof(msg), "%s: %s\n", what, strerror(saved)); + } + if (n > 0) (void) write(STDERR_FILENO, msg, (size_t) n); + _exit(exit_code); +} + +static long long parse_ll(const char *s) { + char *end = NULL; + errno = 0; + long long v = strtoll(s, &end, 10); + if (errno != 0 || end == s || *end != '\0' || v < 0) { + errno = EINVAL; + return -1; + } + return v; +} + +static void xwrite_all(int fd, const char *buf, size_t len, const char *path) { + size_t off = 0; + while (off < len) { + ssize_t n = write(fd, buf + off, len - off); + if (n < 0) { + if (errno == EINTR) continue; + die_perror("write failed", path, -1); + } + off += (size_t) n; + } +} + +int main(int argc, char *argv[]) { + if (argc != 4) { + const char *usage = + "Usage: lab1_var11 \n" + "Variant 11: for every pair of identical consecutive characters, replace the second with a space.\n" + " Stop after replacements globally.\n"; + (void) write(STDERR_FILENO, usage, strlen(usage)); + return -1; + } + + const char *in_path = argv[1]; + const char *out_path = argv[2]; + long long cap = parse_ll(argv[3]); + if (cap < 0) { + die_perror("invalid max_replacements", argv[3], -1); + } + + int in_fd = open(in_path, O_RDONLY); + if (in_fd < 0) { + die_perror("open input failed", in_path, -1); + } + + mode_t filePerms = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + int out_fd = open(out_path, O_CREAT | O_WRONLY | O_TRUNC, filePerms); + if (out_fd < 0) { + die_perror("open output failed", out_path, -1); + } + + char rbuf[RBUFSZ]; + char wbuf[WBUFSZ]; + size_t wlen = 0; + + long long total_replacements = 0; + int replacing_enabled = 1; + + int have_prev = 0; + unsigned char prev = 0; + + for (;;) { + ssize_t n = read(in_fd, rbuf, sizeof(rbuf)); + if (n > 0) { + for (ssize_t i = 0; i < n; i++) { + unsigned char c = (unsigned char) rbuf[i]; + + if (!have_prev) { + prev = c; + have_prev = 1; + continue; + } + + if (c == prev) { + unsigned char out1 = prev; + unsigned char out2 = c; + + if (replacing_enabled && total_replacements < cap) { + out2 = ' '; + total_replacements++; + if (total_replacements == cap) { + replacing_enabled = 0; + } + } else { + replacing_enabled = 0; + } + + if (wlen == sizeof(wbuf)) { + xwrite_all(out_fd, wbuf, wlen, out_path); + wlen = 0; + } + wbuf[wlen++] = (char) out1; + + if (wlen == sizeof(wbuf)) { + xwrite_all(out_fd, wbuf, wlen, out_path); + wlen = 0; + } + wbuf[wlen++] = (char) out2; + + have_prev = 0; + } else { + if (wlen == sizeof(wbuf)) { + xwrite_all(out_fd, wbuf, wlen, out_path); + wlen = 0; + } + wbuf[wlen++] = (char) prev; + + prev = c; + have_prev = 1; + } + } + } else if (n == 0) { + if (have_prev) { + if (wlen == sizeof(wbuf)) { + xwrite_all(out_fd, wbuf, wlen, out_path); + wlen = 0; + } + wbuf[wlen++] = (char) prev; + have_prev = 0; + } + if (wlen > 0) { + xwrite_all(out_fd, wbuf, wlen, out_path); + wlen = 0; + } + break; + } else { + if (errno == EINTR) continue; + die_perror("read failed", in_path, -1); + } + } + + if (close(in_fd) < 0) { + die_perror("close input failed", in_path, -1); + } + if (close(out_fd) < 0) { + die_perror("close output failed", out_path, -1); + } + + char res[64]; + int m = snprintf(res, sizeof(res), "%lld\n", total_replacements); + if (m > 0) { + (void) write(STDOUT_FILENO, res, (size_t) m); + } + + return 0; +} diff --git a/vlad/lab_2/Makefile b/vlad/lab_2/Makefile new file mode 100644 index 0000000..f1b28f8 --- /dev/null +++ b/vlad/lab_2/Makefile @@ -0,0 +1,52 @@ +# Makefile + +CC = gcc +CFLAGS = -Wall -Wextra -O2 +PICFLAGS = -fPIC + +.PHONY: all dynamic static test-dynamic test-static clean + +all: dynamic static + +# --- Dynamic (shared) build --- +dynamic: libtext.so main_d + +libtext.so: lib_d.o + $(CC) -shared -o $@ $^ + +main_d: main_d.o + $(CC) -o $@ $^ -ldl + +lib_d.o: lib_d.c + $(CC) $(CFLAGS) $(PICFLAGS) -c $< -o $@ + +# --- Static build --- +static: libtext.a main_s + +libtext.a: lib_s.o + ar rcs $@ $^ + +main_s: main_s.o libtext.a + $(CC) -o $@ main_s.o libtext.a + +# Generic rule for other .o files +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + +# --- Test targets --- +# Creates a small `test_input.txt`, runs the program, and shows `out.txt` +test-dynamic: dynamic + printf "Hello123456789\n!!@@2211122\n" > test_input.txt + ./main_d test_input.txt out.txt 100 ./libtext.so + @echo "---- out.txt ----" + cat out.txt + +test-static: static + printf "Hello123456789\n!!@@2211122\n" > test_input.txt + ./main_s test_input.txt out.txt 100 + @echo "---- out.txt ----" + cat out.txt + +# --- Cleanup --- +clean: + rm -f *.o *.so *.a main_d main_s test_input.txt out.txt diff --git a/vlad/lab_2/lib_d.c b/vlad/lab_2/lib_d.c new file mode 100644 index 0000000..1d8b32d --- /dev/null +++ b/vlad/lab_2/lib_d.c @@ -0,0 +1,35 @@ +#include + +int replace_char(char *buf, int key, int *replacements_left) { + (void)key; + int replaced = 0; + size_t i = 0; + + while (buf[i] != '\0') { + if (buf[i] == '\n') { + i++; + continue; + } + + // Detect start of a symbol run + char symbol = buf[i]; + size_t run_start = i; + size_t run_len = 1; + + // Count length of run + while (buf[i + run_len] == symbol && buf[i + run_len] != '\n' && buf[i + run_len] != '\0') { + run_len++; + } + + // For pairs and longer runs: replace every 2nd, 4th, ... + for (size_t j = 1; j < run_len && *replacements_left > 0; j += 2) { + buf[run_start + j] = ' '; + replaced++; + (*replacements_left)--; + if (*replacements_left == 0) break; + } + + i += run_len; + } + return replaced; +} diff --git a/vlad/lab_2/lib_s.c b/vlad/lab_2/lib_s.c new file mode 100644 index 0000000..1d8b32d --- /dev/null +++ b/vlad/lab_2/lib_s.c @@ -0,0 +1,35 @@ +#include + +int replace_char(char *buf, int key, int *replacements_left) { + (void)key; + int replaced = 0; + size_t i = 0; + + while (buf[i] != '\0') { + if (buf[i] == '\n') { + i++; + continue; + } + + // Detect start of a symbol run + char symbol = buf[i]; + size_t run_start = i; + size_t run_len = 1; + + // Count length of run + while (buf[i + run_len] == symbol && buf[i + run_len] != '\n' && buf[i + run_len] != '\0') { + run_len++; + } + + // For pairs and longer runs: replace every 2nd, 4th, ... + for (size_t j = 1; j < run_len && *replacements_left > 0; j += 2) { + buf[run_start + j] = ' '; + replaced++; + (*replacements_left)--; + if (*replacements_left == 0) break; + } + + i += run_len; + } + return replaced; +} diff --git a/vlad/lab_2/main_d.c b/vlad/lab_2/main_d.c new file mode 100644 index 0000000..04e4e4b --- /dev/null +++ b/vlad/lab_2/main_d.c @@ -0,0 +1,62 @@ +#include +#include +#include + +#define MAX_LINE 4096 + +typedef int (*replace_func_t)(char*, int, int*); + +int main(int argc, char *argv[]) { + if (argc != 5) { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 1; + } + + FILE *fin = fopen(argv[1], "r"); + if (!fin) { perror("fopen input"); return 1; } + FILE *fout = fopen(argv[2], "w"); + if (!fout) { perror("fopen output"); fclose(fin); return 1; } + + int cap = atoi(argv[3]); + if (cap < 0) { + fprintf(stderr, "invalid cap\n"); + fclose(fin); + fclose(fout); + return 1; + } + + void *lib = dlopen(argv[4], RTLD_LAZY); + if (!lib) { + fprintf(stderr, "dlopen error: %s\n", dlerror()); + fclose(fin); + fclose(fout); + return 1; + } + + replace_func_t replace = (replace_func_t)dlsym(lib, "replace_char"); + if (!replace) { + fprintf(stderr, "dlsym error: %s\n", dlerror()); + dlclose(lib); + fclose(fin); + fclose(fout); + return 1; + } + + int total = 0; + char line[MAX_LINE]; + + while (fgets(line, sizeof(line), fin)) { + if (cap > 0) { + int key = (unsigned char)line[0]; + int repl_line = replace(line, key, &cap); + total += repl_line; + } + fputs(line, fout); + } + + dlclose(lib); + fclose(fin); + fclose(fout); + printf("total_replacements: %d\n", total); + return 0; +} diff --git a/vlad/lab_2/main_s.c b/vlad/lab_2/main_s.c new file mode 100644 index 0000000..227a189 --- /dev/null +++ b/vlad/lab_2/main_s.c @@ -0,0 +1,44 @@ +#include +#include + +#define MAX_LINE 4096 + +int replace_char(char *buf, int key, int *replacements_left); + +int main(int argc, char *argv[]) { + if (argc != 4) { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 1; + } + + FILE *fin = fopen(argv[1], "r"); + if (!fin) { perror("fopen input"); return 1; } + FILE *fout = fopen(argv[2], "w"); + if (!fout) { perror("fopen output"); fclose(fin); return 1; } + + int cap = atoi(argv[3]); + if (cap < 0) { + fprintf(stderr, "invalid cap\n"); + fclose(fin); + fclose(fout); + return 1; + } + + int total = 0; + char line[MAX_LINE]; + + while (fgets(line, sizeof(line), fin)) { + if (cap > 0) { + // key is unused, but pass the first byte for symmetry + int key = (unsigned char)line[0]; + int repl_line = replace_char(line, key, &cap); + total += repl_line; + } + fputs(line, fout); + } + + fclose(fin); + fclose(fout); + printf("total_replacements: %d\n", total); + return 0; +} diff --git a/vlad/lab_3/Makefile b/vlad/lab_3/Makefile new file mode 100644 index 0000000..9fe5e1c --- /dev/null +++ b/vlad/lab_3/Makefile @@ -0,0 +1,51 @@ +# Компилятор и флаги +CC = gcc +CFLAGS = -Wall -Wextra -std=c99 -pedantic + +# Целевые файлы +TARGET_PARENT = parent +TARGET_TASK11 = task11 + +# Исходные файлы +SRC_PARENT = parent.c +SRC_TASK11 = task11.c + +# Объектные файлы +OBJ_PARENT = $(SRC_PARENT:.c=.o) +OBJ_TASK11 = $(SRC_TASK11:.c=.o) + +.PHONY: all clean help test + +all: $(TARGET_PARENT) $(TARGET_TASK11) + +$(TARGET_PARENT): $(OBJ_PARENT) + $(CC) $(CFLAGS) -o $@ $^ + @echo "Родительская программа собрана: $@" + +$(TARGET_TASK11): $(OBJ_TASK11) + $(CC) $(CFLAGS) -o $@ $^ + @echo "Программа лаб. работы №11 собрана: $@" + +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + rm -f $(TARGET_PARENT) $(TARGET_TASK11) $(OBJ_PARENT) $(OBJ_TASK11) input* output* + @echo "Очистка завершена" + +help: + @echo "Доступные цели:" + @echo " all - собрать все" + @echo " clean - удалить все скомпилированные файлы" + @echo " test - запустить тестирование программы" + +test: all + @echo "Создаем тестовые файлы..." + @echo "abbaabbccdd" > input1.txt + @echo "hello world !!" > input2.txt + @echo "Запуск parent..." + ./$(TARGET_PARENT) 5 input1.txt output1.txt input2.txt output2.txt + @echo "Содержимое output1.txt:" + @cat output1.txt || echo "Файл output1.txt не найден" + @echo "Содержимое output2.txt:" + @cat output2.txt || echo "Файл output2.txt не найден" diff --git a/vlad/lab_3/parent.c b/vlad/lab_3/parent.c new file mode 100644 index 0000000..8d8b0e2 --- /dev/null +++ b/vlad/lab_3/parent.c @@ -0,0 +1,88 @@ +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char *argv[]) { + if (argc < 4) { + fprintf(stderr, + "Usage: parent [ ...]\n" + " max_replacements: maximum number of replacements\n" + " input/output: pairs of input and output files\n"); + return -1; + } + + if ((argc - 2) % 2 != 0) { + fprintf(stderr, "Error: number of input/output files must be even (pairs of input/output)\n"); + return -1; + } + + const char *max_replacements_arg = argv[1]; + int num_pairs = (argc - 2) / 2; + + pid_t *child_pids = malloc(num_pairs * sizeof(pid_t)); + if (!child_pids) { + perror("malloc failed"); + return -1; + } + + for (int i = 0; i < num_pairs; i++) { + const char *input_file = argv[2 + i * 2]; + const char *output_file = argv[2 + i * 2 + 1]; + + pid_t pid = fork(); + if (pid < 0) { + perror("fork failed"); + // Wait for already started children + for (int j = 0; j < i; j++) { + waitpid(child_pids[j], NULL, 0); + } + free(child_pids); + return -1; + } + + if (pid == 0) { + // Child process + printf("Child %d (PID=%d) processing %s -> %s\n", i + 1, getpid(), input_file, output_file); + execl("./task11", "task11", input_file, output_file, max_replacements_arg, (char *) NULL); + + // If exec returns, error happened + fprintf(stderr, "Child %d: execl failed for %s: %s\n", i + 1, input_file, strerror(errno)); + _exit(-1); + } + + // Parent process + child_pids[i] = pid; + } + + printf("Parent process waiting for children...\n"); + + int ret_code = 0; + for (int i = 0; i < num_pairs; i++) { + int status; + pid_t pid = waitpid(child_pids[i], &status, 0); + if (pid < 0) { + perror("waitpid failed"); + continue; + } + + if (WIFEXITED(status)) { + int code = WEXITSTATUS(status); + printf("Child %d (PID=%d) exited with code %d\n", i + 1, pid, code); + if (code != 0) { + ret_code = code; + } + } else if (WIFSIGNALED(status)) { + printf("Child %d (PID=%d) terminated by signal %d\n", i + 1, pid, WTERMSIG(status)); + } else { + printf("Child %d (PID=%d) ended abnormally\n", i + 1, pid); + } + } + + free(child_pids); + printf("All children finished\n"); + return ret_code; +} diff --git a/vlad/lab_3/task11.c b/vlad/lab_3/task11.c new file mode 100644 index 0000000..db1bfd4 --- /dev/null +++ b/vlad/lab_3/task11.c @@ -0,0 +1,170 @@ +#include +#include +#include +#include +#include +#include +#include + +#define RBUFSZ 4096 +#define WBUFSZ 4096 + +static void die_perror(const char *what, const char *path, int exit_code) { + int saved = errno; + char msg[512]; + int n; + if (path) { + n = snprintf(msg, sizeof(msg), "%s: %s: %s\n", what, path, strerror(saved)); + } else { + n = snprintf(msg, sizeof(msg), "%s: %s\n", what, strerror(saved)); + } + if (n > 0) (void) write(STDERR_FILENO, msg, (size_t) n); + _exit(exit_code); +} + +static long long parse_ll(const char *s) { + char *end = NULL; + errno = 0; + long long v = strtoll(s, &end, 10); + if (errno != 0 || end == s || *end != '\0' || v < 0) { + errno = EINVAL; + return -1; + } + return v; +} + +static void xwrite_all(int fd, const char *buf, size_t len, const char *path) { + size_t off = 0; + while (off < len) { + ssize_t n = write(fd, buf + off, len - off); + if (n < 0) { + if (errno == EINTR) continue; + die_perror("write failed", path, -1); + } + off += (size_t) n; + } +} + +int main(int argc, char *argv[]) { + if (argc != 4) { + const char *usage = + "Usage: lab1_var11 \n" + "Variant 11: for every pair of identical consecutive characters, replace the second with a space.\n" + " Stop after replacements globally.\n"; + (void) write(STDERR_FILENO, usage, strlen(usage)); + return -1; + } + + const char *in_path = argv[1]; + const char *out_path = argv[2]; + long long cap = parse_ll(argv[3]); + if (cap < 0) { + die_perror("invalid max_replacements", argv[3], -1); + } + + int in_fd = open(in_path, O_RDONLY); + if (in_fd < 0) { + die_perror("open input failed", in_path, -1); + } + + mode_t filePerms = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + int out_fd = open(out_path, O_CREAT | O_WRONLY | O_TRUNC, filePerms); + if (out_fd < 0) { + die_perror("open output failed", out_path, -1); + } + + char rbuf[RBUFSZ]; + char wbuf[WBUFSZ]; + size_t wlen = 0; + + long long total_replacements = 0; + int replacing_enabled = 1; + + int have_prev = 0; + unsigned char prev = 0; + + for (;;) { + ssize_t n = read(in_fd, rbuf, sizeof(rbuf)); + if (n > 0) { + for (ssize_t i = 0; i < n; i++) { + unsigned char c = (unsigned char) rbuf[i]; + + if (!have_prev) { + prev = c; + have_prev = 1; + continue; + } + + if (c == prev) { + unsigned char out1 = prev; + unsigned char out2 = c; + + if (replacing_enabled && total_replacements < cap) { + out2 = ' '; + total_replacements++; + if (total_replacements == cap) { + replacing_enabled = 0; + } + } else { + replacing_enabled = 0; + } + + if (wlen == sizeof(wbuf)) { + xwrite_all(out_fd, wbuf, wlen, out_path); + wlen = 0; + } + wbuf[wlen++] = (char) out1; + + if (wlen == sizeof(wbuf)) { + xwrite_all(out_fd, wbuf, wlen, out_path); + wlen = 0; + } + wbuf[wlen++] = (char) out2; + + have_prev = 0; + } else { + if (wlen == sizeof(wbuf)) { + xwrite_all(out_fd, wbuf, wlen, out_path); + wlen = 0; + } + wbuf[wlen++] = (char) prev; + + prev = c; + have_prev = 1; + } + } + } else if (n == 0) { + if (have_prev) { + if (wlen == sizeof(wbuf)) { + xwrite_all(out_fd, wbuf, wlen, out_path); + wlen = 0; + } + wbuf[wlen++] = (char) prev; + have_prev = 0; + } + if (wlen > 0) { + xwrite_all(out_fd, wbuf, wlen, out_path); + wlen = 0; + } + break; + } else { + if (errno == EINTR) continue; + die_perror("read failed", in_path, -1); + } + } + + if (close(in_fd) < 0) { + die_perror("close input failed", in_path, -1); + } + if (close(out_fd) < 0) { + die_perror("close output failed", out_path, -1); + } + + char res[64]; + int m = snprintf(res, sizeof(res), "%lld\n", total_replacements); + if (m > 0) { + (void) write(STDOUT_FILENO, res, (size_t) m); + } + + return 0; +} diff --git a/vlad/lab_4/Makefile b/vlad/lab_4/Makefile new file mode 100644 index 0000000..c6f4dd3 --- /dev/null +++ b/vlad/lab_4/Makefile @@ -0,0 +1,99 @@ +# Makefile for lab 4 - FIFO (named pipes) only + +CC = gcc +CFLAGS = -Wall -Wextra -std=c99 -g + +# Default target - build FIFO programs +all: fifo + +# ===== FIFO targets ===== +fifo: fifo_server fifo_client + +fifo_server: fifo_server.c + $(CC) $(CFLAGS) -o $@ $< + +fifo_client: fifo_client.c + $(CC) $(CFLAGS) -o $@ $< + +# ===== Test files ===== +test_files: + @echo "Создание тестовых файлов..." + echo "abbaabbaabbaabbaabbaabbaabbaabba" > input1.txt + echo "xyzxyzxyzxyzxyzxyzxyzxyz" >> input1.txt + echo "hello world hello" >> input1.txt + echo "testtest" > input2.txt + echo "aaaaaaa" >> input2.txt + echo "programming" > input3.txt + echo "ppppython" >> input3.txt + +# ===== FIFO tests ===== +test_fifo_server: fifo test_files + @echo "=== Запуск FIFO сервера ===" + @echo "В другом терминале выполните: make test_fifo_client" + ./fifo_server 10 + +test_fifo_client: fifo test_files + @echo "=== Запуск FIFO клиента ===" & \ + ./fifo_client input1.txt output1_fifo.txt; \ + ./fifo_client input2.txt output2_fifo.txt; \ + ./fifo_client input3.txt output3_fifo.txt; + @echo "\n=== Результаты FIFO ===" + @echo "--- output1_fifo.txt ---" + @cat output1_fifo.txt || true + @echo "\n--- output2_fifo.txt ---" + @cat output2_fifo.txt || true + @echo "\n--- output3_fifo.txt ---" + @cat output3_fifo.txt || true + +# Automatic FIFO test (server in background) +test_all: fifo test_files + @echo "=== Автоматический тест FIFO ===" + @./fifo_server 10 & \ + SERVER_PID=$$!; \ + sleep 1; \ + ./fifo_client input1.txt output1_fifo.txt; \ + ./fifo_client input2.txt output2_fifo.txt; \ + ./fifo_client input3.txt output3_fifo.txt; \ + kill $$SERVER_PID 2>/dev/null || true; \ + wait $$SERVER_PID 2>/dev/null || true + @echo "\n=== Результаты FIFO ===" + @echo "--- output1_fifo.txt ---" + @cat output1_fifo.txt || true + @echo "\n--- output2_fifo.txt ---" + @cat output2_fifo.txt || true + @echo "\n--- output3_fifo.txt ---" + @cat output3_fifo.txt || true + +# Error handling test for FIFO +test_error: fifo + @echo "\n=== Тест обработки ошибки (несуществующий файл) - FIFO ===" + @./fifo_server 5 & \ + SERVER_PID=$$!; \ + sleep 1; \ + ./fifo_client nonexistent.txt output_error.txt || true; \ + kill $$SERVER_PID 2>/dev/null || true; \ + wait $$SERVER_PID 2>/dev/null || true + +# Cleanup +clean: + @echo "Очистка..." + rm -f fifo_server fifo_client + rm -f input1.txt input2.txt input3.txt + rm -f output*.txt + rm -f /tmp/fifo_request /tmp/fifo_response + +# Help +help: + @echo "Доступные цели:" + @echo " all - Скомпилировать FIFO программы" + @echo " fifo - Скомпилировать fifo_server и fifo_client" + @echo " test_files - Создать тестовые входные файлы" + @echo " test_fifo_server - Запустить FIFO сервер (использовать с клиентом в другом терминале)" + @echo " test_fifo_client - Запустить FIFO клиент и показать результат" + @echo " test_fifo_auto - Автоматический тест FIFO (сервер в фоне)" + @echo " test_all - Запустить все тесты (FIFO)" + @echo " test_error_fifo - Тест обработки ошибок (несуществующий файл)" + @echo " clean - Удалить скомпилированные файлы и тесты" + +.PHONY: all fifo test_files test_fifo_server test_fifo_client test_all \ + test_error clean help diff --git a/vlad/lab_4/fifo_client.c b/vlad/lab_4/fifo_client.c new file mode 100644 index 0000000..9abe125 --- /dev/null +++ b/vlad/lab_4/fifo_client.c @@ -0,0 +1,144 @@ +// fifo_client.c - Клиентская программа с использованием именованных каналов (FIFO) + +#include +#include +#include +#include +#include +#include +#include +#include + +#define FIFO_REQUEST "/tmp/fifo_request" +#define FIFO_RESPONSE "/tmp/fifo_response" +#define BUFFER_SIZE 4096 + +void print_usage(const char *progname) { + fprintf(stderr, "Usage: %s \n", progname); + fprintf(stderr, "Example: %s input.txt output.txt\n", progname); +} + +int main(int argc, char *argv[]) { + if (argc != 3) { + fprintf(stderr, "ERROR: Неверное количество аргументов\n"); + print_usage(argv[0]); + return 1; + } + + const char *input_file = argv[1]; + const char *output_file = argv[2]; + + printf("=== FIFO Client ===\n"); + printf("Client PID: %d\n", getpid()); + printf("Входной файл: %s\n", input_file); + printf("Выходной файл: %s\n", output_file); + + // Открываем входной файл + int in_fd = open(input_file, O_RDONLY); + if (in_fd < 0) { + fprintf(stderr, "ERROR: Не удалось открыть входной файл %s: %s\n", + input_file, strerror(errno)); + return 1; + } + + // Читаем данные из файла + char *buffer = malloc(BUFFER_SIZE); + if (!buffer) { + fprintf(stderr, "ERROR: Не удалось выделить память\n"); + close(in_fd); + return 1; + } + + ssize_t bytes_read = read(in_fd, buffer, BUFFER_SIZE - 1); + close(in_fd); + + if (bytes_read < 0) { + fprintf(stderr, "ERROR: Не удалось прочитать файл: %s\n", strerror(errno)); + free(buffer); + return 1; + } + + buffer[bytes_read] = '\0'; + printf("Прочитано байт из файла: %zd\n", bytes_read); + + // Открываем FIFO для отправки запроса + printf("Отправка запроса серверу...\n"); + int fd_req = open(FIFO_REQUEST, O_WRONLY); + if (fd_req == -1) { + fprintf(stderr, "ERROR: Не удалось открыть FIFO запроса: %s\n", strerror(errno)); + fprintf(stderr, "Убедитесь, что сервер запущен!\n"); + free(buffer); + return 1; + } + + // Отправляем данные серверу + ssize_t bytes_written = write(fd_req, buffer, bytes_read); + close(fd_req); + + if (bytes_written != bytes_read) { + fprintf(stderr, "ERROR: Ошибка отправки данных\n"); + free(buffer); + return 1; + } + + printf("Отправлено байт: %zd\n", bytes_written); + + // Открываем FIFO для получения ответа + printf("Ожидание ответа от сервера...\n"); + int fd_resp = open(FIFO_RESPONSE, O_RDONLY); + if (fd_resp == -1) { + fprintf(stderr, "ERROR: Не удалось открыть FIFO ответа: %s\n", strerror(errno)); + free(buffer); + return 1; + } + + // Читаем обработанные данные + ssize_t response_bytes = read(fd_resp, buffer, BUFFER_SIZE - 1); + close(fd_resp); + + if (response_bytes < 0) { + fprintf(stderr, "ERROR: Ошибка чтения ответа\n"); + free(buffer); + return 1; + } + + buffer[response_bytes] = '\0'; + printf("Получено байт от сервера: %zd\n", response_bytes); + + // Ищем информацию о количестве замен + char *replacements_info = strstr(buffer, "\nREPLACEMENTS:"); + long long replacements = 0; + + if (replacements_info) { + sscanf(replacements_info, "\nREPLACEMENTS:%lld", &replacements); + *replacements_info = '\0'; // Обрезаем служебную информацию + response_bytes = replacements_info - buffer; + } + + // Открываем выходной файл + int out_fd = open(output_file, O_CREAT | O_WRONLY | O_TRUNC, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + if (out_fd < 0) { + fprintf(stderr, "ERROR: Не удалось открыть выходной файл %s: %s\n", + output_file, strerror(errno)); + free(buffer); + return 1; + } + + // Записываем обработанные данные + ssize_t written = write(out_fd, buffer, response_bytes); + close(out_fd); + + if (written != response_bytes) { + fprintf(stderr, "ERROR: Ошибка записи в выходной файл\n"); + free(buffer); + return 1; + } + + printf("Записано байт в выходной файл: %zd\n", written); + printf("Количество выполненных замен: %lld\n", replacements); + printf("\nОбработка завершена успешно!\n"); + + free(buffer); + return 0; +} diff --git a/vlad/lab_4/fifo_server.c b/vlad/lab_4/fifo_server.c new file mode 100644 index 0000000..e1d5873 --- /dev/null +++ b/vlad/lab_4/fifo_server.c @@ -0,0 +1,187 @@ +// fifo_server.c - Серверная программа с использованием именованных каналов (FIFO) +// Вариант повышенной сложности для Lab 4 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define FIFO_REQUEST "/tmp/fifo_request" +#define FIFO_RESPONSE "/tmp/fifo_response" +#define BUFFER_SIZE 4096 + +volatile sig_atomic_t running = 1; + +void signal_handler(int sig) { + (void)sig; + running = 0; +} + +static long long parse_ll(const char *s) { + char *end = NULL; + errno = 0; + long long v = strtoll(s, &end, 10); + if (errno != 0 || end == s || *end != '\0' || v < 0) { + return -1; + } + return v; +} + +long long process_data(const char *input, size_t input_len, char *output, + size_t output_size, long long max_replacements) { + long long total_replacements = 0; + size_t out_pos = 0; + size_t i = 0; + + while (i < input_len && out_pos < output_size - 1) { + unsigned char c = (unsigned char)input[i]; + + /* Try to form a non-overlapping pair with the next character. + Do not treat newline as part of a pair (preserve line structure). */ + if (i + 1 < input_len) { + unsigned char next = (unsigned char)input[i + 1]; + if (c != '\n' && next != '\n' && c == next && total_replacements < max_replacements) { + /* write first char of pair */ + output[out_pos++] = (char)c; + if (out_pos >= output_size - 1) break; + /* write space instead of second char */ + output[out_pos++] = ' '; + total_replacements++; + i += 2; /* skip both chars (non-overlapping) */ + continue; + } + } + + /* No valid pair -> write current char */ + output[out_pos++] = (char)c; + i++; + } + + /* If we stopped early but still have room, copy remaining bytes (without forming pairs) + until output buffer full or input ends. This preserves trailing data. */ + while (i < input_len && out_pos < output_size - 1) { + output[out_pos++] = input[i++]; + } + + output[out_pos] = '\0'; + return total_replacements; +} + +int main(int argc, char *argv[]) { + if (argc != 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 1; + } + + long long max_replacements = parse_ll(argv[1]); + if (max_replacements < 0) { + fprintf(stderr, "ERROR: Invalid max_replacements\n"); + return 1; + } + + printf("=== FIFO Server запущен ===\n"); + printf("Server PID: %d\n", getpid()); + printf("Максимум замен: %lld\n", max_replacements); + + // Устанавливаем обработчик сигналов + signal(SIGINT, signal_handler); + signal(SIGTERM, signal_handler); + + // Удаляем старые FIFO, если существуют + unlink(FIFO_REQUEST); + unlink(FIFO_RESPONSE); + + // Создаем именованные каналы + if (mkfifo(FIFO_REQUEST, 0666) == -1) { + perror("mkfifo request"); + return 1; + } + if (mkfifo(FIFO_RESPONSE, 0666) == -1) { + perror("mkfifo response"); + unlink(FIFO_REQUEST); + return 1; + } + + printf("FIFO каналы созданы:\n"); + printf(" Request: %s\n", FIFO_REQUEST); + printf(" Response: %s\n", FIFO_RESPONSE); + printf("Ожидание запросов от клиентов...\n\n"); + + while (running) { + // Открываем FIFO для чтения запроса + int fd_req = open(FIFO_REQUEST, O_RDONLY); + if (fd_req == -1) { + if (errno == EINTR) continue; + perror("open request FIFO"); + break; + } + + // Читаем данные от клиента + char *input_buffer = malloc(BUFFER_SIZE); + char *output_buffer = malloc(BUFFER_SIZE); + + if (!input_buffer || !output_buffer) { + fprintf(stderr, "ERROR: Memory allocation failed\n"); + close(fd_req); + free(input_buffer); + free(output_buffer); + continue; + } + + ssize_t bytes_read = read(fd_req, input_buffer, BUFFER_SIZE - 1); + close(fd_req); + + if (bytes_read <= 0) { + free(input_buffer); + free(output_buffer); + continue; + } + + input_buffer[bytes_read] = '\0'; + printf("Получен запрос: %zd байт\n", bytes_read); + + // Обрабатываем данные + long long replacements = process_data(input_buffer, bytes_read, + output_buffer, BUFFER_SIZE, + max_replacements); + + printf("Выполнено замен: %lld\n", replacements); + + // Открываем FIFO для отправки ответа + int fd_resp = open(FIFO_RESPONSE, O_WRONLY); + if (fd_resp == -1) { + perror("open response FIFO"); + free(input_buffer); + free(output_buffer); + continue; + } + + // Отправляем обработанные данные + size_t output_len = strlen(output_buffer); + ssize_t bytes_written = write(fd_resp, output_buffer, output_len); + + // Отправляем количество замен (в отдельной строке) + char result[64]; + snprintf(result, sizeof(result), "\nREPLACEMENTS:%lld\n", replacements); + write(fd_resp, result, strlen(result)); + + close(fd_resp); + + printf("Отправлен ответ: %zd байт\n\n", bytes_written); + + free(input_buffer); + free(output_buffer); + } + + // Очистка + printf("\nЗавершение работы сервера...\n"); + unlink(FIFO_REQUEST); + unlink(FIFO_RESPONSE); + + return 0; +} diff --git a/vlad/lab_5/Makefile b/vlad/lab_5/Makefile new file mode 100644 index 0000000..7e9a8b3 --- /dev/null +++ b/vlad/lab_5/Makefile @@ -0,0 +1,60 @@ +CC = gcc +CFLAGS = -Wall -Wextra -std=c99 -g +LDFLAGS_MQ = -lrt + +# SERVER_ARGS: +# WORKER_ARGS: +SERVER_ARGS = 1000 15 500 500 +WORKER_ARGS = 7 + +all: msg + +# ===== POSIX MQ targets ===== +msg: msg_server msg_worker + +msg_server: server.c common.h + $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS_MQ) + +msg_worker: worker.c common.h + $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS_MQ) + +# ===== Tests ===== +test_msg_server: msg + @echo "=== Запуск сервера POSIX MQ ===" + @echo "В другом терминале выполните: make test_msg_workers" + ./msg_server $(SERVER_ARGS) + +test_msg_workers: msg + @echo "=== Запуск нескольких пчёл ===" + ./msg_worker 7 & \ + ./msg_worker 9 & \ + ./msg_worker 5 & \ + wait + +# Автотест: сервер в фоне, несколько пчёл +test_all: msg + @echo "=== Автотест POSIX MQ ===" + ./msg_server $(SERVER_ARGS) & \ + SRV=$$!; \ + sleep 2; \ + ./msg_worker $(WORKER_ARGS) & \ + ./msg_worker $(WORKER_ARGS) & \ + ./msg_worker $(WORKER_ARGS) & \ + wait; \ + wait $$SRV + +# Очистка +clean: + @echo "Очистка..." + rm -f msg_server msg_worker + +help: + @echo "Available targets:" + @echo " msg - Build POSIX message queue programs" + @echo " test_msg_server - Run MQ server (use workers in another terminal)" + @echo " test_msg_workers - Run several worker processes" + @echo " test_all - Automatic end-to-end test" + @echo " clean - Remove built files" + @echo " help - Show this help" + +.PHONY: all msg test_msg_server test_msg_workers test_all clean help diff --git a/vlad/lab_5/common.h b/vlad/lab_5/common.h new file mode 100644 index 0000000..2399e11 --- /dev/null +++ b/vlad/lab_5/common.h @@ -0,0 +1,16 @@ +#pragma once +#include + +#define REQ_QUEUE "/winnie_req" +#define NAME_MAXLEN 64 + +typedef struct { + pid_t pid; + int want; + char replyq[NAME_MAXLEN]; +} req_msg_t; + +typedef struct { + int granted; + int remain; +} rep_msg_t; diff --git a/vlad/lab_5/server.c b/vlad/lab_5/server.c new file mode 100644 index 0000000..9559569 --- /dev/null +++ b/vlad/lab_5/server.c @@ -0,0 +1,230 @@ +#define _GNU_SOURCE // для определения расширенных возможностей glibc +#include +#include // POSIX очереди сообщений: mq_open, mq_send, mq_receive, mq_timedreceive +#include // sig_atomic_t, signal, SIGINT +#include // bool, true/false +#include +#include // printf, fprintf, perror +#include // atoi, malloc/free при желании +#include // memset, strncpy, strncmp +#include // константы прав доступа к объектам ФС +#include // 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 \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; +} diff --git a/vlad/lab_5/worker.c b/vlad/lab_5/worker.c new file mode 100644 index 0000000..27ffd44 --- /dev/null +++ b/vlad/lab_5/worker.c @@ -0,0 +1,123 @@ +#define _GNU_SOURCE // для определения расширенных возможностей glibc +#include +#include // POSIX очереди сообщений: mq_open, mq_send, mq_receive +#include +#include +#include // fprintf, perror, dprintf +#include // atoi, rand_r +#include // memset, strncpy, snprintf +#include // права для mq_open (0666) +#include // time(), nanosleep, struct timespec +#include // getpid, STDOUT_FILENO + +#include "common.h" // REQ_QUEUE, NAME_MAXLEN, req_msg_t, rep_msg_t + +// Пауза на заданное количество миллисекунд +static void msleep(int ms) { + struct timespec ts = { + .tv_sec = ms / 1000, + .tv_nsec = (ms % 1000) * 1000000L + }; + nanosleep(&ts, NULL); +} + +int main(int argc, char **argv) { + // Ожидается один аргумент: сколько мёда пчела просит за раз + if (argc != 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 2; + } + int portion = atoi(argv[1]); + if (portion <= 0) { + fprintf(stderr, "portion must be >0\n"); + return 2; + } + + // PID пчелы и имя её личной очереди ответов + pid_t me = getpid(); + char replyq[NAME_MAXLEN]; + snprintf(replyq, sizeof(replyq), "/bee_%d", (int) me); + + // На всякий случай удаляем старую очередь с таким именем + mq_unlink(replyq); + + // Атрибуты очереди ответов (куда сервер будет слать rep_msg_t) + struct mq_attr attr; + memset(&attr, 0, sizeof(attr)); + attr.mq_maxmsg = 10; + attr.mq_msgsize = sizeof(rep_msg_t); + + // Создаём очередь ответов пчелы, только для чтения + mqd_t qrep = mq_open(replyq, O_CREAT | O_RDONLY, 0666, &attr); + if (qrep == (mqd_t) -1) { + perror("mq_open reply"); + return 1; + } + + // Открываем очередь запросов к серверу (общая очередь REQ_QUEUE) + mqd_t qreq = -1; + for (int i = 0; i < 50; i++) { + qreq = mq_open(REQ_QUEUE, O_WRONLY); + if (qreq != -1) break; // удалось открыть — выходим из цикла + if (errno != ENOENT) { + // другая ошибка, не "очередь ещё не создана" + perror("mq_open req"); + break; + } + // Если сервер ещё не создал очередь (ENOENT) — подождать и попробовать снова + msleep(100); + } + if (qreq == -1) { + // Не смогли открыть очередь запросов — выходим + perror("mq_open req"); + mq_close(qrep); + mq_unlink(replyq); + return 1; + } + + // Инициализация отдельного генератора случайных чисел для этой пчелы + unsigned seed = (unsigned) (time(NULL) ^ (uintptr_t) me); + + // Основной рабочий цикл пчелы + while (1) { + // Ждём случайное время 100–699 мс перед очередным запросом + int ms = 100 + (rand_r(&seed) % 600); + msleep(ms); + + // Формируем запрос к серверу + req_msg_t req; + memset(&req, 0, sizeof(req)); + req.pid = me; + req.want = portion; // сколько мёда хотим получить + strncpy(req.replyq, replyq, sizeof(req.replyq) - 1); // куда слать ответ + + // Отправляем запрос в очередь REQ_QUEUE + if (mq_send(qreq, (const char *) &req, sizeof(req), 0) == -1) { + perror("mq_send"); + break; + } + + // Ждём ответ от сервера в своей очереди + rep_msg_t rep; + ssize_t rd = mq_receive(qrep, (char *) &rep, sizeof(rep), NULL); + if (rd == -1) { + perror("mq_receive"); + break; + } + + // Если нам больше ничего не дают (granted <= 0) — выходим + if (rep.granted <= 0) { + break; + } + + // Иначе логируем, сколько мёда получили и сколько осталось у сервера + dprintf(STDOUT_FILENO, "Bee %d got %d, remain %d\n", + (int) me, rep.granted, rep.remain); + } + + // Очистка ресурсов: закрываем очереди и удаляем личную очередь ответов + mq_close(qreq); + mq_close(qrep); + mq_unlink(replyq); + return 0; +} diff --git a/vlad/lab_6/Makefile b/vlad/lab_6/Makefile new file mode 100644 index 0000000..6eefc14 --- /dev/null +++ b/vlad/lab_6/Makefile @@ -0,0 +1,51 @@ +CC = gcc +CFLAGS = -Wall -Wextra -std=c99 -g +LDFLAGS_IPC = -lrt -pthread + +SHM_NAME ?= /myshm +SEM_CLIENT_NAME ?= /sem_client +SEM_SERVER_NAME ?= /sem_server +SERVER_ITERS ?= 1000 + +all: shm + +shm: server client + +server: server.c + $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS_IPC) + +client: client.c + $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS_IPC) + +test_server: shm + @echo "=== Запуск сервера POSIX SHM+SEM ===" + @echo "В другом терминале выполните: make test_client" + ./server $(SHM_NAME) $(SEM_CLIENT_NAME) $(SEM_SERVER_NAME) $(SERVER_ITERS) + +test_client: shm + @echo "=== Запуск клиента, чтение input.txt, вывод на stdout ===" + ./client $(SHM_NAME) $(SEM_CLIENT_NAME) $(SEM_SERVER_NAME) + +test_all: shm + @echo "=== Автотест POSIX SHM+SEM с файлами input.txt/output.txt ===" + ./server $(SHM_NAME) $(SEM_CLIENT_NAME) $(SEM_SERVER_NAME) $(SERVER_ITERS) & \ + SRV=$$!; \ + sleep 1; \ + ./client $(SHM_NAME) $(SEM_CLIENT_NAME) $(SEM_SERVER_NAME); \ + wait $$SRV + +clean: + @echo "Очистка..." + rm -f server client + rm -f output.txt + +help: + @echo "Available targets:" + @echo " shm - Build POSIX SHM+SEM programs" + @echo " test_server - Run SHM+SEM server (client in another terminal)" + @echo " test_client - Run client reading input.txt" + @echo " test_all - Automatic end-to-end test with input.txt/output.txt" + @echo " clean - Remove built files" + @echo " help - Show this help" + +.PHONY: all shm test_server test_client test_all clean help diff --git a/vlad/lab_6/client.c b/vlad/lab_6/client.c new file mode 100644 index 0000000..90ddedc --- /dev/null +++ b/vlad/lab_6/client.c @@ -0,0 +1,147 @@ +// client.c +// Клиент: читает строки из input.txt, передаёт их серверу через shared memory, +// ожидает обработку и печатает результат на stdout. [file:21][file:22] + +#include // printf, fprintf, perror, FILE, fopen, fclose, fgets +#include // exit, EXIT_FAILURE +#include // memset, strncpy, strlen +#include // errno +#include // O_RDWR +#include // shm_open, mmap, munmap +#include // mode_t +#include // sem_t, sem_open, sem_close, sem_wait, sem_post +#include // close + +#define SHM_BUFFER_SIZE 1024 // Должен совпадать с server.c. [file:21] + +typedef struct { + int has_data; // Флаг наличия данных. [file:21] + int result_code; // Код результата обработки. [file:21] + char buffer[SHM_BUFFER_SIZE]; // Буфер строки. [file:21] +} shared_block_t; + +int main(int argc, char *argv[]) { + if (argc < 4) { + fprintf(stderr, + "Usage: %s \n", + argv[0]); + return -1; + } + + const char *shm_name = argv[1]; + const char *sem_client_name = argv[2]; + const char *sem_server_name = argv[3]; + + // Открываем существующий shm. [file:21] + int shm_fd = shm_open(shm_name, O_RDWR, 0); + if (shm_fd == -1) { + perror("shm_open"); + return -1; + } + + shared_block_t *shm_ptr = mmap(NULL, + sizeof(shared_block_t), + PROT_READ | PROT_WRITE, + MAP_SHARED, + shm_fd, + 0); + if (shm_ptr == MAP_FAILED) { + perror("mmap"); + close(shm_fd); + return -1; + } + + if (close(shm_fd) == -1) { + perror("close"); + } + + // Открываем семафоры. [file:21] + sem_t *sem_client = sem_open(sem_client_name, 0); + if (sem_client == SEM_FAILED) { + perror("sem_open(sem_client)"); + munmap(shm_ptr, sizeof(shared_block_t)); + return -1; + } + + sem_t *sem_server = sem_open(sem_server_name, 0); + if (sem_server == SEM_FAILED) { + perror("sem_open(sem_server)"); + sem_close(sem_client); + munmap(shm_ptr, sizeof(shared_block_t)); + return -1; + } + + // Открываем input.txt. [file:22] + FILE *fin = fopen("input.txt", "r"); + if (!fin) { + perror("fopen(input.txt)"); + sem_close(sem_client); + sem_close(sem_server); + munmap(shm_ptr, sizeof(shared_block_t)); + return -1; + } + + char input[SHM_BUFFER_SIZE]; + + while (fgets(input, sizeof(input), fin) != NULL) { + size_t len = strlen(input); + + if (len > 0 && input[len - 1] == '\n') { + input[len - 1] = '\0'; + } + + memset(shm_ptr->buffer, 0, sizeof(shm_ptr->buffer)); + strncpy(shm_ptr->buffer, input, SHM_BUFFER_SIZE - 1); + shm_ptr->buffer[SHM_BUFFER_SIZE - 1] = '\0'; + + shm_ptr->has_data = 1; + + if (sem_post(sem_client) == -1) { + perror("sem_post(sem_client)"); + fclose(fin); + sem_close(sem_client); + sem_close(sem_server); + munmap(shm_ptr, sizeof(shared_block_t)); + return -1; + } + + if (sem_wait(sem_server) == -1) { + perror("sem_wait(sem_server)"); + fclose(fin); + sem_close(sem_client); + sem_close(sem_server); + munmap(shm_ptr, sizeof(shared_block_t)); + return -1; + } + + if (shm_ptr->result_code != 0) { + fprintf(stderr, + "Server reported error, result_code = %d\n", + shm_ptr->result_code); + fclose(fin); + sem_close(sem_client); + sem_close(sem_server); + munmap(shm_ptr, sizeof(shared_block_t)); + return -1; + } + + printf("%s\n", shm_ptr->buffer); + } + + if (fclose(fin) == EOF) { + perror("fclose(input.txt)"); + } + + if (sem_close(sem_client) == -1) { + perror("sem_close(sem_client)"); + } + if (sem_close(sem_server) == -1) { + perror("sem_close(sem_server)"); + } + + if (munmap(shm_ptr, sizeof(shared_block_t)) == -1) { + perror("munmap"); + } + + return 0; +} diff --git a/vlad/lab_6/input.txt b/vlad/lab_6/input.txt new file mode 100644 index 0000000..c640816 --- /dev/null +++ b/vlad/lab_6/input.txt @@ -0,0 +1,6 @@ +abacaba +xxxxxx +hello +aaaaa +1abc1d1e1 +qwerty diff --git a/vlad/lab_6/server.c b/vlad/lab_6/server.c new file mode 100644 index 0000000..9f82073 --- /dev/null +++ b/vlad/lab_6/server.c @@ -0,0 +1,210 @@ +// server.c +// Сервер POSIX IPC: разделяемая память + именованные семафоры. [file:21] +// Задача: во всех парах одинаковых соседних символов второй символ заменить на пробел. [file:22] + +#include // printf, fprintf, perror, FILE, fopen, fclose, fprintf +#include // exit, EXIT_FAILURE, strtoul +#include // memset, strncpy, strlen +#include // errno +#include // O_CREAT, O_EXCL, O_RDWR +#include // shm_open, mmap, munmap +#include // S_IRUSR, S_IWUSR +#include // sem_t, sem_open, sem_close, sem_unlink, sem_wait, sem_post +#include // ftruncate, close + +#define SHM_BUFFER_SIZE 1024 // Максимальный размер строки в shared memory. [file:21] + +// Структура разделяемой памяти. [file:21] +typedef struct { + int has_data; // 1, если клиент записал строку. [file:21] + int result_code; // 0 - успех, -1 - ошибка. [file:21] + char buffer[SHM_BUFFER_SIZE]; // Буфер для строки. [file:21] +} shared_block_t; + +// Обработка строки по новому заданию: [file:22] +// "Во всех парах одинаковых символов второй символ заменить на пробел". +static void process_line(char *s) { + if (!s) { + return; + } + + for (size_t i = 0; s[i] != '\0'; ++i) { + // Идём по всей строке. [file:22] + if (s[i] != '\0' && s[i + 1] != '\0' && s[i] == s[i + 1]) { + // Если два соседних символа равны, второй заменяем на пробел. [file:22] + s[i + 1] = ' '; + // Можно сдвинуться дальше, чтобы не склеивать новые пары искусственно. [file:22] + // Но по условию достаточно просто заменить второй символ. + } + } +} + +int main(int argc, char *argv[]) { + // argv[1] - имя shm, argv[2] - имя sem_client, argv[3] - имя sem_server, argv[4] - итерации. [file:22] + if (argc < 4) { + fprintf(stderr, + "Usage: %s [iterations]\n", + argv[0]); + return -1; + } + + const char *shm_name = argv[1]; + const char *sem_client_name = argv[2]; + const char *sem_server_name = argv[3]; + + int iterations = -1; // -1 = работать до сигнала/внешнего завершения. [file:22] + if (argc >= 5) { + char *endptr = NULL; + unsigned long tmp = strtoul(argv[4], &endptr, 10); + if (endptr == argv[4] || *endptr != '\0') { + fprintf(stderr, "Invalid iterations value: '%s'\n", argv[4]); + return -1; + } + iterations = (int) tmp; + } + + // Простая зачистка возможных старых IPC-объектов. [file:21] + shm_unlink(shm_name); + sem_unlink(sem_client_name); + sem_unlink(sem_server_name); + + // Создаём shared memory. [file:21] + int shm_fd = shm_open(shm_name, + O_CREAT | O_EXCL | O_RDWR, + S_IRUSR | S_IWUSR); + if (shm_fd == -1) { + perror("shm_open"); + return -1; + } + + if (ftruncate(shm_fd, sizeof(shared_block_t)) == -1) { + perror("ftruncate"); + close(shm_fd); + shm_unlink(shm_name); + return -1; + } + + shared_block_t *shm_ptr = mmap(NULL, + sizeof(shared_block_t), + PROT_READ | PROT_WRITE, + MAP_SHARED, + shm_fd, + 0); + if (shm_ptr == MAP_FAILED) { + perror("mmap"); + close(shm_fd); + shm_unlink(shm_name); + return -1; + } + + if (close(shm_fd) == -1) { + perror("close"); + } + + // Инициализация shared memory. [file:21] + shm_ptr->has_data = 0; + shm_ptr->result_code = 0; + memset(shm_ptr->buffer, 0, sizeof(shm_ptr->buffer)); + + // Создаём семафоры. [file:21] + sem_t *sem_client = sem_open(sem_client_name, + O_CREAT | O_EXCL, + S_IRUSR | S_IWUSR, + 0); + if (sem_client == SEM_FAILED) { + perror("sem_open(sem_client)"); + munmap(shm_ptr, sizeof(shared_block_t)); + shm_unlink(shm_name); + return -1; + } + + sem_t *sem_server = sem_open(sem_server_name, + O_CREAT | O_EXCL, + S_IRUSR | S_IWUSR, + 0); + if (sem_server == SEM_FAILED) { + perror("sem_open(sem_server)"); + sem_close(sem_client); + sem_unlink(sem_client_name); + munmap(shm_ptr, sizeof(shared_block_t)); + shm_unlink(shm_name); + return -1; + } + + // Открываем файл вывода. [file:22] + FILE *fout = fopen("output.txt", "w"); + if (!fout) { + perror("fopen(output.txt)"); + // Продолжаем работать только через shared memory. [file:21] + } + + int processed_count = 0; // Сколько строк обработано. [file:22] + + while (iterations < 0 || processed_count < iterations) { + // Ждём строку от клиента. [file:21] + if (sem_wait(sem_client) == -1) { + perror("sem_wait(sem_client)"); + processed_count = -1; + break; + } + + if (!shm_ptr->has_data) { + fprintf(stderr, "Warning: sem_client posted, but has_data == 0\n"); + shm_ptr->result_code = -1; + } else { + // Обрабатываем строку по новому заданию. [file:22] + process_line(shm_ptr->buffer); + + shm_ptr->result_code = 0; + shm_ptr->has_data = 0; + processed_count++; + + if (fout) { + fprintf(fout, "%s\n", shm_ptr->buffer); + fflush(fout); + } + } + + // Сообщаем клиенту, что результат готов. [file:21] + if (sem_post(sem_server) == -1) { + perror("sem_post(sem_server)"); + processed_count = -1; + break; + } + } + + if (fout && fclose(fout) == EOF) { + perror("fclose(output.txt)"); + } + + // Выводим число операций или -1. [file:22] + if (processed_count >= 0) { + printf("%d\n", processed_count); + } else { + printf("-1\n"); + } + + // Очистка IPC-объектов. [file:21] + if (sem_close(sem_client) == -1) { + perror("sem_close(sem_client)"); + } + if (sem_close(sem_server) == -1) { + perror("sem_close(sem_server)"); + } + + if (sem_unlink(sem_client_name) == -1) { + perror("sem_unlink(sem_client)"); + } + if (sem_unlink(sem_server_name) == -1) { + perror("sem_unlink(sem_server)"); + } + + if (munmap(shm_ptr, sizeof(shared_block_t)) == -1) { + perror("munmap"); + } + if (shm_unlink(shm_name) == -1) { + perror("shm_unlink"); + } + + return 0; +} diff --git a/vlad/lab_7/Makefile b/vlad/lab_7/Makefile new file mode 100644 index 0000000..6066bf5 --- /dev/null +++ b/vlad/lab_7/Makefile @@ -0,0 +1,42 @@ +CC = gcc +CFLAGS = -Wall -Wextra -std=c11 -g -pthread + +MAX_REPL ?= 100 +INPUT1 ?= in1.txt +OUTPUT1 ?= out1.txt +INPUT2 ?= in2.txt +OUTPUT2 ?= out2.txt + +all: threads_pairs + +threads_pairs: main.c + $(CC) $(CFLAGS) -o $@ $< + +test_all: threads_pairs + @echo "=== Тест с двумя файлами ===" + @printf "aabb\nzzzz\n" > $(INPUT1) + @printf "hello\nkkk\n" > $(INPUT2) + ./threads_pairs $(MAX_REPL) $(INPUT1) $(OUTPUT1) $(INPUT2) $(OUTPUT2) + @echo "--- $(INPUT1) -> $(OUTPUT1) ---" + @cat $(INPUT1) + @echo "-----" + @cat $(OUTPUT1) + @echo + @echo "--- $(INPUT2) -> $(OUTPUT2) ---" + @cat $(INPUT2) + @echo "-----" + @cat $(OUTPUT2) + +clean: + @echo "Очистка..." + rm -f threads_pairs + rm -f in1.txt out1.txt in2.txt out2.txt + +help: + @echo "Available targets:" + @echo " all - build threads_pairs" + @echo " test_all - run all tests" + @echo " clean - remove binaries and test files" + @echo " help - show this help" + +.PHONY: all test_one test_two test_all clean help diff --git a/vlad/lab_7/main.c b/vlad/lab_7/main.c new file mode 100644 index 0000000..f1b841d --- /dev/null +++ b/vlad/lab_7/main.c @@ -0,0 +1,300 @@ +// threads_pairs.c +// Лабораторная №7: многопоточное программирование. [file:22] +// Задача: во всех парах одинаковых соседних символов второй символ заменить на пробел. [file:22] + +#include +#include +#include +#include +#include +#include +#include +#include + +#define RBUFSZ 4096 +#define WBUFSZ 4096 +#define MAX_THREADS 100 + +typedef struct { + const char *input_path; + const char *output_path; + long long max_repl; + int thread_index; + int result; +} ThreadTask; + +static pthread_mutex_t log_mutex = PTHREAD_MUTEX_INITIALIZER; + +static void die_perror_thread(const char *what, + const char *path, + int exit_code) { + int saved = errno; + char msg[512]; + + if (path) { + snprintf(msg, sizeof(msg), + "%s: %s: %s\n", what, path, strerror(saved)); + } else { + snprintf(msg, sizeof(msg), + "%s: %s\n", what, strerror(saved)); + } + + pthread_mutex_lock(&log_mutex); + write(STDERR_FILENO, msg, strlen(msg)); + pthread_mutex_unlock(&log_mutex); + + (void) exit_code; +} + +static long long parse_ll(const char *s) { + char *end = NULL; + errno = 0; + long long v = strtoll(s, &end, 10); + if (errno != 0 || end == s || *end != '\0' || v < 0) { + errno = EINVAL; + return -1; + } + return v; +} + +// Обработка файла по задаче: во всех парах одинаковых символов второй заменить на пробел. [file:22] +static int process_file_pairs(const char *in_path, + const char *out_path, + long long cap) { + int in_fd = open(in_path, O_RDONLY); + if (in_fd < 0) { + die_perror_thread("open input failed", in_path, -1); + return -1; + } + + mode_t perms = S_IRUSR | S_IWUSR | + S_IRGRP | S_IWGRP | + S_IROTH | S_IWOTH; + + int out_fd = open(out_path, + O_CREAT | O_WRONLY | O_TRUNC, + perms); + if (out_fd < 0) { + die_perror_thread("open output failed", out_path, -1); + close(in_fd); + return -1; + } + + char rbuf[RBUFSZ]; + char wbuf[WBUFSZ]; + size_t wlen = 0; + + long long total_replacements = 0; + + for (;;) { + ssize_t n = read(in_fd, rbuf, sizeof(rbuf)); + if (n > 0) { + for (ssize_t i = 0; i < n; i++) { + unsigned char c = (unsigned char) rbuf[i]; + unsigned char outc = c; + + if (total_replacements < cap && + i + 1 < n && + (unsigned char) rbuf[i] == (unsigned char) rbuf[i + 1]) { + outc = c; + if (total_replacements < cap) { + outc = c; // первый из пары остаётся + if (wlen == sizeof(wbuf)) { + ssize_t wrote = write(out_fd, wbuf, wlen); + if (wrote < 0 || (size_t) wrote != wlen) { + die_perror_thread("write failed", out_path, -1); + close(in_fd); + close(out_fd); + return -1; + } + wlen = 0; + } + wbuf[wlen++] = (char) outc; + i++; + unsigned char c2 = (unsigned char) rbuf[i]; + outc = c2; + if (total_replacements < cap) { + outc = ' '; + total_replacements++; + } + } + } + + if (wlen == sizeof(wbuf)) { + ssize_t wrote = write(out_fd, wbuf, wlen); + if (wrote < 0 || (size_t) wrote != wlen) { + die_perror_thread("write failed", out_path, -1); + close(in_fd); + close(out_fd); + return -1; + } + wlen = 0; + } + + wbuf[wlen++] = (char) outc; + } + } else if (n == 0) { + if (wlen > 0) { + ssize_t wrote = write(out_fd, wbuf, wlen); + if (wrote < 0 || (size_t) wrote != wlen) { + die_perror_thread("write failed", out_path, -1); + close(in_fd); + close(out_fd); + return -1; + } + wlen = 0; + } + break; + } else { + die_perror_thread("read failed", in_path, -1); + close(in_fd); + close(out_fd); + return -1; + } + } + + if (close(in_fd) < 0) { + die_perror_thread("close input failed", in_path, -1); + close(out_fd); + return -1; + } + + if (close(out_fd) < 0) { + die_perror_thread("close output failed", out_path, -1); + return -1; + } + + pthread_mutex_lock(&log_mutex); + char res[64]; + int m = snprintf(res, sizeof(res), "%lld\n", total_replacements); + if (m > 0) { + write(STDOUT_FILENO, res, (size_t) m); + } + pthread_mutex_unlock(&log_mutex); + + return 0; +} + +static void *thread_func(void *arg) { + ThreadTask *task = (ThreadTask *) arg; + + pthread_mutex_lock(&log_mutex); + printf("[thread %d] start: %s -> %s, max_repl=%lld\n", + task->thread_index, + task->input_path, + task->output_path, + task->max_repl); + pthread_mutex_unlock(&log_mutex); + + int rc = process_file_pairs(task->input_path, + task->output_path, + task->max_repl); + + task->result = rc; + + pthread_mutex_lock(&log_mutex); + printf("[thread %d] finished with code %d\n", + task->thread_index, + task->result); + pthread_mutex_unlock(&log_mutex); + + return NULL; +} + +static void print_usage(const char *progname) { + fprintf(stderr, + "Usage: %s [ ...]\n", + progname); + fprintf(stderr, + "Example: %s 100 in1.txt out1.txt in2.txt out2.txt\n", + progname); +} + +int main(int argc, char *argv[]) { + if (argc < 4 || ((argc - 2) % 2) != 0) { + fprintf(stderr, + "ERROR: Недостаточное или неверное количество аргументов\n"); + print_usage(argv[0]); + return -1; + } + + long long cap = parse_ll(argv[1]); + if (cap < 0) { + die_perror_thread("invalid max_replacements", argv[1], -1); + return -1; + } + + int num_files = (argc - 2) / 2; + if (num_files > MAX_THREADS) { + fprintf(stderr, + "ERROR: Слишком много файлов (максимум %d пар)\n", + MAX_THREADS); + return -1; + } + + printf("=== Многопоточная обработка: пары одинаковых символов ===\n"); + printf("Главный поток TID: %lu\n", (unsigned long) pthread_self()); + printf("Максимум замен на файл: %lld\n", cap); + printf("Количество файловых пар: %d\n\n", num_files); + + pthread_t threads[MAX_THREADS]; + ThreadTask tasks[MAX_THREADS]; + + for (int i = 0; i < num_files; i++) { + const char *input_path = argv[2 + i * 2]; + const char *output_path = argv[3 + i * 2]; + + tasks[i].input_path = input_path; + tasks[i].output_path = output_path; + tasks[i].max_repl = cap; + tasks[i].thread_index = i + 1; + tasks[i].result = -1; + + int rc = pthread_create(&threads[i], + NULL, + thread_func, + &tasks[i]); + if (rc != 0) { + errno = rc; + die_perror_thread("pthread_create failed", NULL, -1); + tasks[i].result = -1; + } + } + + int success_count = 0; + int error_count = 0; + + for (int i = 0; i < num_files; i++) { + if (!threads[i]) { + error_count++; + continue; + } + + int rc = pthread_join(threads[i], NULL); + if (rc != 0) { + errno = rc; + die_perror_thread("pthread_join failed", NULL, -1); + error_count++; + continue; + } + + if (tasks[i].result == 0) { + success_count++; + } else { + error_count++; + } + } + + printf("\n=== Итоговая статистика ===\n"); + printf("Всего потоков: %d\n", num_files); + printf("Успешно завершено: %d\n", success_count); + printf("С ошибкой: %d\n", error_count); + + if (error_count > 0) { + printf("\nОБЩИЙ СТАТУС: Завершено с ошибками\n"); + return -1; + } else { + printf("\nОБЩИЙ СТАТУС: Все потоки завершены успешно\n"); + return 0; + } +} diff --git a/vlad/lab_8/Makefile b/vlad/lab_8/Makefile new file mode 100644 index 0000000..287bb37 --- /dev/null +++ b/vlad/lab_8/Makefile @@ -0,0 +1,51 @@ +CC = gcc +CFLAGS = -Wall -Wextra -std=c11 -g + +all: server_tcp_pairs client_tcp_pairs + +server_tcp_pairs: server.c + $(CC) $(CFLAGS) -o $@ $< + +client_tcp_pairs: client.c + $(CC) $(CFLAGS) -o $@ $< + +test_server: server_tcp_pairs + @echo "=== Запуск TCP-сервера (пары одинаковых символов) ===" + @echo "Выходной файл: out.txt" + ./server_tcp_pairs 5000 out.txt + +test_client: client_tcp_pairs + @echo "=== Запуск TCP-клиента ===" + @echo "Создаём input.txt" + @printf "aabbccddeeff\nabba\nxxxxx\n" > input.txt + ./client_tcp_pairs 127.0.0.1 5000 input.txt + +test_all: all + @echo "=== Автотест TCP (пары одинаковых символов) ===" + @printf "aabbccddeeff\nabba\nxxxxx\n" > input.txt + ./server_tcp_pairs 5000 out.txt & \ + SRV=$$!; \ + sleep 1; \ + ./client_tcp_pairs 127.0.0.1 5000 input.txt; \ + wait $$SRV; \ + echo "--- input.txt ---"; \ + cat input.txt; \ + echo "--- out.txt ---"; \ + cat out.txt + +clean: + @echo "Очистка..." + rm -f server_tcp_pairs client_tcp_pairs + rm -f input.txt out.txt + +help: + @echo "Targets:" + @echo " all - build server_tcp_pairs and client_tcp_pairs" + @echo " test_server - run server (port 5000, out.txt)" + @echo " test_client - run client to send input.txt" + @echo " test_all - end-to-end TCP test" + @echo " clean - remove binaries and test files" + @echo " help - show this help" + +.PHONY: all test_server test_client test_all clean help + diff --git a/vlad/lab_8/client.c b/vlad/lab_8/client.c new file mode 100644 index 0000000..79a7cd1 --- /dev/null +++ b/vlad/lab_8/client.c @@ -0,0 +1,139 @@ +// client_tcp_pairs.c +// TCP-клиент для лабораторной №8, вариант "во всех парах одинаковых символов +// второй символ заменить на пробел". +// Клиент читает входной файл и передаёт его содержимое серверу по TCP. + +#include // printf, fprintf, perror, FILE, fopen, fclose, fread +#include // exit, EXIT_FAILURE, strtol +#include // memset, strlen +#include // errno +#include // close, write +#include // sockaddr_in, inet_pton, htons +#include // socket, connect +#include // типы сокетов +#include // sockaddr_in + +#define BUF_SIZE 4096 + +int main(int argc, char *argv[]) +{ + // Ожидаемые аргументы: + // argv[1] - IP-адрес сервера (например, 127.0.0.1). + // argv[2] - порт сервера. + // argv[3] - путь к входному файлу. + if (argc < 4) { + fprintf(stderr, + "Usage: %s \n", + argv[0]); + fprintf(stderr, + "Example: %s 127.0.0.1 5000 input.txt\n", + argv[0]); + return -1; + } + + const char *server_ip = argv[1]; + const char *server_port = argv[2]; + const char *input_path = argv[3]; + + // Разбор порта. + char *endptr = NULL; + errno = 0; + long port_long = strtol(server_port, &endptr, 10); + if (errno != 0 || endptr == server_port || *endptr != '\0' || + port_long <= 0 || port_long > 65535) { + fprintf(stderr, "ERROR: invalid port '%s'\n", server_port); + return -1; + } + int port = (int)port_long; + + // Открываем входной файл. + FILE *fin = fopen(input_path, "r"); + if (!fin) { + perror("fopen(input_file)"); + return -1; + } + + // Создаём TCP-сокет. + int sockfd = socket(AF_INET, SOCK_STREAM, 0); + if (sockfd < 0) { + perror("socket"); + fclose(fin); + return -1; + } + + // Заполняем структуру адреса сервера. + struct sockaddr_in servaddr; + memset(&servaddr, 0, sizeof(servaddr)); + servaddr.sin_family = AF_INET; + servaddr.sin_port = htons(port); + + // Переводим строковый IP в бинарную форму. + if (inet_pton(AF_INET, server_ip, &servaddr.sin_addr) != 1) { + fprintf(stderr, "ERROR: invalid IP address '%s'\n", server_ip); + close(sockfd); + fclose(fin); + return -1; + } + + // Устанавливаем TCP-соединение. + if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { + perror("connect"); + close(sockfd); + fclose(fin); + return -1; + } + + printf("Connected to %s:%d, sending file '%s'\n", + server_ip, port, input_path); + + char buf[BUF_SIZE]; + + for (;;) { + // Читаем блок из файла. + size_t n = fread(buf, 1, sizeof(buf), fin); + if (n > 0) { + // Отправляем блок по TCP. + size_t sent_total = 0; + while (sent_total < n) { + ssize_t sent = write(sockfd, + buf + sent_total, + n - sent_total); + if (sent < 0) { + perror("write"); + close(sockfd); + fclose(fin); + return -1; + } + sent_total += (size_t)sent; + } + } + + // Если прочитали меньше, чем BUF_SIZE, либо EOF, либо ошибка. + if (n < sizeof(buf)) { + if (ferror(fin)) { + perror("fread"); + close(sockfd); + fclose(fin); + return -1; + } + break; // EOF, передачу завершаем. + } + } + + // Закрываем файл. + if (fclose(fin) == EOF) { + perror("fclose(input_file)"); + } + + // Корректно закрываем соединение, чтобы сервер получил EOF. + if (shutdown(sockfd, SHUT_WR) < 0) { + perror("shutdown"); + } + + close(sockfd); + + printf("File '%s' sent to %s:%d, connection closed.\n", + input_path, server_ip, port); + + return 0; +} diff --git a/vlad/lab_8/server.c b/vlad/lab_8/server.c new file mode 100644 index 0000000..b77c65d --- /dev/null +++ b/vlad/lab_8/server.c @@ -0,0 +1,189 @@ +// server_tcp_pairs.c +// Лабораторная №8, протокол TCP (нечётные варианты, но здесь по заданию TCP). +// Задача: во всех парах одинаковых символов второй символ заменить на пробел, +// считать количество замен и сохранить обработанный текст в выходной файл. + +#include // printf, fprintf, perror, FILE, fopen, fclose, fwrite +#include // exit, EXIT_FAILURE, strtol +#include // memset, strlen +#include // errno +#include // close, read, write +#include // sockaddr_in, inet_ntoa, htons, htonl, ntohs, ntohl +#include // socket, bind, listen, accept +#include // типы сокетов +#include // структура sockaddr_in + +#define BUF_SIZE 4096 // Размер буфера приёма из TCP-сокета. + +// Функция обрабатывает блок данных, заменяя во всех парах одинаковых подряд +// идущих символов второй символ пары на пробел. Пары могут пересекать границу +// буферов, поэтому учитывается последний символ предыдущего блока. +static void process_block_pairs(char *buf, + ssize_t len, + int *has_prev, + unsigned char *prev_char, + long long *repl_counter) { + if (!buf || !has_prev || !prev_char || !repl_counter) { + return; // Проверка входных указателей. + } + + for (ssize_t i = 0; i < len; i++) { + unsigned char c = (unsigned char) buf[i]; + + if (*has_prev) { + // Есть предыдущий символ из этого же потока. + if (c == *prev_char) { + // Найдена пара одинаковых подряд символов. + buf[i] = ' '; // Заменяем второй символ пары на пробел. + (*repl_counter)++; // Увеличиваем счётчик замен. + } + *has_prev = 0; // Сбрасываем флаг: пара уже обработана. + } else { + // Текущий символ станет предыдущим для возможной пары в следующем байте. + *prev_char = c; + *has_prev = 1; + } + } +} + +int main(int argc, char *argv[]) { + // Ожидаемые аргументы: + // argv[1] - порт TCP сервера (например, 5000). + // argv[2] - имя выходного файла. + if (argc < 3) { + fprintf(stderr, + "Usage: %s \n", + argv[0]); + fprintf(stderr, + "Example: %s 5000 out.txt\n", + argv[0]); + return -1; + } + + // Разбор номера порта. + char *endptr = NULL; + errno = 0; + long port_long = strtol(argv[1], &endptr, 10); + if (errno != 0 || endptr == argv[1] || *endptr != '\0' || + port_long <= 0 || port_long > 65535) { + fprintf(stderr, "ERROR: invalid port '%s'\n", argv[1]); + return -1; + } + int port = (int) port_long; + + const char *out_path = argv[2]; + + // Открываем выходной файл для записи (перезаписываем). + FILE *fout = fopen(out_path, "w"); + if (!fout) { + perror("fopen(output_file)"); + return -1; + } + + // Создаём TCP-сокет. + int listen_fd = socket(AF_INET, SOCK_STREAM, 0); + if (listen_fd < 0) { + perror("socket"); + fclose(fout); + return -1; + } + + // Включаем SO_REUSEADDR, чтобы можно было быстро перезапускать сервер. + int optval = 1; + if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, + &optval, sizeof(optval)) < 0) { + perror("setsockopt(SO_REUSEADDR)"); + close(listen_fd); + fclose(fout); + return -1; + } + + // Заполняем структуру адреса сервера. + struct sockaddr_in servaddr; + memset(&servaddr, 0, sizeof(servaddr)); + servaddr.sin_family = AF_INET; + servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // Принимать на всех интерфейсах. + servaddr.sin_port = htons(port); + + // Привязываем сокет к адресу и порту. + if (bind(listen_fd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) { + perror("bind"); + close(listen_fd); + fclose(fout); + return -1; + } + + // Переводим сокет в режим прослушивания. + if (listen(listen_fd, 1) < 0) { + perror("listen"); + close(listen_fd); + fclose(fout); + return -1; + } + + printf("TCP server (pairs) listening on port %d, output file: %s\n", + port, out_path); + + // Принимаем одно соединение от клиента. + struct sockaddr_in cliaddr; + socklen_t cli_len = sizeof(cliaddr); + int conn_fd = accept(listen_fd, (struct sockaddr *) &cliaddr, &cli_len); + if (conn_fd < 0) { + perror("accept"); + close(listen_fd); + fclose(fout); + return -1; + } + + printf("Client connected from %s:%d\n", + inet_ntoa(cliaddr.sin_addr), + ntohs(cliaddr.sin_port)); + + close(listen_fd); // Больше новых клиентов не принимаем. + + char buf[BUF_SIZE]; + long long repl_counter = 0; // Общее количество замен. + int has_prev = 0; // Был ли предыдущий символ для пары. + unsigned char prev_char = 0; // Предыдущий символ, если has_prev == 1. + + for (;;) { + // Считываем данные из TCP-сокета. + ssize_t n = read(conn_fd, buf, sizeof(buf)); + if (n > 0) { + // Обрабатываем блок на предмет пар одинаковых символов. + process_block_pairs(buf, n, &has_prev, &prev_char, &repl_counter); + + // Пишем обработанный блок в выходной файл. + if (fwrite(buf, 1, (size_t) n, fout) != (size_t) n) { + perror("fwrite"); + repl_counter = -1; + break; + } + } else if (n == 0) { + // Клиент корректно закрыл соединение. + printf("Client disconnected.\n"); + break; + } else { + // Ошибка при чтении. + perror("read"); + repl_counter = -1; + break; + } + } + + // Закрываем соединение. + close(conn_fd); + + // Закрываем файл. + if (fclose(fout) == EOF) { + perror("fclose(output_file)"); + repl_counter = -1; + } + + printf("Total replacements (pairs): %lld\n", repl_counter); + if (repl_counter < 0) { + return -1; + } + + return 0; +}