Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Сетевой калькулятор с переменными X, Y, Z

Лабораторная работа: TCP клиент-серверное приложение с поддержкой пользовательских переменных X, Y, Z.

Сервер реализован как однопроцессное однопоточное приложение. Для одновременной работы с несколькими клиентами используются неблокирующие сокеты и системный вызов poll(2).


1. Описание проекта

Проект состоит из двух частей:

  • клиент — консольная программа, через которую пользователь вводит команды;
  • сервер — программа, которая принимает TCP-подключения, хранит значения переменных пользователей и вычисляет выражения.

Клиент не выполняет вычисления самостоятельно. Он только отправляет команды на сервер и выводит полученный ответ.

Сервер для каждого пользователя хранит значения трёх переменных:

X, Y, Z

Значения переменных сохраняются между подключениями пользователя. То есть если пользователь Danil задал X = 24, отключился, а потом снова подключился под именем Danil, сервер восстановит значение X.


2. Архитектура

Общая архитектура приложения:

+-------------------+              TCP              +----------------------+
|                   | <---------------------------> |                      |
|      Клиент       |                               |        Сервер        |
|                   |                               |                      |
+-------------------+                               +----------------------+
        |                                                       |
        | ввод команд                                           |
        |                                                       |
        | "X = 3 * 8"                                           |
        |------------------------------------------------------>|
        |                                                       |
        |                                   разбор выражения    |
        |                                   вычисление          |
        |                                   сохранение X        |
        |                                                       |
        | "OK X = 24"                                           |
        |<------------------------------------------------------|

Файлы проекта:

netcalc-lab/
├── CMakeLists.txt
└── src/
    ├── main.cpp          # точка входа, выбор режима server/client
    ├── server.hpp        # описание класса сервера
    ├── server.cpp        # сервер, poll(2), неблокирующие сокеты
    ├── client.hpp        # описание клиента
    ├── client.cpp        # консольный клиент
    ├── expression.hpp    # описание вычислителя выражений
    └── expression.cpp    # разбор и вычисление арифметических выражений

3. Сетевой протокол

3.1. Общая идея протокола

Протокол прикладного уровня построен поверх TCP.

Формат обмена — текстовый. Каждое сообщение передаётся отдельной строкой и заканчивается символом перевода строки:

\n

Клиент отправляет серверу команды в виде строк. Сервер отвечает строкой с результатом.

Общий формат ответа сервера:

OK <результат>

или

ERR <описание ошибки>

Также при завершении работы сервер может отправить:

BYE

3.2. Начало работы

После подключения сервер отправляет клиенту приветствие:

HELLO enter username

Клиент отправляет имя пользователя:

Danil

Сервер создаёт нового пользователя или загружает уже существующие значения переменных и отвечает:

OK user Danil X=0 Y=0 Z=0

3.3. Команды клиента

Присваивание переменной

Формат:

X = <выражение>
Y = <выражение>
Z = <выражение>

Пример:

X = 3 * 8

Ответ:

OK X = 24

Вычисление выражения без изменения переменных

Формат:

вычислить <выражение>

Пример:

вычислить X + 1

Ответ:

OK 25

Просмотр переменных

Формат:

показать

Ответ:

OK X=24 Y=0 Z=0

Завершение работы клиента

Формат:

выход

Ответ:

BYE

Тест производительности

Для проверки производительности без разбора выражений используется команда:

PING

Ответ:

PONG

Эта команда нужна, чтобы измерять скорость сетевого обмена без затрат на вычисления и проверку корректности выражений.


4. Диаграмма последовательностей

Диаграмма взаимодействия клиента и сервера:

sequenceDiagram
    actor User as Пользователь
    participant Client as Клиент
    participant Server as Сервер калькулятора

    User->>Client: Запуск клиента
    Client->>Server: TCP connect 127.0.0.1:4321
    Server-->>Client: HELLO enter username

    User->>Client: Ввод имени Danil
    Client->>Server: Danil
    Server-->>Client: OK user Danil X=0 Y=0 Z=0

    User->>Client: X = 3 * 8
    Client->>Server: X = 3 * 8
    Server->>Server: Разбор выражения
    Server->>Server: Вычисление результата
    Server->>Server: Сохранение X = 24
    Server-->>Client: OK X = 24
    Client-->>User: SERVER: OK X = 24

    User->>Client: вычислить X + 1
    Client->>Server: вычислить X + 1
    Server->>Server: Подстановка X = 24
    Server->>Server: Вычисление 24 + 1
    Server-->>Client: OK 25
    Client-->>User: SERVER: OK 25

    User->>Client: показать
    Client->>Server: показать
    Server-->>Client: OK X=24 Y=0 Z=0
    Client-->>User: SERVER: OK X=24 Y=0 Z=0

    User->>Client: выход
    Client->>Server: выход
    Server-->>Client: BYE
    Client-->>User: SERVER: BYE
    Client->>Server: Закрытие соединения
Loading

Или короче эту диаграмму можно представить так:

Пользователь          Клиент                         Сервер
     |                  |                              |
     | запуск           |                              |
     |----------------->|                              |
     |                  | TCP connect                  |
     |                  |----------------------------->|
     |                  | HELLO enter username         |
     |                  |<-----------------------------|
     | имя Danil        |                              |
     |----------------->| Danil                        |
     |                  |----------------------------->|
     |                  | OK user Danil X=0 Y=0 Z=0    |
     |                  |<-----------------------------|
     | X = 3 * 8        |                              |
     |----------------->| X = 3 * 8                    |
     |                  |----------------------------->|
     |                  | OK X = 24                    |
     |                  |<-----------------------------|
     | вычислить X + 1  |                              |
     |----------------->| вычислить X + 1              |
     |                  |----------------------------->|
     |                  | OK 25                        |
     |                  |<-----------------------------|
     | показать         |                              |
     |----------------->| показать                     |
     |                  |----------------------------->|
     |                  | OK X=24 Y=0 Z=0              |
     |                  |<-----------------------------|
     | выход            |                              |
     |----------------->| выход                        |
     |                  |----------------------------->|
     |                  | BYE                          |
     |                  |<-----------------------------|

5. Неблокирующий ввод-вывод и poll(2)

Требование лабораторной работы:

Сервер должен быть однопроцессным и однопоточным, использовать неблокирующие сокеты и poll(2).

Это реализовано в файле:

src/server.cpp

5.1. Подключение poll

В серверной части используется заголовочный файл:

#include <poll.h>

5.2. Перевод сокетов в неблокирующий режим

Для перевода сокета в неблокирующий режим используется функция fcntl и флаг O_NONBLOCK:

void setNonBlocking(int fd)
{
    int flags = fcntl(fd, F_GETFL, 0);
    if (flags == -1)
    {
        throw std::runtime_error("fcntl(F_GETFL) failed");
    }
    if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
    {
        throw std::runtime_error("fcntl(F_SETFL) failed");
    }
}

Эта функция вызывается для слушающего сокета сервера и для каждого нового клиентского сокета.

5.3. Главный цикл сервера

Сервер формирует список файловых дескрипторов и передаёт его в poll:

std::vector<pollfd> fds;
fds.push_back({listenFd, POLLIN, 0});

for (auto &[fd, client] : clients)
{
    short events = POLLIN;
    if (!client.output.empty())
    {
        events |= POLLOUT;
    }
    fds.push_back({fd, events, 0});
}

int rc = poll(fds.data(), fds.size(), -1);

poll сообщает серверу, какие сокеты готовы к чтению или записи.

Если готов слушающий сокет, сервер принимает нового клиента.
Если готов клиентский сокет, сервер читает команду или отправляет накопленный ответ.

5.4. Почему сервер однопроцессный и однопоточный

В сервере не используются:

fork()
std::thread
pthread_create()

Все клиенты обслуживаются одним циклом poll.


6. Сборка проекта

mkdir -p build
cd build
cmake ..
cmake --build .

7. Запуск

В первом терминале запустить сервер:

./netcalc server 4321

Во втором терминале запустить клиента:

./netcalc client 127.0.0.1 4321

8. Пример работы

SERVER: HELLO enter username
Введите имя пользователя: Danil
SERVER: OK user Danil X=0 Y=0 Z=0

Команды: X = 1+2, вычислить X*3, показать, выход

> X = 3*8
SERVER: OK X = 24

> вычислить X+1
SERVER: OK 25

> показать
SERVER: OK X=24 Y=0 Z=0

> выход
SERVER: BYE

9. Проверка ошибок

Пример деления на ноль:

> X = 10 / 0
SERVER: ERR division by zero

Пример неизвестной переменной:

> вычислить A + 1
SERVER: ERR unknown variable

10. Проверка производительности

Для тестирования производительности используется команда PING, которая не выполняет разбор и вычисление выражений.

Запуск клиента в режиме тестирования:

./netcalc client 127.0.0.1 4321 --perf 100000

Пример результата:

Requests: 100000
Time: 1.25 sec
RPS: 80000
Average latency: 0.0125 ms

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages