server0451
372 строки · 15.2 Кб
1/******************* ОПИСАНИЕ *******************/
2
3/**
4* Имя файла: main.c
5* ----------------------------------------------------------------------------|---------------------------------------|
6* Назначение: основной файл с исходным кодом простого TCP-сервера IoT
7* для Linux, написанным на языке Си.
8* ----------------------------------------------------------------------------|---------------------------------------|
9* Примечания:
10*/
11
12
13/************ ДИРЕКТИВЫ ПРЕПРОЦЕССОРА ***********/
14
15/*--- Включения ---*/
16
17// Из стандартной библиотеки языка Си.
18#include <stdio.h>19#include <inttypes.h>20#include <stdbool.h>21#include <string.h>22#include <stdlib.h>23#include <errno.h>24
25// Из библиотек POSIX.
26#include <unistd.h>27
28// Настройки.
29#include "config_general.h"30
31// Локальные модули.
32#include "sockets.h"33#include "cmd.h"34#include "msg_format_check.h"35#include "timestamp.h"36#include "help_page.h"37
38
39/*************** ПРОТОТИПЫ ФУНКЦИЙ **************/
40
41// Чтение опций командной строки и их аргументов.
42void opt_handle(int32_t argc, char *argv[],43int32_t *port,44char *password, size_t password_buf_size,45bool *oneshot_mode,46uint32_t *verbosity_level,47bool *print_help_page);48
49// Завершение связи с клиентом.
50void finish_communication(int32_t fd, uint32_t attempts, uint32_t pause, uint32_t verbosity_level);51
52// Вывод из буфера (для использования внутри цикла).
53void flush_all();54
55
56/******************** ФУНКЦИИ *******************/
57
58int32_t main(int32_t argc, char *argv[])59{
60/*--- Чтение и обработка опций командной строки и их аргументов ---*/61
62// Переменные для хранения значений, переданных из командной строки.63int32_t port = -1; // По умолчанию задано невалидное значение.64char password[STR_MAX_LEN + 1] = {0};65bool oneshot_mode = 0; // Флаг запуска в тестовом режиме (oneshot).66uint32_t verbosity_level = 0;67bool print_help_page = 0;68
69// Чтение опций командной строки и их аргументов.70opt_handle(argc, argv, &port, password, sizeof(password), &oneshot_mode, &verbosity_level, &print_help_page);71
72if (print_help_page) {73PRINT_HELP_PAGE;74exit(0);75}76
77// Проверка валидности значений, переданных из командной строки.78if (port <= 0) {79fprintf(stderr, "Error: invalid port specified. Program terminated.\n"80"Please restart the program and insert a valid port number as a -p option argument.\n");81exit(1);82}83
84if (strlen(password) < PASSWORD_MIN_LEN || strlen(password) > PASSWORD_MAX_LEN) {85fprintf(stderr, "Error: invalid password specified. Program terminated.\n"86"Please restart the program and insert a valid password as a -P option argument.\n"87"Password must consist of 5 to 40 ASCII alphanumerics.\n");88exit(1);89}90
91if (verbosity_level > 0) {92printf("\n\n"93"* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\n"94" Starting TCP IoT server\n"95"Server started at port %u. ", port);96timestamp_print();97printf("\n"98"* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\n");99}100
101
102/*--- Работа с сокетами ---*/103
104// Отключение сигнала SIGPIPE на случай, если клиент отключится до вызова функции write().105sockets_sig_ign();106
107// Создание и подготовка "слушающего" сокета.108int32_t sockfd = -1; // По умолчанию задано невалидное значение.109uint32_t sockets_init_retval = sockets_init(&sockfd, port, SOCKET_BACKLOG, verbosity_level);110switch (sockets_init_retval) {111case SOCKETS_INIT_ERR_CREATE:112fprintf(stderr, "\n"113"Error: socket creation failed. Program terminated.\n"114"Error description: %s\n",115strerror(errno));116exit(1);117break; // По сути не нужно после вызова exit(), но здесь и далее присутствует для единообразия.118case SOCKETS_INIT_ERR_SETSOCKOPT:119fprintf(stderr, "\n"120"Error: setting socket option failed. Program terminated.\n"121"Error description: %s\n",122strerror(errno));123exit(1);124break;125case SOCKETS_INIT_ERR_BIND:126fprintf(stderr, "\n"127"Error: socket binding failed. Program terminated.\n"128"Error description: %s\n",129strerror(errno));130exit(1);131break;132case SOCKETS_INIT_ERR_LISTEN:133fprintf(stderr, "\n"134"Error: entering a listen mode failed. Program terminated.\n"135"Error description: %s\n",136strerror(errno));137exit(1);138break;139case SOCKETS_INIT_OK:140// Успешная инициализация "слушающего" сокета.141break;142default:143// Ничего не делаем, отдаём дань MISRA.144break;145}146
147// Сокет текущего соединения.148int32_t connfd = -1; // По умолчанию задано невалидное значение.149
150/* Установка соединения и обмен данными.151* В тестовом режиме (oneshot) следующий блок кода выполнится единожды,
152* в основном режиме (loop) он будет выполняться циклически.
153*/
154do {155flush_all();156
157uint32_t sockets_proceed_retval = sockets_proceed(sockfd, &connfd, SELECT_TIMEOUT_SEC, verbosity_level);158switch (sockets_proceed_retval) {159case SOCKETS_PROCEED_ERR_ACCEPT:160fprintf(stderr, "\n"161"Error: server failed to accept a client. "162"Keeps listening for a next connection.\n"163"Error description: %s\n",164strerror(errno));165continue;166break;167case SOCKETS_PROCEED_ERR_SELECT:168fprintf(stderr, "\n"169"Error: server failed to find a socket available for reading. "170"Keeps listening for a next connection.\n"171"Error description: %s\n",172strerror(errno));173continue;174break;175case SOCKETS_PROCEED_TIMEOUT:176finish_communication(connfd, SOCKET_GRACEFUL_CLOSE_ATTEMPTS, SOCKET_CLOSE_PAUSE, verbosity_level);177continue;178break;179case SOCKETS_PROCEED_OK:180// Связь с клиентом успешно установлена, клиент направил данные через сокет.181break;182default:183// Ничего не делаем, отдаём дань MISRA.184break;185}186flush_all();187
188/* Исполнение не дойдёт досюда, пока не будет установлена связь189* с клиентом и тот не направит данные через сокет.
190*/
191char buf[STR_MAX_LEN + 1] = {0};192sockets_read_message(connfd, buf, sizeof(buf), verbosity_level);193flush_all();194
195
196/*--- Проверка формата сообщения от клиента ---*/197
198char resulting_pattern[STR_MAX_LEN * 2 + 1] = {0};199strcpy(resulting_pattern, password);200strcat(resulting_pattern, MSG_FORMAT_REGEX_PATTERN);201
202uint32_t msg_format_check_retval = msg_format_check(buf, resulting_pattern);203switch (msg_format_check_retval) {204case MSG_FORMAT_CHECK_REGEX_COMP_FAIL:205fprintf(stderr, "\nError: message format check failed - error compiling regex.\n");206strcpy(buf, "Error: message format check failed - error compiling regex.");207sockets_write_message(connfd, buf, 0);208break;209case MSG_FORMAT_CHECK_NO_MATCH:210fprintf(stderr, "\nError: message format check failed - no match found.\n");211strcpy(buf, "Error: message format check failed - no match found.");212sockets_write_message(connfd, buf, 0);213break;214case MSG_FORMAT_CHECK_PARTIAL_MATCH:215fprintf(stderr, "\nError: message format check failed - partial match.\n");216strcpy(buf, "Error: message format check failed - partial match.");217sockets_write_message(connfd, buf, 0);218break;219case MSG_FORMAT_CHECK_MATCH:220// Проверка формата сообщения пройдена успешно.221break;222default:223// Ничего не делаем, отдаём дань MISRA.224break;225}226flush_all();227
228
229/*--- Обработка поступившей команды ---*/230
231uint32_t cmd_handle_retval = cmd_handle(connfd, buf, verbosity_level);232switch (cmd_handle_retval) {233case CMD_ERR_EXTRACT:234/* По идее после предыдущей проверки такого быть не должно,235* но на всякий случай здесь вшита собственная проверка.
236*/
237fprintf(stderr, "\nError: invalid message format.\n");238strcpy(buf, "Error: invalid message format.");239sockets_write_message(connfd, buf, 0);240break;241case CMD_ERR_TOGGLE:242fprintf(stderr, "\nError: can't toggle current load state (invalid data in the topic).\n");243strcpy(buf, "Error: can't toggle current load state (invalid data in the topic).");244sockets_write_message(connfd, buf, 0);245break;246case CMD_ERR_NO_VALID_COMMAND:247fprintf(stderr, "\nError: no valid command received.\n");248strcpy(buf, "Error: no valid command received.");249sockets_write_message(connfd, buf, 0);250break;251case CMD_OK:252// Поступившая команда обработана успешно.253break;254default:255// Ничего не делаем, отдаём дань MISRA.256break;257}258flush_all();259
260
261/*--- Завершение коммуникации с очередным клиентом ---*/262
263finish_communication(connfd, SOCKET_GRACEFUL_CLOSE_ATTEMPTS, SOCKET_CLOSE_PAUSE, verbosity_level);264flush_all();265}266while (!oneshot_mode);267
268/* Закрытие слушающего сокета.269* Исполнение программы доходит досюда только в режиме одиночного прогона.
270*/
271finish_communication(sockfd, SOCKET_GRACEFUL_CLOSE_ATTEMPTS, SOCKET_CLOSE_PAUSE, 0);272
273return 0;274}
275
276void opt_handle(int32_t argc, char *argv[],277int32_t *port,278char *password, size_t password_buf_size,279bool *oneshot_mode,280uint32_t *verbosity_level,281bool *print_help_page)282{
283int32_t opt = 0;284while ((opt = getopt(argc, argv, "p:P:ovVh")) >= 0) {285switch (opt) {286// Обязательная опция, принимает в качестве аргумента номер порта.287case 'p':288*port = strtol(optarg, NULL, 10);289break;290
291// Обязательная опция, принимает в качестве аргумента строку с паролем.292case 'P':293if (strlen(optarg) < password_buf_size) {294strcpy(password, optarg);295}296break;297
298case 'o':299*oneshot_mode = 1;300break;301
302case 'v':303*verbosity_level = 1;304break;305
306case 'V':307*verbosity_level = 2;308break;309
310case 'h':311*print_help_page = 1;312break;313
314default:315// Ничего не делаем, отдаём дань MISRA.316break;317}318}319}
320
321void finish_communication(int32_t fd, uint32_t attempts, uint32_t pause, uint32_t verbosity_level)322{
323if (verbosity_level == 0) {324if (sockets_close(fd, pause) != 0) {325fprintf(stderr, "\n"326"Communication closed ungracefully.\n"327"----------------------------------\n");328}329return;330}331
332if (verbosity_level == 1) {333if (sockets_close(fd, pause) == 0) {334printf("\n"335"Communication closed gracefully.\n"336"--------------------------------\n");337} else {338fprintf(stderr, "\n"339"Communication closed ungracefully.\n"340"----------------------------------\n");341}342return;343}344
345for (uint32_t i = 1; i <= attempts; ++i) {346if (sockets_close(fd, pause) != 0) {347fprintf(stderr, "\n"348"Closing socket failed on attempt %d of %d, status: %s\n",349i, attempts, strerror(errno));350} else {351printf("\n"352"Communication closed gracefully on attempt %d.\n"353"-----------------------------------------------\n",354i);355
356return;357}358}359fprintf(stderr, "\n"360"Communication closed ungracefully.\n"361"----------------------------------\n");362}
363
364void flush_all()365{
366/* Если в программе есть бесконечный цикл, то по умолчанию выводимые в цикле данные367* будут копиться в буфере, пока не будет дана команда на вывод из буфера
368* или он не заполнится до определённого значения.
369*/
370fflush(stdin);371fflush(stdout);372}
373