/
alevadkal
/
basic_netlab
Обзор
Документация
Войти
/
alevadkal
/
basic_netlab
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
client/client.cpp
216 строк
9 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 <cstddef> #include <cstring> #include <iostream> #include <netinet/in.h> #include <sys/socket.h> #include <sys/types.h> #include <thread> #include <unistd.h> #define TRACE(...) ({ std::cerr __VA_OPT__(<<) __VA_ARGS__ << std::endl; }) #define EPIC_TIMEOUT usleep(100000) // для наглядоности логов //#define EPIC_TIMEOUT ({}) const int PORT = 1234; const int BUFFER_SIZE = 1024; void usage(int argc, char** argv) { (void)argc; std::cerr << "Usage: " << argv[0] << " <server IP> [-c]" << std::endl; } void server_disconnect(int sock) { /* * shutdown() - инициация закрытия соединения * * В СЕТИ: * 1. Клиент -> Сервер: FIN (seq=текущий номер) * - TCP-флаги: FIN=1, ACK=1 * 2. Сервер -> Клиент: ACK (ack=seq+1) * 3. Сервер -> Клиент: FIN (если тоже закрывается) * 4. Клиент -> Сервер: ACK (подтверждение FIN) * * СОСТОЯНИЯ TCP: * - FIN_WAIT_1 → FIN_WAIT_2 (после ACK сервера) * - TIME_WAIT (после отправки ACK на FIN сервера) */ if(shutdown(sock, SHUT_WR) < 0) { perror("shutdown failed"); } std::cout << "Серверу направлен запрос на закрытие соединения" << std::endl; std::cout << "Ожидаем возможный FIN от сервера" << std::endl; char temp_buf[128]; while(recv(sock, temp_buf, sizeof(temp_buf), 0) > 0) {} } bool client_receive_message(int sock) { /* * recv() - ожидание эхо-ответа * * Ожидаемый TCP-сегмент: * - Флаги: ACK * - Acknowledgment number = отправленный seq + длина сообщения * - Sequence number = предыдущий ack сервера * - Данные = точная копия отправленных данных */ char buffer[BUFFER_SIZE]; ssize_t recv_status = recv(sock, buffer, sizeof(buffer) - 1, 0); if(recv_status < 0) { perror("send failed"); return false; } size_t received_bytes = static_cast<size_t>(recv_status); EPIC_TIMEOUT; if(received_bytes == 0) { std::cout << "Сервер закрыл соединение" << std::endl; return false; } std::cout << "Принято " << received_bytes << "байт" << std::endl; buffer[received_bytes] = 0; std::cout << "Ответ сервера:" << buffer << std::endl; return true; } bool client_chat_mode = false; bool client_need_stop = false; void chat_receive_handler(int sock) { while(!client_need_stop && client_receive_message(sock)) { ; } client_need_stop = true; } int main(int argc, char** argv) { if(argc < 2) { usage(argc, argv); std::cerr << "Usage: " << argv[0] << " <server IP>" << std::endl; return 1; } if(argc > 2) { if(0 == strcmp(argv[2], "-c")) { std::cout << "Клиент запущен в режиме чата" << std::endl; client_chat_mode = true; } else { usage(argc, argv); return 1; } } /* * socket() - создание сокета (без сетевой активности) */ int sock = socket(AF_INET, SOCK_STREAM, 0); if(sock < 0) { perror("socket failed"); return 1; } // Настройка адреса сервера sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(PORT); inet_pton(AF_INET, argv[1], &server_addr.sin_addr); std::cout << "Подключение к серверу" << std::endl; /* * connect() - инициация TCP-соединения * * В СЕТИ: * 1. ARP-запрос (если сервер в локальной сети): * - Ethernet broadcast: FF:FF:FF:FF:FF:FF * - "Кто имеет IP X.X.X.X? Скажите Y.Y.Y.Y" * 2. Отправка SYN: * - TCP-флаги: SYN=1 * - Sequence number = случайное число (например, 12345) * 3. Ожидание SYN-ACK: * - Проверка ack_number = 12346 * 4. Отправка ACK: * - TCP-флаги: ACK=1 * - Sequence number = 12346 * - Acknowledgment number = полученный seq+1 */ if(0 != connect(sock, (sockaddr*)&server_addr, sizeof(server_addr))) { perror("connect failed"); close(sock); return 1; } EPIC_TIMEOUT; std::thread receive_thread; if(client_chat_mode) { receive_thread = std::thread([sock]() { chat_receive_handler(sock); }); } std::cout << "Успешное подключение к серверу" << std::endl; std::cout << "Введите сообщение (exit для выхода):" << std::endl; int status = 0; while(!client_need_stop) { std::cout << "Ожидание ввода:" << std::endl; std::string user_message; std::getline(std::cin, user_message); std::cout << "Ввод пользователя:" << user_message << std::endl; if(0 == strcmp(user_message.c_str(), "exit")) { break; } std::cout << "Отправка сообщения " << user_message.size() + 1 << " байт" << std::endl; EPIC_TIMEOUT; /* * send() - отправка данных * * TCP-сегмент содержит: * - Флаги: ACK и PSH (push) * - Sequence number = текущее значение счетчика байт * - Данные = введенное сообщение */ ssize_t send_status = send(sock, user_message.c_str(), user_message.size() + 1, 0); if(send_status < 0 || static_cast<size_t>(send_status) != user_message.size() + 1) { perror("send failed"); status = 1; break; } if(client_chat_mode) { continue; } if(client_receive_message(sock)) { continue; } } client_need_stop = true; if(client_chat_mode) { receive_thread.join(); } server_disconnect(sock); if(0 != close(sock)) { perror("close failed"); return 1; } std::cout << "Соединение закрыто" << std::endl; return status; }