epoll介绍
epoll是Linux内核提供的一种高效的I/O多路复用机制,用于处理大量的并发连接。它可以监视多个文件描述符,当其中任何一个文件描述符就绪时,就通知应用程序进行处理。epoll是Linux内核2.6版本引入的,相比于select和poll,它具有更好的性能和可伸缩性。
epoll的优点
- 支持较大的并发连接数,可以处理数百万个连接。
- 监视的文件描述符数量不受限制,可以支持数百万个文件描述符。
- 支持水平触发和边缘触发两种模式。
- 支持EPOLLONESHOT选项,可以保证每个文件描述符只被一个线程处理。
- 支持EPOLLEXCLUSIVE选项,可以保证每个文件描述符只被一个进程处理。
epoll的工作原理
epoll使用一个事件表来存储文件描述符和事件。当调用epoll_wait()函数时,内核会检查事件表中的文件描述符,如果有文件描述符就绪,就将其加入到就绪链表中。epoll_wait()函数会返回就绪链表中的文件描述符数量,并将就绪链表中的文件描述符复制到用户空间中。应用程序可以使用这些文件描述符进行读写操作。
示例一:使用epoll实现高并发服务器
#include <sysepoll.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define MAX_EVENTS 1024
#define BUF_SIZE 1024
int setnonblocking(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
if (flags == -1) {
return -1;
}
flags |= O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) == -1) {
return -1;
}
return 0;
}
int main(int argc, char *argv[]) {
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(8080);
if (bind(listen_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
perror("bind");
exit(EXIT_FAILURE);
}
if (listen(listen_fd, SOMAXCONN) == -1) {
perror("listen");
exit(EXIT_FAILURE);
}
int epoll_fd = epoll_create1(0);
if (epoll_fd == -1) {
perror("epoll_create1");
exit(EXIT_FAILURE);
}
struct epoll_event event;
event.data.fd = listen_fd;
event.events = EPOLLIN | EPOLLET;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &event) == -1) {
perror("epoll_ctl");
exit(EXIT_FAILURE }
struct epoll_event *events = (struct epoll_event *)calloc(MAX_EVENTS, sizeof(struct epoll_event));
while (1) {
int n = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
for (int i = 0; i < n; i++) {
if (events[i].data.fd == listen_fd) {
int conn_fd = accept(listen_fd, NULL, NULL);
if (conn_fd == -1) {
perror("accept");
continue;
}
setnonblocking(conn_fd);
event.data.fd = conn_fd;
event.events = EPOLLIN | EPOLLET;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn_fd, &event) == -1) {
perror("epoll_ctl");
exit(EXIT_FAILURE);
}
} else {
int conn_fd = events[i].data.fd;
char buf[BUF_SIZE];
memset(buf, 0, sizeof(buf));
int n = read(conn_fd, buf, sizeof(buf));
if (n == -1) {
if (errno == ECONNRESET) {
close(conn_fd);
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, conn_fd, NULL);
} else {
perror("read");
}
} else if (n == 0) {
close(conn_fd);
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, conn_fd, NULL);
} else {
printf("recv: %s\n", buf);
write(conn_fd, buf, n);
}
}
}
}
free(events);
close(epoll_fd);
close(listen_fd);
return 0;
}
在这个示例中,我们使用epoll实现了一个高并发的服务器。首先创建一个socket并绑定到指定的端口,然后使用epoll_create1()函数创建一个epoll实例。将监听socket添加到epoll实例中,然后使用epoll_wait()函数等待事件。当有新的连接到来时,将连接socket添加到epoll实例中。当连接socket可读时,读取数据并将其发送回客户端。
示例二:使用epoll实现高性能的HTTP服务器
#include <sys/epoll.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define MAX_EVENTS 1024
#define BUF_SIZE 1024
int setnonblocking(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
if (flags == -1) {
return -1;
}
flags |= O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) == -1) {
return -1;
}
return 0;
}
int main(int argc, char *argv[]) {
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(8080);
if (bind(listen_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
perror("bind");
exit(EXIT_FAILURE);
}
if (listen(listen_fd, SOMAXCONN) == -1) {
perror("listen");
exit(EXIT_FAILURE);
}
int epoll_fd = epoll_create1(0);
if (epoll_fd == -1) {
perror("epoll_create1");
exit(EXIT_FAILURE);
}
struct epoll_event event;
event.data.fd = listen_fd;
event.events = EPOLLIN | EPOLLET;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &event) == -1) {
perror("epoll_ctl");
exit(EXIT_FAILURE);
}
struct epoll_event *events = (struct epoll_event *)calloc(MAX_EVENTS, sizeof(struct epoll_event));
while (1) {
int n = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
for (int i = 0; i < n; i++) {
if (events[i].data.fd == listen_fd) {
int conn_fd = accept(listen_fd, NULL, NULL);
if (conn_fd == -1) {
perror("accept");
continue;
}
setnonblocking(conn_fd);
event.data.fd = conn_fd;
event.events = EPOLLIN | EPOLLET;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn_fd, &event) == -1) {
perror("epoll_ctl");
exit(EXIT_FAILURE);
}
} else {
int conn_fd = events[i].data.fd;
char buf[BUF_SIZE];
memset(buf, 0, sizeof(buf));
int n = read(conn_fd, buf, sizeof(buf));
if (n == -1) {
if (errno == ECONNRESET) {
close(conn_fd);
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, conn_fd, NULL);
} else {
perror("read");
}
} else if (n == 0) {
close(conn_fd);
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, conn_fd, NULL);
} else {
char response[BUF_SIZE];
memset(response, 0, sizeof(response));
sprintf(response, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\n\r\n%s", n, buf);
write(conn_fd, response, strlen(response));
}
}
}
}
free(events);
close(epoll_fd);
close(listen_fd);
return 0;
}
在这个示例中,我们使用epoll实现了一个高性能的HTTP服务器。与前面的示例类似,首先创建一个socket并绑定到指定的端口,然后使用epoll_create1()函数创建一个epoll实例。将监听socket添加到epoll实例中,然后使用epoll_wait()函数等待事件。当有新的连接到来时,将连接socket添加到epoll实例中。当连接socket可读时,读取数据并将其作为HTTP响应发送回客户端。
总之,epoll是Linux内核提供的一种高效的I/O多路复用机制,用于处理大量的并发连接。可以使用epoll实现高并发服务器或高性能的HTTP服务器。