Проблемы с приложением для пирингового чата – управление именем пира и соединениями

Вопрос или проблема

Я работаю над простым приложением для однорангового чата с использованием TCP и столкнулся с несколькими проблемами во время тестирования. Я протестировал приложение, запустив два экземпляра локально, но столкнулся с несколькими ошибками, которые не могу понять.

Краткое описание кода: Приложение использует TCP для установления соединения между двумя участниками, позволяя им общаться. Один участник слушает на динамически выбранном свободном порту, а подключающийся участник автоматически получает порт без ручного ввода. Связь осуществляется путем отправки сообщений между двумя подключенными участниками, имена которых отображаются вместе с каждым сообщением.

Вот фрагмент кода, обрабатывающий подключение участников и обмен сообщениями (полный файл прилагается):

bool establish_connection(int &connection_sock, int listening_sock, const std::string &peer_ip, int peer_port)
{
    bool connected = false;
    //  Попытка подключиться к обнаруженному участнику (режим клиента)
    if (!peer_ip.empty() && peer_port > 0)
    {
        // Создание TCP-сокета для соединения
        connection_sock = socket(AF_INET, SOCK_STREAM, 0);
        if (connection_sock == -1)
        {
            std::cerr << "Не удалось создать сокет для подключения к участнику." << std::endl;
            return false; // Возвращаем false, если создание сокета не удалось
        }

        // Настройка структуры адреса участника
        sockaddr_in peer_addr;
        peer_addr.sin_family = AF_INET;
        peer_addr.sin_port = htons(peer_port);
        inet_pton(AF_INET, peer_ip.c_str(), &peer_addr.sin_addr);
..........

и

void handle_chat_session(int connection_sock, const std::string &peer_name)
{
    char buffer[256];
    std::string input_message;
    fd_set read_fds;
    struct timeval tv;

    std::cout << "Чат-сессия начата с участником: " << peer_name << std::endl;

    while (true)
    {
        FD_ZERO(&read_fds);
        FD_SET(STDIN_FILENO, &read_fds);
        FD_SET(connection_sock, &read_fds);

        tv.tv_sec = 0;
        tv.tv_usec = 100000; // тайм-аут 100мс

        int max_fd = std::max(STDIN_FILENO, connection_sock) + 1;
        int activity = select(max_fd, &read_fds, NULL, NULL, &tv);
    ...................
 

Проблемы, с которыми я сталкиваюсь:

  1. Неправильное отображение имени участника:

Когда два участника подключены, один из участников отображает имя другого участника как свое собственное. Например, если участник A общается с участником B, участник A видит “B” как отправителя своих собственных сообщений. Я не уверен, является ли это ошибкой в том, как имя участника передается или обрабатывается во время соединения.

  1. Нет обнаружения отключения участника:

Когда один участник отключается от чата, другой участник, похоже, не замечает отключения и продолжает ждать сообщений. Есть ли проблемы с тем, как приложение обрабатывает отключения сокетов?

  1. Нет обнаружения нового участника после повторного подключения:

Если участник покидает чат, а другой участник присоединяется вместо него, существующий участник, похоже, не осознает, что новый участник присоединился. Чат продолжается так, как будто предыдущий участник все еще подключен. Должно ли приложение активно отслеживать изменения в подключениях участников?

  1. Другие потенциальные ошибки:

Я подозреваю, что могут быть и другие проблемы, связанные с тем, как я обрабатываю подключения участников или обмен сообщениями. Я был бы признателен за любые советы от сообщества по тому, что еще вы заметили в коде, что может вызвать нестабильность или ошибки даже в простых сценариях.

Что я попробовал:

Я дважды проверил логику обработки имен участников, но не могу найти ошибку. Я попытался обработать отключения, проверяя состояние сокета, но похоже, что это не срабатывает, когда участник уходит. Я просмотрел логику обработки соединений, но, возможно, упустил что-то в плане повторного подключения и обнаружения новых участников. Я был бы признателен за любые идеи о том, как исправить эти ошибки или улучшить надежность подключений участников!

Среда:

Я запускаю это приложение на Ubuntu, используя два локальных экземпляра приложения для имитации одноранговой связи. Используется select() для неблокирующего ввода-вывода и динамического назначения портов для слушающих участников.

ссылка на Github репозиторий

Ответ или решение

Ваш вопрос о проблемах с обработкой имен пиров и соединений в вашем приложении для чата является достаточно распространенным. Давайте разберем каждый из ваших вопросов и предложим решения.

1. Неправильное отображение имен пиров

Проблема с отображением имен пиров, где один пир видит имя другого пира как свое собственное, может быть связана с тем, как вы передаете имя пира при установлении соединения. Убедитесь, что каждый пир правильно сообщает свое имя перед отправкой сообщений. Вот несколько шагов для устранения проблемы:

  • Проверьте, что вы устанавливаете переменную peer_name в функции establish_connection. Возможно, вы передаете одно и то же имя вместо имен разных пиров.

  • Убедитесь, что каждый пир имеет уникальное имя, которое устанавливается перед началом сессии чата, и что это имя передается правильно в функцию обработки чата.

void handle_chat_session(int connection_sock, const std::string &peer_name) {
    // Убедитесь, что peer_name действительно отличается для каждого пира
    // ....
}

2. Отсутствие определения отключения пира

Если приложение не замечает отключение одного из пиров, это может быть связано с тем, что вы не обрабатываете ситуацию, когда сокет становится недоступным. В зависимости от вашей реализации, вам нужно следить за состоянием сокета:

  • Используйте select() для отслеживания состояния сокетов. Когда один из пиров отключается, обратите внимание на то, что функция recv() (или recvfrom(), если используете UDP) может вернуть 0, что означает, что удаленный пир закрыл соединение.
if (activity > 0 && FD_ISSET(connection_sock, &read_fds)) {
    int bytes_received = recv(connection_sock, buffer, sizeof(buffer), 0);
    if (bytes_received == 0) {
        std::cout << "Peer disconnected." << std::endl;
        // Завершите сессию
        break;
    }
}

3. Отсутствие обнаружения нового пира после переподключения

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

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

  • Используйте специальный протокол обмена сообщениями, позволяющий пирами делиться своим статусом (например, DISCONNECT и CONNECT сообщения) для обновления состояния соединения.

4. Другие потенциальные ошибки

Некоторые рекомендации для улучшения стабильносости и надежности:

  • Обработка ошибок и исключений: Убедитесь, что вы обрабатываете все возможные ошибки, включая ошибки подключения и ошибки чтения/записи данных.

  • Безопасность памяти: Проверьте, что вы правильно выделяете и освобождаете память для используемых буферов, чтобы избежать утечек памяти.

  • Тайм-ауты соединения: Рассмотрите возможность установления тайм-аутов для соединения, чтобы определить, что связь потеряна.

  • Логирование: Добавьте вывод отладочной информации, чтобы помочь выявить проблемы при тестировании, например, записывая статусы соединений и сообщения.

Заключение

Я надеюсь, что эти рекомендации помогут вам улучшить ваше приложение для чата и устранить текущие проблемы. Если у вас есть дополнительные вопросы или вы столкнетесь с другими проблемами, не стесняйтесь делиться. Удачи с вашим проектом!

Оцените материал
Добавить комментарий

Капча загружается...