Один из моих проектов в 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
часто выполняется.
РЕДАКТИРОВАТЬ: некоторые вещи, которые я заметил, которые могут иметь отношение.
Я устанавливаю тайм-аут чтения для сокета, используя setsockopts()
, но он установлен на 30 секунд. EAGAIN
случаются чаще, чем раз в 30 секунд. ИСПРАВЛЕНИЕ моя отладка была ошибочной, EAGAIN
не происходит так часто, как я думал, что они это сделали. Возможно, это срабатывает тайм-аут.
Для подключения я хочу иметь тайм-аут подключения, поэтому я временно установил неблокирующее гнездо. Этот код выглядит так:
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()
успешно возвращаются, поэтому сокет должен снова оказаться в режиме блокировки после завершения этой функции.