1. концепция эполла
poll
системные вызовы по сравнению сselect
Это в основном устраняет ограничение количества файловых дескрипторов, но не решает фундаментальную проблему в сценариях с высокой степенью параллелизма:
- Весь массив fd копируется между пространством ядра и пространством пользователя.
- Обход всего массива fd для поиска событий приводит к трате ресурсов
Две проблемы с производительностьюBangaНаписал статью в 1999 годуA Scalable and Explicit Event
Delivery Mechanism for UNIX,предложитьselect
а такжеpoll
не имеют состояния и требуют процессов пользовательского пространстваПерейдите, чтобы найти события самостоятельно,
Схема улучшения естьЯдро поддерживает собственную коллекцию событий., подобнымdeclare_interest
системный вызов, ядро можетПостепенно обновлять список наборов событий, в которых заинтересован процесс., приложение обрабатывает с помощьюget_next_event
Вызов может отправлять новые события ядру.
По результатам исследования статьи,LINUX
а такжеFreeBSD
Их соответствующие решения:epoll
а такжеkqueue
.В основном мы обсуждаем epoll, в конце концов, повседневная серверная среда — это LINUX.
В ядре Linux 2.6 и выше,epoll
только поддерживается.
2. функция опроса
В процессе работы epoll есть 3 функции
#include <sys/epoll.h>
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
-
epoll_create
Создаватьepoll fd
И таблица событий, после LINUX 2.6.8 красно-черное дерево используется для управления таблицей событий epoll, поэтому размер не имеет большого значения -
epoll_ctl
Управляйте таблицей событий epoll, созданной выше, вы можете добавлять события чтения и записи сокета -
epoll_wait
похож на предыдущийselect
а такжеpoll
, получить произошедшие события, такие как доступность сокета для чтения и записи
epoll_ctl
Второй параметр использует 3 макроса для представления действия:
-
EPOLL_CTL_ADD
: зарегистрировать новый fd в epfd -
EPOLL_CTL_MOD
: изменить событие прослушивателя fd, зарегистрированного мнением -
EPOLL_CTL_DEL
: удалить fd из epfd
Четвертый параметр используется, чтобы сообщить ядру, какие события следует прослушивать.epoll_event
Структура выглядит следующим образом
struct epoll_event {
__uint32_t events; // epoll事件
epoll_data_t data; // 用户数据变量
}
события epoll и доpoll
Типы событий похожи, в основном эти три:
- EPOLLIN: соответствующий fd доступен для чтения
- EPOLLOUT: соответствующий fd можно записать
- EPOLLERR: соответствующий fd отправляет пятый день
3. Эпол бой
Во-первых, MacOS основана на BSD, поэтому нетepoll
Функции, разработанные под Mac, должны компилироваться в среде LINUX. Все те же 5 шагов
- Создайте новый сокет для прослушивания порта
- порт привязки fd сокета
- прослушивание сокета
- создать epoll fd
- Инициируйте epoll_wait для получения событий и используйте epoll_ctl для управления событиями.
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/epoll.h>
#define MAX_FD_NUM 1024
#define MAXLEN 1024
int buf_len = 0;
int main()
{
// 1. 新建socket, 用于监听端口
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (listenfd == -1) printf("创建socket失败, error: %s (errno: %d)\n", strerror(errno), errno);
// 2. 绑定端口
unsigned short listenPort = 8090;
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(listenPort);
int on = 1;
// 设置socket绑定的端口,再程序关闭之后可以重复使用
if ((setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(int))) < 0) {
exit(1);
}
int bindRet = bind(listenfd, (struct sockaddr *) &server_addr, sizeof(server_addr));
if (bindRet == -1) {
printf("socket绑定地址失败, error: %s (errno: %d)\n", strerror(errno), errno);
exit(1);
}
// 3. 监听端口
int listenRet = listen(listenfd, 10);
if (listenRet == -1) printf("socket监听端口失败, error: %s (errno: %d)\n", strerror(errno), errno);
printf("socket 监听完毕, 地址: 127.0.0.1:%d", listenPort);
struct sockaddr_in client_addr;
socklen_t client_addr_len = sizeof(struct sockaddr_in);
// 4. 创建一个 epfd,并且把 listenfd 注册到这个 epfd上。
int epfd = epoll_create(1024);
struct epoll_event ev,events[20];
ev.data.fd = listenfd;
ev.events = EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);
int cur_fd_num = 1;
char buf[MAXLEN]={0};
while (1) {
// 5. 调用epoll_wait获取IO事件
// nReady 就是 events 数组的长度。
int nready = epoll_wait(epfd, events, 20, 50);
int i = 0;
for (; i < nready; i++) {
if (events[i].data.fd == listenfd) {
int client_sockfd = accept(listenfd,(struct sockaddr*)&client_addr,&client_addr_len);
if(client_sockfd < 0) {
perror("accept");
}
else {
printf("accept client_addr %s\n",inet_ntoa(client_addr.sin_addr));
ev.data.fd = client_sockfd;
ev.events=EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_ADD, client_sockfd, &ev);
}
}
else if (events[i].events & EPOLLIN) {
int connfd = events[i].data.fd;
int n = recv(connfd, buf, MAXLEN, 0);
if(n <= 0) {
if(ECONNRESET == errno) {
close(connfd);
epoll_ctl(epfd, EPOLL_CTL_DEL, connfd, 0);
}
else {
perror("recv");
}
}
printf("receive %s", buf);
buf_len = n;
ev.data.fd = connfd;
ev.events = EPOLLOUT;
epoll_ctl(epfd, EPOLL_CTL_MOD, connfd, &ev);
}
else if (events[i].events & EPOLLOUT) {
int connfd = events[i].data.fd;
write(connfd, buf, buf_len);
ev.data.fd = connfd;
ev.events = EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_MOD, connfd, &ev);
}
}
}
return 0;
}
Скомпилировать с помощью gcc, запустить
$ gcc -o epoll ./epoll.c
$ ./epoll
затем используйтеnc
просто проверить
4. сводка по эполлу
epoll
решеноselect
а такжеpoll
Есть две проблемы с производительностью, оставшиеся от эпохи.Не нужно копировать весь массив fd туда-сюда между пространством ядра и пространством пользователя, и в то же время
Процессу приложения не требуется проходить весь массив fd, чтобы выяснить, что произошло.epoll
использоватьmmap
Ускоряет передачу сообщений между ядром и пользовательским пространством, избегая ненужных копий памяти.epoll
Возвращаются только активные fd сокета, поэтому эффективность ввода-вывода не падает значительно по мере увеличения количества fd.
epoll
также поддерживаетET(边缘触发)
а такжеLT(水平触发)
, они подробно не обсуждаются.