Я пытаюсь реализовать сокет-сервер с помощью epoll
, У меня есть 2 темы, делающие 2 задачи:
Для моего теста у меня есть клиент и сервер на одной машине с 3 или 4 клиентами.
Сервер работает нормально, пока я не убью одного из клиентов, выдав CTRL-C
: как только я это сделаю, сервер начнет зацикливаться и печатать с очень высокой скоростью данные с другого клиента. Странно то, что
epoll_wait
также должен что-то печатать, когда один из клиентов отключается, так как он проверяет также наличие EPOLLHUP или EPOLLERRepoll_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
Отключение клиента сигнализируется условием 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
должен заботиться о.
Гнездо, полностью закрытое другой стороной, станет читабельным и read(2)
вернусь 0
, вы должны проверить это. Как закодировано сейчас — опровергнутый уровнем опрос — epoll_wait(2)
возвращается каждый раз, не дожидаясь сообщения о том, что вы еще не прочитали этот конец потока.
В качестве альтернативы, вы можете переключиться на опровержение опроса (EPOLLET
) и реагировать на EPOLLRDHUP
тоже.