server0451

Форк
0
/
main.c 
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
// Чтение опций командной строки и их аргументов.
42
void opt_handle(int32_t argc, char *argv[],
43
                int32_t *port,
44
                char *password, size_t password_buf_size,
45
                bool *oneshot_mode, 
46
                uint32_t *verbosity_level,
47
                bool *print_help_page);
48

49
// Завершение связи с клиентом.
50
void finish_communication(int32_t fd, uint32_t attempts, uint32_t pause, uint32_t verbosity_level);
51

52
// Вывод из буфера (для использования внутри цикла).
53
void flush_all();
54

55

56
/******************** ФУНКЦИИ *******************/
57

58
int32_t main(int32_t argc, char *argv[])
59
{
60
    /*--- Чтение и обработка опций командной строки и их аргументов ---*/
61

62
    // Переменные для хранения значений, переданных из командной строки.
63
    int32_t port = -1;      // По умолчанию задано невалидное значение.
64
    char password[STR_MAX_LEN + 1] = {0};
65
    bool oneshot_mode = 0;  // Флаг запуска в тестовом режиме (oneshot).
66
    uint32_t verbosity_level = 0;
67
    bool print_help_page = 0;
68

69
    // Чтение опций командной строки и их аргументов.
70
    opt_handle(argc, argv, &port, password, sizeof(password), &oneshot_mode, &verbosity_level, &print_help_page);
71

72
    if (print_help_page) {
73
        PRINT_HELP_PAGE;
74
        exit(0);
75
    }
76

77
    // Проверка валидности значений, переданных из командной строки.
78
    if (port <= 0) {
79
        fprintf(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");
81
        exit(1);
82
    }
83

84
    if (strlen(password) < PASSWORD_MIN_LEN || strlen(password) > PASSWORD_MAX_LEN) {
85
        fprintf(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");
88
        exit(1);
89
    }
90

91
    if (verbosity_level > 0) {
92
        printf("\n\n"
93
               "* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\n"
94
               "                        Starting TCP IoT server\n"
95
               "Server started at port %u. ", port);
96
        timestamp_print();
97
        printf("\n"
98
               "* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\n");
99
    }
100

101

102
    /*--- Работа с сокетами ---*/
103

104
    // Отключение сигнала SIGPIPE на случай, если клиент отключится до вызова функции write().
105
    sockets_sig_ign();
106

107
    // Создание и подготовка "слушающего" сокета.
108
    int32_t sockfd = -1;  // По умолчанию задано невалидное значение.
109
    uint32_t sockets_init_retval = sockets_init(&sockfd, port, SOCKET_BACKLOG, verbosity_level);
110
    switch (sockets_init_retval) {
111
        case SOCKETS_INIT_ERR_CREATE:
112
            fprintf(stderr, "\n"
113
                            "Error: socket creation failed. Program terminated.\n"
114
                            "Error description: %s\n",
115
                            strerror(errno));
116
            exit(1);
117
            break;        // По сути не нужно после вызова exit(), но здесь и далее присутствует для единообразия.
118
        case SOCKETS_INIT_ERR_SETSOCKOPT:
119
            fprintf(stderr, "\n"
120
                            "Error: setting socket option failed. Program terminated.\n"
121
                            "Error description: %s\n",
122
                            strerror(errno));
123
            exit(1);
124
            break;
125
        case SOCKETS_INIT_ERR_BIND:
126
            fprintf(stderr, "\n"
127
                            "Error: socket binding failed. Program terminated.\n"
128
                            "Error description: %s\n",
129
                            strerror(errno));
130
            exit(1);
131
            break;
132
        case SOCKETS_INIT_ERR_LISTEN:
133
            fprintf(stderr, "\n"
134
                            "Error: entering a listen mode failed. Program terminated.\n"
135
                            "Error description: %s\n",
136
                            strerror(errno));
137
            exit(1);
138
            break;
139
        case SOCKETS_INIT_OK:
140
            // Успешная инициализация "слушающего" сокета.
141
            break;
142
        default:
143
            // Ничего не делаем, отдаём дань MISRA.
144
            break;
145
    }
146

147
    // Сокет текущего соединения.
148
    int32_t connfd = -1;  // По умолчанию задано невалидное значение.
149

150
    /* Установка соединения и обмен данными.
151
     * В тестовом режиме (oneshot) следующий блок кода выполнится единожды,
152
     * в основном режиме (loop) он будет выполняться циклически.
153
     */
154
    do {
155
        flush_all();
156

157
        uint32_t sockets_proceed_retval = sockets_proceed(sockfd, &connfd, SELECT_TIMEOUT_SEC, verbosity_level);
158
        switch (sockets_proceed_retval) {
159
            case SOCKETS_PROCEED_ERR_ACCEPT:
160
                fprintf(stderr, "\n"
161
                                "Error: server failed to accept a client. "
162
                                "Keeps listening for a next connection.\n"
163
                                "Error description: %s\n",
164
                                strerror(errno));
165
                continue;
166
                break;
167
            case SOCKETS_PROCEED_ERR_SELECT:
168
                fprintf(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",
172
                                strerror(errno));
173
                continue;
174
                break;
175
            case SOCKETS_PROCEED_TIMEOUT:
176
                finish_communication(connfd, SOCKET_GRACEFUL_CLOSE_ATTEMPTS, SOCKET_CLOSE_PAUSE, verbosity_level);
177
                continue;
178
                break;
179
            case SOCKETS_PROCEED_OK:
180
                // Связь с клиентом успешно установлена, клиент направил данные через сокет.
181
                break;
182
            default:
183
                // Ничего не делаем, отдаём дань MISRA.
184
                break;
185
        }
186
        flush_all();
187

188
        /* Исполнение не дойдёт досюда, пока не будет установлена связь
189
         * с клиентом и тот не направит данные через сокет.
190
         */
191
        char buf[STR_MAX_LEN + 1] = {0};
192
        sockets_read_message(connfd, buf, sizeof(buf), verbosity_level);
193
        flush_all();
194

195

196
        /*--- Проверка формата сообщения от клиента ---*/
197

198
        char resulting_pattern[STR_MAX_LEN * 2 + 1] = {0};
199
        strcpy(resulting_pattern, password);
200
        strcat(resulting_pattern, MSG_FORMAT_REGEX_PATTERN);
201

202
        uint32_t msg_format_check_retval = msg_format_check(buf, resulting_pattern);
203
        switch (msg_format_check_retval) {
204
            case MSG_FORMAT_CHECK_REGEX_COMP_FAIL:
205
                fprintf(stderr, "\nError: message format check failed - error compiling regex.\n");
206
                strcpy(buf, "Error: message format check failed - error compiling regex.");
207
                sockets_write_message(connfd, buf, 0);
208
                break;
209
            case MSG_FORMAT_CHECK_NO_MATCH:
210
                fprintf(stderr, "\nError: message format check failed - no match found.\n");
211
                strcpy(buf, "Error: message format check failed - no match found.");
212
                sockets_write_message(connfd, buf, 0);
213
                break;
214
            case MSG_FORMAT_CHECK_PARTIAL_MATCH:
215
                fprintf(stderr, "\nError: message format check failed - partial match.\n");
216
                strcpy(buf, "Error: message format check failed - partial match.");
217
                sockets_write_message(connfd, buf, 0);
218
                break;
219
            case MSG_FORMAT_CHECK_MATCH:
220
                // Проверка формата сообщения пройдена успешно.
221
                break;
222
            default:
223
                // Ничего не делаем, отдаём дань MISRA.
224
                break;
225
        }
226
        flush_all();
227

228

229
        /*--- Обработка поступившей команды ---*/
230

231
        uint32_t cmd_handle_retval = cmd_handle(connfd, buf, verbosity_level);
232
        switch (cmd_handle_retval) {
233
            case CMD_ERR_EXTRACT:
234
                /* По идее после предыдущей проверки такого быть не должно,
235
                 * но на всякий случай здесь вшита собственная проверка.
236
                 */
237
                fprintf(stderr, "\nError: invalid message format.\n");
238
                strcpy(buf, "Error: invalid message format.");
239
                sockets_write_message(connfd, buf, 0);
240
                break;
241
            case CMD_ERR_TOGGLE:
242
                fprintf(stderr, "\nError: can't toggle current load state (invalid data in the topic).\n");
243
                strcpy(buf, "Error: can't toggle current load state (invalid data in the topic).");
244
                sockets_write_message(connfd, buf, 0);
245
                break;
246
            case CMD_ERR_NO_VALID_COMMAND:
247
                fprintf(stderr, "\nError: no valid command received.\n");
248
                strcpy(buf, "Error: no valid command received.");
249
                sockets_write_message(connfd, buf, 0);
250
                break;
251
            case CMD_OK:
252
                // Поступившая команда обработана успешно.
253
                break;
254
            default:
255
                // Ничего не делаем, отдаём дань MISRA.
256
                break;
257
        }
258
        flush_all();
259

260

261
        /*--- Завершение коммуникации с очередным клиентом ---*/
262

263
        finish_communication(connfd, SOCKET_GRACEFUL_CLOSE_ATTEMPTS, SOCKET_CLOSE_PAUSE, verbosity_level);
264
        flush_all();
265
    }
266
    while (!oneshot_mode);
267

268
    /* Закрытие слушающего сокета.
269
     * Исполнение программы доходит досюда только в режиме одиночного прогона.
270
     */
271
    finish_communication(sockfd, SOCKET_GRACEFUL_CLOSE_ATTEMPTS, SOCKET_CLOSE_PAUSE, 0);
272

273
    return 0;
274
}
275

276
void opt_handle(int32_t argc, char *argv[],
277
                int32_t *port,
278
                char *password, size_t password_buf_size,
279
                bool *oneshot_mode,
280
                uint32_t *verbosity_level,
281
                bool *print_help_page)
282
{
283
    int32_t opt = 0;
284
    while ((opt = getopt(argc, argv, "p:P:ovVh")) >= 0) {
285
        switch (opt) {
286
            // Обязательная опция, принимает в качестве аргумента номер порта.
287
            case 'p':
288
                *port = strtol(optarg, NULL, 10);
289
                break;
290

291
            // Обязательная опция, принимает в качестве аргумента строку с паролем.
292
            case 'P':
293
                if (strlen(optarg) < password_buf_size) {
294
                    strcpy(password, optarg);
295
                }
296
                break;
297

298
            case 'o':
299
                *oneshot_mode = 1;
300
                break;
301

302
            case 'v':
303
                *verbosity_level = 1;
304
                break;
305

306
            case 'V':
307
                *verbosity_level = 2;
308
                break;
309

310
            case 'h':
311
                *print_help_page = 1;
312
                break;
313

314
            default:
315
                // Ничего не делаем, отдаём дань MISRA.
316
                break;
317
        }
318
    }
319
}
320

321
void finish_communication(int32_t fd, uint32_t attempts, uint32_t pause, uint32_t verbosity_level)
322
{
323
    if (verbosity_level == 0) {
324
        if (sockets_close(fd, pause) != 0) {
325
            fprintf(stderr, "\n"
326
                    "Communication closed ungracefully.\n"
327
                    "----------------------------------\n");         
328
        }
329
        return;
330
    }
331

332
    if (verbosity_level == 1) {
333
        if (sockets_close(fd, pause) == 0) {
334
            printf("\n"
335
                   "Communication closed gracefully.\n"
336
                   "--------------------------------\n");   
337
        } else {
338
            fprintf(stderr, "\n"
339
                    "Communication closed ungracefully.\n"
340
                    "----------------------------------\n");         
341
        }
342
        return;
343
    }
344

345
    for (uint32_t i = 1; i <= attempts; ++i) {
346
        if (sockets_close(fd, pause) != 0) {
347
            fprintf(stderr, "\n"
348
                            "Closing socket failed on attempt %d of %d, status: %s\n",
349
                            i, attempts, strerror(errno));
350
        } else {
351
            printf("\n"
352
                   "Communication closed gracefully on attempt %d.\n"
353
                   "-----------------------------------------------\n",
354
                   i);
355

356
            return;
357
        }
358
    }
359
    fprintf(stderr, "\n"
360
                    "Communication closed ungracefully.\n"
361
                    "----------------------------------\n");
362
}
363

364
void flush_all()
365
{
366
    /* Если в программе есть бесконечный цикл, то по умолчанию выводимые в цикле данные
367
     * будут копиться в буфере, пока не будет дана команда на вывод из буфера
368
     * или он не заполнится до определённого значения.
369
     */
370
     fflush(stdin);
371
     fflush(stdout);
372
}
373

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.