@maxcad

Возможна ли гонка данных при отправке их на сокет?

Надо мне приложение которое входящие данные выстраивает в очереди, сортирует итп. Решил реализовать при помощи файловых unix сокетов. Было бы удобно когда разные приложения кидают на сокет дейтаграммы, а он их там сортирует и обрабатывает в порядке очереди.

И тут я спросил у GPT:
Одно приложение отправляет на сокет строку 'aaaa'
Второе приложение отправляет строку 'bbbb'
Если они отправят эти строки одновременно, есть вероятность получить на сокете строку 'abab'?

На что он мне ответил:
Да, если два приложения отправляют строки "aaaa" и "bbbb" одновременно, то существует вероятность получить на сокете строку "abab" или другие комбинации букв из этих двух строк.
Это связано с тем, что сокеты работают на низком уровне и не гарантируют сохранение порядка отправленных данных. Когда два приложения отправляют данные одновременно, они могут перемешиваться в процессе передачи по сети и приходить в произвольном порядке.


Получается идея с сокетами не очень хороша? При параллельной отправке на сокет можно получить кашу?

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

Приложение отравляет многопоточно 5 дейтаграмм по 40 байт
#include <string>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <iostream>
#include <filesystem>
#include <thread>
#include <vector>

#define DATA_SIZE 40

int sock(const std::string &msg) {
    // Создание файлового Unix сокета
    int sockfd = socket(AF_UNIX, SOCK_DGRAM, 0);
    if (sockfd == -1)
    {
        std::cerr << "Ошибка при создании сокета" << std::endl;
        return 1;
    }

    // Заполнение адреса сервера
    const char *socketPath = "sock";
    sockaddr_un serverAddr;
    serverAddr.sun_family = AF_UNIX;
    strncpy(serverAddr.sun_path, socketPath, sizeof(serverAddr.sun_path) - 1);

    // Отправка дейтаграммы
    auto message = msg.c_str();

    ssize_t numBytes = sendto(sockfd, message, DATA_SIZE, 0, reinterpret_cast<sockaddr *>(&serverAddr), sizeof(serverAddr));
    if (numBytes == -1)
    {
        std::cerr << "Ошибка при отправке дейтаграммы" << std::endl;
        close(sockfd);
        return 1;
    }

    // Закрытие сокета
    close(sockfd);
    return 0;
}

int main() {
    const int numThreads = 5;
    std::thread threads[numThreads];

    std::vector<std::string> v{
        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
        "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
        "cccccccccccccccccccccccccccccccccccccccc",
        "dddddddddddddddddddddddddddddddddddddddd",
        "ffffffffffffffffffffffffffffffffffffffff"
    };

    // Создание и запуск потоков
    for (int i = 0; i < numThreads; ++i) {
        threads[i] = std::thread(sock, v[i]);
    }

    // Ожидание завершения всех потоков
    for (int i = 0; i < numThreads; ++i) {
        threads[i].join();
    }

    std::cout << "All threads have finished" << std::endl;

    return 0;
}


Приложение-получатель. Принимает и печатает дейтаграммы по 40 байт
#include <string>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <iostream>
#include <filesystem>
#include <thread>

#define DATA_SIZE 40

int main() {
    // Создание файлового Unix сокета
    int sockfd = socket(AF_UNIX, SOCK_DGRAM, 0);
    if (sockfd == -1)
    {
        std::cerr << "Ошибка при создании сокета" << std::endl;
        return 1;
    }

    // Удаление существующего сокета с таким же именем
    const char *socketPath = "sock";
    unlink(socketPath);

    // Связывание сокета с адресом
    sockaddr_un address;
    address.sun_family = AF_UNIX;
    strncpy(address.sun_path, socketPath, sizeof(address.sun_path) - 1);

    if (bind(sockfd, reinterpret_cast<sockaddr *>(&address), sizeof(address)) == -1)
    {
        std::cerr << "❌Socket::run: Error when binding a socket to an address." << std::endl;
        close(sockfd);
        return 1;
    }

    std::cout << "✅Socket::run: The socket was successfully created and associated with the address" << std::endl;

    // Ожидание дейтаграммы
    char buffer[DATA_SIZE];
    sockaddr_un clientAddr;
    socklen_t clientAddrLen = sizeof(clientAddr);
    while (1)
    {
        ssize_t numBytes = recvfrom(sockfd, buffer, sizeof(buffer), 0, reinterpret_cast<sockaddr *>(&clientAddr), &clientAddrLen);
        if (numBytes == -1)
        {
            std::cerr << "Socket::run: Error when receiving a datagram" << std::endl;
            continue;
        }
        std::cout << buffer << std::endl;
        // std::this_thread::sleep_for(std::chrono::seconds(1)); // пауза между приемами дейтаграмм.
    }

    // Закрытие сокета
    close(sockfd);
    return 0;
}


Вопрос: прав ли GPT? Действительно ли на сокете можно получить кашу из дейтаграмм?
  • Вопрос задан
  • 96 просмотров
Пригласить эксперта
Ответы на вопрос 1
wataru
@wataru Куратор тега C++
Разработчик на С++, экс-олимпиадник.
ssize_t numBytes = sendto

Вот эта часть кода, что по вашему делает? Почему sendto возвращает количество байт, а не код ошибки?

Проблема может быть в том, что какой-то там буфер переполнится и sendto отправит только часть данных. Если вы добавите обработку этого, то вы будете посылать данные в цикле, но вот тут уже многопоточность доставит проблемы: между двумя последовательными вызовами в одном потоке другой поток может успеть отправить свои данные.

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


А это очередная галлюцинация ГПТ. Забудьте эти слова - они не имеют ничего общего с реальностью.

Попробуйте строки ваши сделать побольше - в пару миллионов символов, вот тут-то вам мешанина и будет приходить.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы