Почему recv возвращает -1 и errno=EINTR при установке SO_RCVTIMEO?

Эта проблема возникает только тогда, когда для сокета установлено время ожидания с помощью SO_RCVTIMEO.

recv должен заблокироваться на 3 секунды. Но он возвращается из-за EINTR, как только запускается другой поток.

Если я запускаю поток t2, recv в потоке t1 вернет -1 без блокировки и установит errno в EINTR.

Но recv в потоке t1 работает нормально, когда поток t2 не запущен, он просто блокируется на 3 секунды.

Если поток t2 выполняется перед потоком t1, recv также работает правильно.

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

Вот код:

test.cpp: ссылка -pthread для использования <thread> или исключения потока

#include<unistd.h>
#include<netdb.h>
#include<string.h>
#include<thread>

int socket_fd;
sockaddr_in server_addr;

void recvThread()
{
    char pData[4096];
    int len = recv(socket_fd,pData,4096,0);
    if(len<=0)
    {
        printf("len:%d\n",len);
        printf("errno:%d\n",errno);
    }
}

void otherThread()
{
    while(1)
    {
         sleep(1);
    }
}

int main()
{
    hostent *host;
    if((host=gethostbyname("127.0.0.1"))==NULL)
    {
         return 1;
    }
    memset(&server_addr, 0, sizeof(sockaddr_in));
    server_addr.sin_family=AF_INET;
    server_addr.sin_port=htons(8887);
    server_addr.sin_addr=*((in_addr*)host->h_addr);
    bzero(&(server_addr.sin_zero),8);

    socket_fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

    timeval timeout = {3,0};
    setsockopt(socket_fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeval));
    if(connect(socket_fd, (sockaddr*)&server_addr, sizeof(server_addr))<0)
    {
         return 1;
    }

    std::thread t1(recvThread);
    std::thread t2(otherThread);
    t1.join();
    t2.join();
}

person zero15    schedule 03.08.2018    source источник
comment
Вы знаете, что EINTR означает? Пожалуйста, начните с чтения страницы руководства recv.   -  person Some programmer dude    schedule 03.08.2018
comment
Я не знаю, почему он прерывается сигналом? Вызов std::thread t2(otherThread) отправит сигнал?   -  person zero15    schedule 03.08.2018
comment
Если ваш второй поток не делает ничего подобного, как показано в вопросе, это не приведет к отправке сигнала вашему процессу (но приведет к потере большого количества циклов ЦП). Это может быть любой сигнал, и у нас просто нет возможности узнать что-либо еще без надлежащего Minimal, Complete и Поддающийся проверке пример.   -  person Some programmer dude    schedule 03.08.2018
comment
Я обновил там весь код. Поскольку я не могу копировать с внутренней машины, могут быть опечатки. Этот код может представить то, что я спросил.   -  person zero15    schedule 03.08.2018
comment
Странная часть: поменяв порядок вызова std:thread t1 t2, recv может работать хорошо.   -  person zero15    schedule 03.08.2018
comment
@Someprogrammerdude   -  person zero15    schedule 03.08.2018
comment
Сигналы используются повсюду внутри потоковой обработки. Не удивляйтесь, если вы получите периферийное свидетельство сигнала, не видя его на самом деле.   -  person o11c    schedule 04.08.2018
comment
@ o11c Как мне с этим справиться? Мне просто нужен блок recv() на 3 секунды.   -  person zero15    schedule 04.08.2018
comment
Вы проверяете, сколько времени прошло, и ждете оставшуюся сумму. Предполагая, конечно, что сигнал не был тем, который преднамеренно говорил вернуться к циклу событий и сделать что-то еще   -  person o11c    schedule 05.08.2018


Ответы (2)


См. комментарий к EINTR от "Какого-то чувака-программиста"

Отладчик (gdb), если он не может устанавливать/изменять точки останова асинхронно, должен остановить цель (вашу задачу), установить точки останова, а затем возобновить ее. Чтобы остановить это, он может отправить SIGINT, что приведет к EINTR для заблокированных вызовов вашей системы.

Если вы используете библиотеку GNU C, вы можете использовать макрос TEMP_FAILURE_RETRY, см. этот пост: TEMP_FAILURE_RETRY и __USE_GNU< /а>

person v01d    schedule 04.08.2018

Вот потенциальный баг

bzero(&(server_addr.sin_zero),8);

должно быть

bzero(&(server_addr.sin_zero), sizeof(server_addr.sin_zero));

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

person Surt    schedule 03.08.2018
comment
И вызов bzero даже не нужен после предыдущего вызова memset. - person Some programmer dude; 03.08.2018
comment
Я удалил bzero, но ничего не изменилось. - person zero15; 04.08.2018
comment
Я попробовал несколько способов заставить его работать. Даже sleep(1) после std::thread t1 и recv блокирует 1 секунду, затем recv возвращает -1 при запуске t2. - person zero15; 04.08.2018
comment
Я обнаружил, что это терпит неудачу каждый раз, когда я отлаживал с помощью SlickEdit или gdb. Но работает правильно, когда он работает в терминале. - person zero15; 04.08.2018