петли epoll при отключении клиента

Я пытаюсь реализовать сокет-сервер с помощью epoll, У меня есть 2 темы, делающие 2 задачи:

  1. прослушивание входящего соединения
  2. запись на экране данных, отправляемых клиентом.

Для моего теста у меня есть клиент и сервер на одной машине с 3 или 4 клиентами.
Сервер работает нормально, пока я не убью одного из клиентов, выдав CTRL-C: как только я это сделаю, сервер начнет зацикливаться и печатать с очень высокой скоростью данные с другого клиента. Странно то, что

  1. клиент отправляет данные каждые 2 секунды, но скорость сервера выше
  2. epoll_wait также должен что-то печатать, когда один из клиентов отключается, так как он проверяет также наличие EPOLLHUP или EPOLLERR
  3. epoll_wait Я должен немного подождать перед печатью, так как я дал ему тайм-аут в 3000 миллисекунд.

Вы можете помочь? Может быть, я неправильно передаю дескриптор epoll в другой поток? Я не могу понять, так как код похож на многие примеры вокруг.

большое спасибо

Миннесота

 // server.cpp
#include <iostream>
#include <cstdio>
#include <cstring>
extern "C" {
#include <sys/epoll.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netdb.h>
#include <pthread.h>
}

#define MAX_BACKLOG 10

void* readerthread(void* args){
int epfd = *((int*)args);
epoll_event outwait[10];
while(true){
int retpw = epoll_wait( epfd, outwait,20, 3000 );
if( retpw == -1 ){
printf("epoll error %m\n");
}else if( retpw == 0 ){
printf("nothing is ready yet\n");
continue;
}else{
for( int i=0;i<retpw;i++){
if( outwait[i].events & EPOLLIN ){
int fd = outwait[i].data.fd;
char buf[64];
if( -1 == read(fd,buf,64) ){
printf("error reading %m\n");
}
printf("%s\n",buf);
}else{
std::cout << "other event" << std::endl;
}
}
}
}
}

int main(){

int epfd = epoll_create(10);
if( -1 == epfd ){
std::cerr << "error creating EPOLL server" << std::endl;
return -1;
}

pthread_t reader;
int rt = pthread_create( &reader, NULL, readerthread, (void*)&epfd );
if( -1 == rt ){
printf("thread creation %m\n");
return -1;
}struct addrinfo addr;
memset(&addr,0,sizeof(addrinfo));
addr.ai_family   = AF_INET;
addr.ai_socktype = SOCK_STREAM;
addr.ai_protocol = 0;
addr.ai_flags    = AI_PASSIVE;

struct addrinfo * rp,* result;
getaddrinfo( "localhost","59000",&addr,&result );
for( rp = result; rp != NULL; rp = rp->ai_next ){

// we want to take the first ( it could be IP_V4
// or IP_V6 )
break;
}

int sd = socket( AF_INET, SOCK_STREAM, 0 );
if(-1==sd ){
std::cerr << "error creating the socket" << std::endl;
return -1;
}
// to avoid error 'Address already in Use'
int optval = 1;
setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));

if( -1==bind( sd, result->ai_addr, result->ai_addrlen ) ){
printf("%m\n");
std::cerr << "error binding" << std::endl;
return -1;
}

while(true){

std::cout << "listen" << std::endl;
if( -1== listen(sd, MAX_BACKLOG ) ){
std::cerr << "listen didn't work" << std::endl;
return -1;
}

std::cout << "accept" << std::endl;
sockaddr peer;
socklen_t addr_size;
int pfd = accept( sd, &peer ,&addr_size );
if( pfd == -1 ){
std::cerr << "error calling accept()" << std::endl;
return -1;
}
epoll_event ev;
ev.data.fd = pfd;
ev.events =  EPOLLIN;
std::cout << "adding to epoll list" << std::endl;
if( -1 == epoll_ctl( epfd, EPOLL_CTL_ADD, pfd, &ev ) ){
printf("epoll_ctl error %m\n");
return -1;
}

}

}
// end of server.cpp// client.cpp
#include <iostream>
#include <cstring>
#include <cstdio>
extern "C"{
#include <sys/socket.h>
#include <sys/types.h>
#include <netdb.h>
}

int main(){

const char* servername = "localhost";
const char* serverport = "59000";

struct addrinfo server_address;
memset( &server_address, 0, sizeof(struct addrinfo) );
server_address.ai_family  =  AF_INET;
server_address.ai_socktype =  SOCK_STREAM;
server_address.ai_protocol  =  0; // any protocol
server_address.ai_flags    =  0;

struct addrinfo * result, * rp;

int res = getaddrinfo( servername, serverport, &server_address, &result );
if( -1 == res ){
std::cout << "I cannot getaddress " << servername << std::endl;
return -1;
}

int fd = socket( server_address.ai_family
, server_address.ai_socktype
, server_address.ai_protocol );
if( -1 == fd ){
printf("I cannot open a socket %m\n");
return -1;
}

for( rp = result; rp != NULL; rp = rp->ai_next ){
std::cout << "************" << std::endl;
if( -1 == connect( fd, rp->ai_addr, rp->ai_addrlen ) ){
close(fd);
}else{
std::cout << "connected" << std::endl;
break;
}
}
if( rp == NULL ){
std::cerr << "I couldn't connect server " << servername << std::endl;
}
while(true){
sleep(2);
pid_t me = getpid();
char buf[64];
bzero( buf,sizeof(buf));
sprintf( buf,"%ld",me );
write(fd,buf,sizeof(buf));
printf("%s\n",buf);
}
}
// end of client.cpp

2

Решение

Отключение клиента сигнализируется условием EOF в дескрипторе файла. Система считает EOF состоянием, в котором дескриптор файла является «читаемым». Но, конечно, условие EOF не может быть прочитано. Это источник вашего цикла. epoll действует как дескриптор файла для отключенного клиента всегда удобочитаемый. Вы можете обнаружить, что у вас есть состояние EOF, проверив, когда read возвращает 0 прочитанных байтов.

Единственный способ справиться с условием EOF — это close дескриптор файла в некотором роде. В зависимости от того, как точно идет поток вещей, это может быть с shutdown(sockfd, SHUT_RD), shutdown(sockfd, SHUT_RDWR) или же close(sockfd);,

Если вы не знаете, что вам нужно shutdown(2) позвоните по любой причине, я бы порекомендовал вам использовать close, Конечно, вы должны помнить, чтобы сказать epoll что файловый дескриптор больше не представляет интереса для вас close, Я не уверен, что произойдет, если вы этого не сделаете, но одна возможность состоит в том, что epoll будет ошибка. Другое в том, что epoll загадочно начнет сообщать о событиях для нового файлового дескриптора с таким же числовым значением, прежде чем добавить его в список epoll должен заботиться о.

3

Другие решения

Гнездо, полностью закрытое другой стороной, станет читабельным и read(2) вернусь 0, вы должны проверить это. Как закодировано сейчас — опровергнутый уровнем опрос — epoll_wait(2) возвращается каждый раз, не дожидаясь сообщения о том, что вы еще не прочитали этот конец потока.

В качестве альтернативы, вы можете переключиться на опровержение опроса (EPOLLET) и реагировать на EPOLLRDHUP тоже.

3

По вопросам рекламы [email protected]