Linuxのepoll通知機能について詳しく知るために、私はhttpサーバーに取り組んできました。サーバーの構造は、基本的に、要求を表すイベント構造体の配列を持ち、それがネストされたforループとwhileループで繰り返されます。
私がこのプリミティブイベントループを最初に書いたとき、それはかなりうまく機能しました。ただし、コードをリファクタリングすると、ループの信頼性が大幅に低下します。特に、errnoがBADFDに設定されている状態で、epoll_ctlエラーの束(リクエストの約75%)が発生し始めました。epoll_ctlは、私のソケットファイル記述子が実際にはソケットファイル記述子ではないと考えているようです。
ただし、リファクタリングはa)リクエストの解析のクリーンアップと、b)メイン関数のプログラムの先頭への移動のみで構成されていたため、なぜこのようなパフォーマンスの低下が発生するのか、私は本当に混乱しています。サーバーの以前のコミットをチェックアウトし、メイン機能を新しいバージョンと比較しました。これは同じです。
ここで何が起こっているのかについて誰かが洞察を持っていますか?よろしくお願いします。
参照用のmain関数のコードは次のとおりです。
int main(int argc, char *argv[]) {
c("in main");
char *progname=argv[0];
int sockfd, newsockfd, portno, clilen, n, pid, epollfd;
struct sockaddr_in serv_addr, cli_addr;
initFt();
if (argc < 2 ) {
fprintf(stderr, "\nERROR: No Port Provided\n");
exit(EXIT_FAILURE);
}
if (!createSocket(&sockfd)) {
fprintf(stderr, "%s ERROR, COULD NOT CREATE SERVER SOCKET\n", progname);
exit(EXIT_FAILURE);
}
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(atoi(argv[1]));
serv_addr.sin_addr.s_addr = INADDR_ANY;
Bind(&sockfd, &serv_addr);
listen(sockfd, 5);
struct epoll_event *events = calloc(SOMAXCONN, sizeof(struct epoll_event));
struct epoll_event event;
makeSocketNB(&sockfd);
event.events = EPOLLIN | EPOLLET;
event.data.fd = sockfd;
if ((epollfd = createpoll())<0) {
fprintf(stderr, "epoll create error\n");
exit(EXIT_FAILURE);
}
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, sockfd, &event) == -1) {
fprintf(stderr, "error with epoll_ctl on sockfd");
exit(EXIT_FAILURE);
}
while (1) {
int n, e; //e for events
e = epoll_wait(epollfd, events, SOMAXCONN, -1);
for (n=0; n<e; n++) {
if ((events[n].events & EPOLLERR) || (events[n].events & EPOLLHUP) || !(events[n].events & EPOLLIN)) {
fprintf (stderr, "epoll error\n");
close (events[n].data.fd);
continue;
}
else if (events[n].data.fd == sockfd) {
while (1) {
newsockfd=accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);
if (newsockfd>0) {
makeSocketNB(&newsockfd);
fprintf(stderr, "Accepted a new connection on fd %d, made nonblocking\n", newsockfd);
}
else if (errno == EAGAIN || errno == EWOULDBLOCK) {
fprintf (stderr, "we have accepted all the clients on this event\n");
break;
}
event.data.fd = newsockfd;
event.events = EPOLLIN | EPOLLET;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, newsockfd, &event)<0) {
fprintf(stderr, "epoll_ctl error\n");
if (errno == EEXIST) {
fprintf(stderr, "fd already registered\n");
}
else if (errno == EBADF) {
fprintf(stderr, "fd bad\n%d\n", newsockfd);
break;
}
else if (errno == ENOMEM) {
fprintf(stderr, "no memory\n");
}
else if (errno == ENOSPC) {
fprintf(stderr, "enospc\n");
}
exit(EXIT_FAILURE);
}
continue;
}
}
else { // there is stuff for us to read
handleResponse(&events[n].data.fd);
fprintf(stderr, "we are about to close file descriptor %d\n", events[n].data.fd);
close (events[n].data.fd);
fprintf(stderr, "connection closed\n");
}
}
}
exit(EXIT_SUCCESS);
}