/
alevadkal
/
basic_netlab
Обзор
Документация
Войти
/
alevadkal
/
basic_netlab
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
server/server.cpp
297 строк
13 KB
alevadkal
Add chat
03 июн 2025, 23:45
03 июн 2025, 23:45
e0a77df
Код
Авторство
О чём код?
// Copyright (c) 2025 Александр Вадимович Калюжный // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED // “AS IS”AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE // INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO // EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR // CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF // USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER // TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE // OF THIS SOFTWARE. // Разрешение на использование, копирование, модификацию и/или распространение // этого программного обеспечения для любой цели с оплатой или без таковой // настоящим предоставляется. ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ ПРЕДОСТАВЛЯЕТСЯ «КАК // ЕСТЬ», А АВТОР ОТКАЗЫВАЕТСЯ ОТ ВСЕХ ГАРАНТИЙ В ОТНОШЕНИИ ДАННОГО // ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ, ВКЛЮЧАЯ ВСЕ ПОДРАЗУМЕВАЕМЫЕ ГАРАНТИИ ТОВАРНОЙ // ПРИГОДНОСТИ И СООТВЕТСТВИЯ ЦЕЛИ. НИ ПРИ КАКИХ ОБСТОЯТЕЛЬСТВАХ АВТОР НЕ НЕСЁТ // ОТВЕТСТВЕННОСТИ ЗА ЛЮБЫЕ СПЕЦИАЛЬНЫЕ, ПРЯМЫЕ, КОСВЕННЫЕ ИЛИ ПОСЛЕДУЮЩИЕ // УБЫТКИ ИЛИ ЛЮБЫЕ УБЫТКИ ВООБЩЕ,ВЫТЕКАЮЩИЕ ИЗ ИСПОЛЬЗОВАНИЯ, ДАННЫХ ИЛИ // ПРИБЫЛИ, ПОНЕСЕННЫЕ ИЗ-ЗА ДЕЙСТВИЙ ПО ДОГОВОРУ, НЕБРЕЖНОСТИ ИЛИ ИНОГО // ПРАВОНАРУШЕНИЯ, ВОЗНИКШИЕ В СВЯЗИ С ИСПОЛЬЗОВАНИЕМ ИЛИ РАБОТОЙ ДАННОГО // ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ. #include <arpa/inet.h> #include <cerrno> #include <cstring> #include <iostream> #include <map> #include <netinet/in.h> #include <string> #include <sys/socket.h> #include <thread> #include <unistd.h> const int PORT = 1234; const int BUFFER_SIZE = 1024; #define EPIC_TIMEOUT usleep(300000) // для наглядоности логов //#define EPIC_TIMEOUT ({}) class ServerDataSingleton { private: std::mutex lock; std::map<int, std::string> clients; ServerDataSingleton() = default; ServerDataSingleton(const ServerDataSingleton&) = delete; void operator=(const ServerDataSingleton&) = delete; static inline ServerDataSingleton* data = nullptr; public: static ServerDataSingleton& instance() { if(nullptr == data) { data = new ServerDataSingleton(); } return *data; } int send_all(int i_socket, const char* buffer) { std::lock_guard guard(lock); std::cout << i_socket << ":Рассылка сообщения" << std::endl; auto client_name = clients[i_socket]; auto message = client_name + ":" + std::string(buffer); for(auto client : clients) { int socket = client.first; EPIC_TIMEOUT; ssize_t bytes_sent = send(socket, message.c_str(), message.size() + 1, 0); //send is thread save according to POSIX if(bytes_sent < 0) { perror("send error"); return -1; } std::cout << i_socket << ":Отправлено сообщение " << bytes_sent << " байт к " << socket << std::endl; } return 0; } void removeClient(int socket) { std::lock_guard guard(lock); std::cout << socket << ":клиент удалён из рассылки" << std::endl; clients.erase(socket); } void addClient(int socket, std::string& name) { std::lock_guard guard(lock); std::cout << socket << ":клиент добавлен в рассылку" << std::endl; clients[socket] = name; } }; bool client_parallel_handling = false; bool client_chat_handling = false; void handle_connection(int client_socket) { char buffer[BUFFER_SIZE]; if(client_chat_handling) { auto client_name = "client" + std::to_string(client_socket); ServerDataSingleton::instance().addClient(client_socket, client_name); } while(true) { /* * recv() - ожидание TCP-сегмента от клиента * * В СЕТИ ПРОИСХОДИТ: * 1. Приходит Ethernet-фрейм: * - MAC-адреса получателя/отправителя * - Тип 0x0800 (IPv4) * 2. Внутри IP-пакет: * - Версия 4, IHL, TOS * - Длина пакета, ID, флаги, смещение * - TTL (обычно 64), протокол 6 (TCP) * - Checksum, адреса источника/назначения * 3. TCP-сегмент: * - Порт источника (случайный), назначения (8080) * - Sequence number, Acknowledgment number * - Data offset, флаги (ACK, PSH и др.) * - Window size, Checksum, Urgent pointer * - Данные приложения (если есть) */ ssize_t bytes_received = recv(client_socket, buffer, sizeof(buffer), 0); EPIC_TIMEOUT; if(bytes_received == 0) { // Получен TCP-сегмент с флагом FIN std::cout << client_socket << ":Клиент инициировал закрытие соединения" << std::endl; break; } if(bytes_received < 0) { perror((std::to_string(client_socket) + ":recv error").c_str()); break; } std::cout << client_socket << ":Принято " << bytes_received << " байт" << std::endl; if(client_chat_handling) { int status = ServerDataSingleton::instance().send_all(client_socket, buffer); if(status != 0) { std::cout << client_socket << ":Ошибка отправки сообщения" << std::endl; break; } continue; } /* * send() - отправка TCP-сегмента с данными * * В СЕТИ ФОРМИРУЕТСЯ: * 1. TCP-сегмент: * - Флаги: ACK и PSH (push) * - Sequence number = последний ACK сервера * - Acknowledgment number = последний seq клиента + длина данных * - Данные = полученный буфер * 2. Инкапсуляция в IP-пакет: * - Адрес отправителя = IP сервера * - Адрес получателя = IP клиента * 3. Инкапсуляция в Ethernet-фрейм: * - MAC-адрес отправителя = MAC сервера * - MAC-адрес получателя = шлюза или клиента (если в одной сети) */ EPIC_TIMEOUT; ssize_t bytes_sent = send(client_socket, buffer, bytes_received, 0); if(bytes_sent < 0 || bytes_sent != bytes_received) { perror((std::to_string(client_socket) + ":send error").c_str()); break; } std::cout << client_socket << ":Отправлено эхо " << bytes_received << " байт" << std::endl; } ServerDataSingleton::instance().removeClient(client_socket); /* * shutdown(SHUT_WR) - инициация закрытия соединения * * В СЕТИ ОТПРАВЛЯЕТСЯ: * - TCP-сегмент с флагом FIN и ACK * - Sequence number = следующее ожидаемое число * - Acknowledgment number = последнее полученное число * * ПОСЛЕДОВАТЕЛЬНОСТЬ ЗАКРЫТИЯ (4-way handshake): * 1. Сервер: FIN + ACK (этот вызов) * 2. Клиент: ACK (подтверждение FIN) * 3. Клиент: FIN (если тоже закрывается) * 4. Сервер: ACK (подтверждение FIN клиента) */ if(shutdown(client_socket, SHUT_WR) < 0) { perror((std::to_string(client_socket) + ":shutdown error").c_str()); } // Довычитываем сокет перед завершение соединения char temp_buf[128]; while(recv(client_socket, temp_buf, sizeof(temp_buf), 0) > 0) {} if(0 != close(client_socket)) { perror((std::to_string(client_socket) + ":close error").c_str()); } } int main(int argc, const char** argv) { if(argc >= 2) { if(0 == strcmp(argv[1], "-j")) { std::cout << "Сервер запущен в режиме обработки многих клиентов" << std::endl; client_parallel_handling = true; } else if(0 == strcmp(argv[1], "-c")) { std::cout << "Сервер запущен в режиме обработки чата" << std::endl; client_parallel_handling = true; client_chat_handling = true; } else { std::cerr << "Usage: " << argv[0] << " [-j|-c]" << std::endl; exit(1); } } else { std::cout << "Сервер запущен в режиме обработки одного клиента" << std::endl; } /* * socket() - создание конечной точки для сетевого обмена * (Пока ничего не происходит в сети, только подготовка в ОС) */ int server_fd = socket(AF_INET, SOCK_STREAM, 0); if(server_fd < 0) { perror("socket failed"); return 1; } // Разрешаем повторное использование порта int opt = 1; if(0 != setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) { perror("setsockopt failed"); return 1; } // Настройка адреса сервера sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(PORT); /* * bind() - привязка к сетевому интерфейсу и порту * * В СЕТИ: * - Сервер начинает "слушать" кадры Ethernet, содержащие: * - IP-пакеты с адресом назначения = IP сервера * - TCP-сегменты с портом назначения 8080 */ if(0 != bind(server_fd, (sockaddr*)&server_addr, sizeof(server_addr))) { perror("bind failed"); close(server_fd); exit(1); } /* * listen() - переход в режим ожидания подключений * (Теперь сервер готов принимать SYN-пакеты) */ if(0 != listen(server_fd, 5)) { perror("listen failed"); close(server_fd); return 1; } while(true) { sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); std::cout << "Сервер ожидает подключений на порту " << PORT << std::endl; /* * accept() - ожидание входящего подключения * * В СЕТИ ПРОИСХОДИТ 3-WAY HANDSHAKE: * 1. Клиент -> Сервер: SYN (seq=X) * - TCP-флаги: SYN=1 * 2. Сервер -> Клиент: SYN-ACK (seq=Y, ack=X+1) * - TCP-флаги: SYN=1, ACK=1 * 3. Клиент -> Сервер: ACK (seq=X+1, ack=Y+1) * - TCP-флаги: ACK=1 * * После этого соединение считается установленным (ESTABLISHED) */ int client_socket = accept(server_fd, (sockaddr*)&client_addr, &client_len); EPIC_TIMEOUT; if(client_socket < 0) { if((errno == EAGAIN || errno == EINTR)) { continue; } perror("accept failed"); exit(1); } std::cout << client_socket << ":Принято подключение от " << inet_ntoa(client_addr.sin_addr) << ":" << ntohs(client_addr.sin_port) << std::endl; if(client_parallel_handling) { std::thread([client_socket]() { handle_connection(client_socket); }).detach(); std::cout << client_socket << ":Обработка клиента запущена в отдельном треде" << std::endl; } else { handle_connection(client_socket); EPIC_TIMEOUT; std::cout << "Соединение закрыто" << std::endl; } } if(0 != close(server_fd)) { perror("close failed"); exit(1); }; return 0; }