LINUX.ORG.RU

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

 ,


0

4

Доброго всем!

Для нужд тестирования tcp-клиента делаю простой сервер, от которого (пока) нужно только принять сообщение и подтвердить его прием.

Клиент (псевдокод):

cli_sd = socket(AF_INET, SOCK_STREAM, 0);
setsockopt(cli_sd, SOL_SOCKET, SO_RCVTIME0, ...);
setsockopt(cli_sd, SOL_SOCKET, SO_SNDTIME0, ...);
connect(cli_sd, ...);
while (1) {
    send(...);
    recv(...);
}

Сервер (тоже псевдокод):

srv_sd = socket(AF_INET, SOCK_STREAM, 0);
setsockopt(srv_sd, SOL_SOCKET, SO_REUSEADDR, ...);
bind(...);
while (1) {
    cli_sd = accept(...);
    setsockopt(..., SO_RCVTIME0, ...);
    setsockopt(..., SO_SNDTIME0, ...);
    int rd = recv(...);
    if (errno == ECONNRESET) {
        shutdown(cli_sd, ...);
        close(cli_sd, ...);
        continue;
    }
    send(...);
}

Обмен есть, сообщения посылаются/отправляются, а вот когда я прерываю работу клиента (чтобы сымитировать разрыв соединения) accept как ни в чем не бывало, тут же соединяется, cli_sd получает то же значение, что и было, но ведь клиент уже не работает.

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


Ответ на: комментарий от anonymous

Понял, буду проверять.

Только вопрос с переподключением остался. errno содержит ECONNRESET, т.е. из этого сокета больше ничего читать не получится, так?

Я закрываю сокет, делаю accept, запускаю клиента - а он не коннектится.

braboar
() автор топика

У тебя у «сервера» какой-то странный код и непонятное описание того, что происходит. Напиши не псевдокод а что там на самом деле.

И проверяй везде что если функция вернула <0 то это ошибка и надо её обрабатывать. Как минимум выводить в лог errno, strerror(errno).

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 2)
Ответ на: комментарий от firkax

Есть там проверки:

    srv_sd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == srv_sd)
    {
        log(string("error creating server socket (") + strerror(errno) + ")");
        return 1;
    }

    int optval { 1 };
    setsockopt(srv_sd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof optval);
    if (-1 == bind(srv_sd, (struct sockaddr*)&srv_addr, sizeof(srv_addr)))
    {
        log(string("error binding socket to local address: (") + strerror(errno) + ")");
        return 1;
    }

    signal(SIGINT, sig_handler);

    log("Waiting for a client to connect...");

    //listen for up to 5 requests at a time
    listen(srv_sd, 5);

    int bytes_rd { 0 };
    int bytes_wr { 0 };
    int cli_sd { -1 };
    while (!g_need_stop)
    {
        socklen_t sock_size = sizeof(cli_addr);
        cli_sd = accept(srv_sd, (sockaddr*)&cli_addr, &sock_size);
        if (-1 == cli_sd)
        {
            log(fmt::format("error accepting client's request {} ({})", errno, strerror(errno)));
            return;
        }
        log("--- client accepted");

        const int timeout { 5 };
        struct timeval tv;
        tv.tv_sec = timeout;
        tv.tv_usec = 0;

        setsockopt(cli_sd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(struct timeval));
        setsockopt(cli_sd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(struct timeval));

        log("Connected with client");

        while (1)
        {
            memset(buf, 0, sizeof(buf));
            int rd = recv(cli_sd, (char*)buf, sizeof(buf), 0);

            if (-1 == rd)
            {
                log(fmt::format("[ERROR]: {} ({})", errno2str(errno), strerror(errno)));
                if (errno != ECONNRESET)
                {
                    continue;
                }
                else
                {
                    shutdown(cli_sd, SHUT_WR | SHUT_RD);
                    close(cli_sd);
                    cli_sd = -1;
                    log("--- Returning to accept");
                    break;
                }
            }
            // if 0 == rd, successful reading, nothing to do.
            if (0 < rd)
            {
                log(string("Message from client: ") + (char*)(buf));
                string ans { "ack"};
                memset(buf, 0, sizeof(buf));
                strncpy((char*)buf, ans.c_str(), ans.size());
                log(fmt::format("--- sending to client {}", (char*)buf));
                bytes_wr += send(cli_sd, (char*)buf, ans.size(), 0);

                if (0 == ans.compare("exit"))
                {
                    break;
                }
            }
            std::this_thread::sleep_for(std::chrono::microseconds(670));
        }
    }

    log("Closing sockets and exiting");
    close(cli_sd);
    close(srv_sd);

Функция работает, пока не получит SIGINT. Когда руками прибиваешь клиента в логе ECONNRESET (Connection reset by peer), запускаю клиента повторно (сервер продолжает работу), сообщений от клиента нет.

braboar
() автор топика
Ответ на: комментарий от braboar

Значит не тут проблема, но учти: функция log() может испортить errno и в следующем if управление ушло бы не туда. errno надо сохранять в локальную переменную сразу если планируешь с ним какую-то длинную логику с вызовами функций.

А так странно. Поставь отладочный вывод на строчке прямо перед recv и сразу после него (с кодом возврата). И в клиенте так же вокруг send.

firkax ★★★★★
()
Ответ на: комментарий от braboar

А ещё, если recv вернул 0 - это значит что на той стороне соединение закрыли. А у тебя там бесконечный sleep получается.

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

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 1)
Ответ на: комментарий от braboar

Да, видел такие советы. Только непонятно, что select и как poll. Можно больше подробностей?

Если тебе просто нужно разобраться с работой сокетов и написать простой tcp сервер, то select/poll тебе не нужен (И asio тебе не нужен).

poll/select/epoll или io_uring тебе нужен для более/менее сложных приложений, чтобы не оказаться в аду мудреного многопоточного ввода/вывода. Но там большой порог вхождения в саму эту концепцию асинхронного ввода/вывода. Двумя словами не объяснишь.

pathfinder ★★★★
()
Ответ на: комментарий от pathfinder

Если тебе просто нужно разобраться с работой сокетов и написать простой tcp сервер, то select/poll тебе не нужен (И asio тебе не нужен).

Именно так, мне надо было просто разобраться с сокетами (accept, bind, connect и пр.).

braboar
() автор топика