Блокировка розетки возвращается EAGAIN - PullRequest
7 голосов
/ 09 апреля 2009

Один из моих проектов в Linux использует блокирующие сокеты. Вещи происходят очень последовательно, поэтому неблокирование только усложнит ситуацию. Во всяком случае, я обнаружил, что часто recv() вызов возвращается -1 с errno, установленным на EAGAIN.

На странице man действительно упоминается, что это происходит только для неблокирующих сокетов, что имеет смысл. При неблокировании сокет может быть или не быть доступным, поэтому вам, возможно, придется повторить попытку.

Что могло бы произойти для блокирующего сокета? Могу ли я сделать что-нибудь, чтобы избежать этого?

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

int ret;
do {
    ret = ::recv(socket, buf, len, flags | MSG_NOSIGNAL);
} while(ret == -1 && errno == EAGAIN);


if(ret == -1) {
    throw socket_error(strerror(errno));
}
return ret;

Это даже правильно? Условие EAGAIN часто выполняется.

РЕДАКТИРОВАТЬ: некоторые вещи, которые я заметил, которые могут иметь отношение.

  1. Я устанавливаю тайм-аут чтения для сокета, используя setsockopts(), но он установлен на 30 секунд. EAGAIN случаются чаще, чем раз в 30 секунд. ИСПРАВЛЕНИЕ моя отладка была ошибочной, EAGAIN не происходит так часто, как я думал, что они это сделали. Возможно, это срабатывает тайм-аут.

  2. Для подключения я хочу иметь тайм-аут подключения, поэтому я временно установил неблокирующее гнездо. Этот код выглядит так:

    int      error = 0;
    fd_set   rset;
    fd_set   wset;
    int      n;
    const SOCKET sock = m_Socket;
    
    // set the socket as nonblocking IO
    const int flags = fcntl (sock, F_GETFL, 0);
    fcntl(sock, F_SETFL, flags | O_NONBLOCK);
    
    errno = 0;
    
    // we connect, but it will return soon
    n = ::connect(sock, addr, size_addr);
    
    if(n < 0) { 
        if (errno != EINPROGRESS) {
            return -1;
        }
    } else if (n == 0) {
        goto done;
    }
    
    FD_ZERO(&rset);
    FD_ZERO(&wset);
    FD_SET(sock, &rset);
    FD_SET(sock, &wset);
    
    struct timeval tval;
    tval.tv_sec = timeout;
    tval.tv_usec = 0;
    
    // We "select()" until connect() returns its result or timeout
    n = select(sock + 1, &rset, &wset, 0, timeout ? &tval : 0);
    if(n == 0) {    
        errno = ETIMEDOUT;
        return -1;
    }
    
    if (FD_ISSET(sock, &rset) || FD_ISSET(sock, &wset)) {
        socklen_t len = sizeof(error);
        if (getsockopt(SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
            return -1;
        }
    } else {
        return -1;
    }
    
    done:
    // We change the socket options back to blocking IO
    if (fcntl(sock, F_SETFL, flags) == -1) {
        return -1;
    }
    return 0;
    

Идея состоит в том, что я установил неблокирование, попытался установить соединение и выбрал сокет, чтобы установить тайм-аут. Вызовы set и restore fcntl() успешно возвращаются, поэтому сокет должен снова оказаться в режиме блокировки после завершения этой функции.

Ответы [ 4 ]

19 голосов
/ 09 апреля 2009

Возможно, у вас установлен ненулевой таймаут приема на сокете (через setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO,...)), так как это также приведет к возврату recv EAGAIN

1 голос
/ 09 апреля 2009

Возможно ли, что вы используете MSG_DONTWAIT, который указан как часть ваших флагов? Страница man сообщает, что EAGAIN произойдет, если нет доступных данных и указан этот флаг.

Если вы действительно хотите форсировать блок до тех пор, пока recv() не будет несколько успешным, вы можете использовать флаг MSG_WAITALL.

0 голосов
/ 21 октября 2014

EAGAIN генерируется ОС почти как «Ой! Извините, что беспокою вас». В случае этой ошибки вы можете попробовать прочитать снова. Это не серьезная или фатальная ошибка. Я видел, как эти прерывания происходят в Linux и LynxOS от одного раза в день до 100 раз в день.

0 голосов
/ 09 апреля 2009

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...