Неблокирующая проблема с сокетом recv при использовании его с epoll
У меня проблема: иногда (не регулярно) recv возвращается -1
а также errno == EAGAIN
при использовании epoll
в edge-triggered
Режим. кусок кода:
server_sock = startup(&port);
if ( (epollfd = epoll_create(4096)) < 0) {
perror("epoll_create error");
exit(EXIT_FAILURE);
}
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = server_sock;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, server_sock, &ev) == -1) {
perror("epoll_ctl: server_sock");
exit(EXIT_FAILURE);
}
while (1) {
int nfds = epoll_wait(epollfd, events, 4096, -1);
if (nfds == -1) {
perror("epoll_wait");
exit(EXIT_FAILURE);
}
for (int i = 0; i < nfds; i++) {
if (events[i].data.fd == server_sock) {
client_sock = accept(server_sock,
(struct sockaddr *)&client_name,
(socklen_t *)(&client_name_len));
if (client_sock == -1) //server overloaded
continue;
if (events[i].events & EPOLLIN) {
std::cout << "EPOLLIN on " << client_sock << std::endl;
}
Arch::set_nonblocking(client_sock);
ev.events = EPOLLIN | EPOLLRDHUP | EPOLLET; //input data and connection closing
ev.data.fd = client_sock;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, client_sock, &ev) == -1) {
perror("epoll_ctl: client_socket");
exit(EXIT_FAILURE);
}
accept_request(client_sock);
} else {
if (events[i].events & EPOLLRDHUP) {
epoll_ctl(epollfd, EPOLL_CTL_DEL, events[i].data.fd, &ev);
}
}
}
}
startup(&port)
создает неблокирующий сокет, привязку к порту и так далее. мой скрипт отправляет следующие данные:GET /connect?id=1&secret=1 HTTP/1.0\r\n\r\n
но иногда recv
вернуть -1
в этой функции (вызов внутри accept_request
):
/**********************************************************************/
/* Get a line from a socket, whether the line ends in a newline,
* carriage return, or a CRLF combination. Terminates the string read
* with a null character. If no newline indicator is found before the
* end of the buffer, the string is terminated with a null. If any of
* the above three line terminators is read, the last character of the
* string will be a linefeed and the string will be terminated with a
* null character.
* Parameters: the socket descriptor
* the buffer to save the data in
* the size of the buffer
* Returns: the number of bytes stored (excluding null) */
/**********************************************************************/
int get_line(int sock, char *buf, int size) {
int i = 0;
char c = '\0';
int n;
while ((i < size - 1) && (c != '\n')) {
n = recv(sock, &c, 1, 0);
//debug
std::cout << "n = " << n << std::endl;
if (n > 0) {
if (c == '\r') {
n = recv(sock, &c, 1, MSG_PEEK);
if ((n > 0) && (c == '\n'))
recv(sock, &c, 1, 0);
else
c = '\n';
}
buf[i] = c;
i++;
} else {
//debug
if (errno == EWOULDBLOCK)
std::cout << "EWOULDBLOCK" << std::endl;
c = '\n';
}
}
buf[i] = '\0';
return(i);
}
как написала страница руководства epoll, я должен читать / писать, пока не получу EAGAIN
, но я уже понял! и я уверен, что буфер не будет пустым. что я делаю не так?
UPD: я обнаружил интересную вещь: когда такая ситуация случается, я использую в своем коде sleep(1)
а также recc(...)
снова, и я получаю данные, которые я ожидаю! это подвох. Есть ли более изящный подход к решению этой проблемы?
1 ответ
Это совершенно нормально для первого recv()
в таком случае вернуть EAGAIN
, epoll()
никогда не говорил вам, если он был читабельным или нет.
Каждый recv()
должен быть готов справиться EAGAIN
если вы используете неблокирующие сокеты. Возможны ложные пробуждения, поэтому всякий раз, когда API select()
, poll()
или же epoll()
говорит вам, что сокет доступен для чтения, он только говорит: "он может быть читаемым - попробуйте".