Почему самописный сервер на си работает медленнее nginx?
Вроде бы должно работать намного быстрее, но на практике получаются лютые тормоза.
#include <sys/epoll.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <stddef.h>
#include <unistd.h>
#define COUNT_EVENTS 10
int main() {
int sfd = socket( AF_INET, SOCK_STREAM, 0 );
fcntl( sfd, F_SETFL, O_NONBLOCK );
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons( 5002 );
addr.sin_addr.s_addr = htonl( INADDR_ANY );
bind( sfd, (struct sockaddr *)&addr, sizeof( addr ) );
listen( sfd, 1 );
int ep = epoll_create1( 0 );
struct epoll_event server_event;
struct epoll_event events[COUNT_EVENTS];
server_event.events = EPOLLIN | EPOLLET;
server_event.data.fd = sfd;
epoll_ctl( ep, EPOLL_CTL_ADD, sfd, &server_event );
while( 1 ) {
int count_events = epoll_wait( ep, events, COUNT_EVENTS, -1 );
for( int i = 0; i < count_events; i++ ) {
int fd = events[i].data.fd;
if( events[i].events & EPOLLRDHUP ) {
close( fd );
epoll_ctl( ep, EPOLL_CTL_DEL, fd, NULL );
} else if( events[i].events & EPOLLIN ) {
if( fd == sfd ) {
int cfd = accept(
sfd,
NULL,
NULL
);
fcntl( cfd, F_SETFL, O_NONBLOCK );
struct epoll_event ev = { 0 };
ev.events = EPOLLRDHUP | EPOLLET;
ev.data.fd = cfd;
epoll_ctl( ep, EPOLL_CTL_ADD, cfd, &ev );
}
}
}
}
}
Ответы (2 шт):
Как заметил @eri, основная проблема в том, что размер очереди ожидающих соединений сокета всего 1, т.е. :
listen( sfd, 1 );
должно быть*:
listen( sfd, 511 );
Что вызывает наблюдаемое поведение
Подразумевая, что клиент делает что-то примитивное вроде:
for(int i=0; i<N; i++){
int sfd = socket(AF_INET, SOCK_STREAM, 0);
connect(sfd,addr,addrlen);
close(sfd);
}
то иногда возникает ситуация, когда клиент успевает создать новый сокет и отправить SYN-запрос для установки соединения до того как сервер успеет обработать предыдущий accept()'ом. В результате чего очередь приёма переполняется и этот запрос отбрасывается, а клиент ожидает 1 секунду перед его повторной отправкой.
* 511 — значение взятое из strace'а nginx'а с кофигом по умолчанию.
Как я разобрался проблема в следующем: прослушивающий входящие соединение сокет создан с флагами EPOLLIN | EPOLLET, epollet генерит событие один раз (edge-triggered), даже если пришло несколько соединений. Accept забирает первое соединение и цикл продолжается, при этом накапливается очередь из неподключенных коннектов, идет переполнение и амба. Выход - либо не юзать EPOLLET на главном сокете, тогда событие будет генерится при каждой попытке коннекта, либо циклом вызывать accept пока не вернется -1