epoll与event_pool

本文探讨了在处理多个I/O描述符时所面临的挑战及解决方案,包括使用多进程、多线程、非阻塞I/O、异步I/O等方法,并重点介绍了epoll的工作原理及其在Glusterfs中的实现。

  epoll is a scalable I/O event notification . 它在内核2.5.44首次引入, 用来取代POSIX select和poll系统调用。

    说到select和poll,也就是IO多路转接,在APUE 14.5节有详细介绍:

转自http://blog.youkuaiyun.com/delphiwcdj/article/details/8827659

------------------------------------------------

当从一个描述符读,然后又写到另一个描述符时,可以在下列形式的循环中使用阻塞I/O:

 

但是如果需要从多个描述符读,上面的方法是不行的,如果仍旧使用阻塞I/O,那么就可能长时间阻塞在一个描述符上,而另一个描述符虽有很多数据却不能得到及时处理。针对这个问题,有哪些处理方法呢?

    while ( (n = read(STDIN_FILENO, buf,BUFSIZ)) > 0 )  
    {  
                if( write(STDOUT_FILENO, buf, n) != n )  
                {  
                            printf("write error\n");  
                            return1;  
                }  
    }  

 

 

 

方法1:

 

用fork将一个进程变成两个进程,每个进程处理一条数据通路,即每个进程都执行阻塞read。

 

问题是:在进程终止时,使程序变得复杂。

 

 

 

方法2:

 

使用多线程单进程,这避免了终止的复杂性。

 

问题是:但却要求处理线程之间的同步,依然没有摆脱复杂性。

 

 

 

方法3:

 

使用单进程非阻塞I/O读数据,即轮询(polling)的方式。将多个描述符都设置为非阻塞的,对第一个描述符发一个read,如果该输入上有数 据,则读数据并处理它;如果无数据可读,则read立即返回。然后对第二个描述符做同样的处理。所有描述符处理完之后,等待若干秒然后再进行轮询。

 

问题是:浪费CPU时间,因为大多数时间实际是无数据可读的,但仍花费时间不断反复执行read系统调用,在多任务系统中应当避免使用这种方法。

 

 

 

方法4:

 

异步I/O(asynchronous I/O),其思想是:告知内核启动某个操作,并让内核在整个操作(包括将数据从内核复制到我们自己的缓冲区)完成后通知我们。即,异步I/O模型是由内核通知我们I/O操作合适完成。

 

问题是:并非所有系统都支持这种机制。

 

 

 

方法5:

 

使用I/O多路转接(I/Omultiplexing)。先构造一张有关描述符的列表,然后调用一个函数,直到这些描述符中的一个已准备好进行I /O时,该函数才返回,在返回时,此函数告诉进程哪些描述符已准备好可以进行I/O。poll、pselect和select这三个函数使我们能够执行I /O多路转接。即,我们使用select可以等待多个描述符就绪。

-----------------------------------

 

    在监控文件数量n很大的时候,select和poll的算法复杂度为O(n),而epoll的算法复杂度为O(1)。因而性能会提高很多。

    epoll只有三个API[1]

 

int epoll_create(int size);

Creates an epoll object and returns its file descriptor.

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

Controls (configures) which file descriptors are watched by this object, and for which events.

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

Waits for any of the registered events, until at least one occurs or the timeout elapses.

 

    Glusterfs使用event_pool封装了epoll。相关代码在libglusterfs/src/event-epoll.c

 

    主要有如下几个函数:

 

        .new              = event_pool_new_epoll,
        .event_register   = event_register_epoll,
        .event_select_on  = event_select_on_epoll,
        .event_unregister = event_unregister_epoll,
        .event_dispatch   = event_dispatch_epoll

 

static struct event_pool *
event_pool_new_epoll (int count)
{
        struct event_pool *event_pool = NULL;
        int                epfd = -1;

        //申请空间,event_pool封装了下epoll
        event_pool = GF_CALLOC (1, sizeof (*event_pool),
                                gf_common_mt_event_pool);

        event_pool->count = count;
        event_pool->reg = GF_CALLOC (event_pool->count,
                                     sizeof (*event_pool->reg),
                                     gf_common_mt_reg);
        //获得epoll的文件描述符
        epfd = epoll_create (count);

        event_pool->fd = epfd;
        event_pool->count = count;

        pthread_mutex_init (&event_pool->mutex, NULL);
        pthread_cond_init (&event_pool->cond, NULL);

out:
        return event_pool;
}


 

int
event_register_epoll (struct event_pool *event_pool, int fd,
                      event_handler_t handler,
                      void *data, int poll_in, int poll_out)
{
....
                //如果满了,就扩容1倍
                if (event_pool->count == event_pool->used) {
                        event_pool->count *= 2;

                        event_pool->reg = GF_REALLOC (event_pool->reg,
                                                      event_pool->count *
                                                      sizeof (*event_pool->reg));

                        if (!event_pool->reg) {
                                gf_log ("epoll", GF_LOG_ERROR,
                                        "event registry re-allocation failed");
                                goto unlock;
                        }
                }

                idx = event_pool->used;
                event_pool->used++;
                //向event_pool中加入一个注册信息
                event_pool->reg[idx].fd = fd;
                event_pool->reg[idx].events = EPOLLPRI;
                event_pool->reg[idx].handler = handler;
                event_pool->reg[idx].data = data;
....
                event_pool->changed = 1;

                epoll_event.events = event_pool->reg[idx].events;
                ev_data->fd = fd;
                ev_data->idx = idx;

                //注册到epoll
                ret = epoll_ctl (event_pool->fd, EPOLL_CTL_ADD, fd,
                                 &epoll_event);
 ....

 

 

 

int main() { int server_fd, epoll_fd; struct sockaddr_in address; struct epoll_event event, events[MAX_EVENTS]; socklen_t addrlen = sizeof(address); // 创建服务器socket if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) { perror("socket failed"); exit(EXIT_FAILURE); } // 设置socket选项 int opt = 1; if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) { perror("setsockopt"); close(server_fd); exit(EXIT_FAILURE); } // 绑定地址和端口 address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons(PORT); if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) { perror("bind failed"); close(server_fd); exit(EXIT_FAILURE); } // 开始监听 if (listen(server_fd, 128) < 0) { perror("listen"); close(server_fd); exit(EXIT_FAILURE); } printf("Server listening on port %d\n", PORT); printf("Web root: %s\n", WEB_ROOT); // 创建epoll实例 epoll_fd = epoll_create1(0); if (epoll_fd < 0) { perror("epoll_create1"); close(server_fd); exit(EXIT_FAILURE); } // 添加服务器socket到epoll event.events = EPOLLIN; event.data.fd = server_fd; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event) < 0) { perror("epoll_ctl: server_fd"); close(server_fd); close(epoll_fd); exit(EXIT_FAILURE); } // 初始化线程池 thread_pool_init(&thread_pool, THREAD_POOL_SIZE); printf("Thread pool initialized with %d threads\n", THREAD_POOL_SIZE); // 主循环 while (1) { int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); if (nfds < 0) { perror("epoll_wait"); break; } for (int i = 0; i < nfds; i++) { if (events[i].data.fd == server_fd) { // 处理新连接 struct sockaddr_in client_addr; socklen_t addr_len = sizeof(client_addr); int client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &addr_len); if (client_fd < 0) { perror("accept"); continue; } // 设置非阻塞 set_nonblocking(client_fd); // 添加客户端socket到epoll event.events = EPOLLIN | EPOLLET; event.data.fd = client_fd; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &event) < 0) { perror("epoll_ctl: client_fd"); close(client_fd); continue; } // 打印新连接信息 char client_ip[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &(client_addr.sin_addr), client_ip, INET_ADDRSTRLEN); printf("New connection from %s:%d\n", client_ip, ntohs(client_addr.sin_port)); } else { // 处理客户端请求 int client_fd = events[i].data.fd; // 获取客户端地址 struct sockaddr_in client_addr; socklen_t addr_len = sizeof(client_addr); if (getpeername(client_fd, (struct sockaddr *)&client_addr, &addr_len) < 0) { perror("getpeername failed"); close(client_fd); continue; } // 将任务添加到线程池 add_task(client_fd, client_addr); // 从epoll中移除,避免重复处理 epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client_fd, NULL); } } } // 清理 close(server_fd); close(epoll_fd); // 关闭线程池 thread_pool.shutdown = 1; pthread_cond_broadcast(&thread_pool.cond); for (int i = 0; i < thread_pool.thread_count; i++) { pthread_join(thread_pool.threads[i], NULL); } free(thread_pool.threads); free(task_queue); return 0; } 可以将main函数的服务器初始化部分(主循环之前)封装到一个startup函数中吗
最新发布
08-16
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <sys/epoll.h> #include <pthread.h> #include <fcntl.h> #define MAX_EVENTS 1024 #define THREAD_POOL_SIZE 4 #define BUFFER_SIZE 4096 #define PORT 8080 // 任务队列结构 typedef struct { void (*task_function)(int); int client_fd; } Task; // 线程池结构 typedef struct { pthread_t *threads; Task *task_queue; int queue_size; int queue_capacity; int head; int tail; int count; pthread_mutex_t lock; pthread_cond_t task_cond; pthread_cond_t space_cond; } ThreadPool; // 全局变量 ThreadPool thread_pool; int epoll_fd; // 设置非阻塞套接字 void set_nonblocking(int fd) { int flags = fcntl(fd, F_GETFL, 0); fcntl(fd, F_SETFL, flags | O_NONBLOCK); } // 初始化线程池 void thread_pool_init(ThreadPool *pool, int size, int capacity) { pool->threads = malloc(size * sizeof(pthread_t)); pool->task_queue = malloc(capacity * sizeof(Task)); pool->queue_size = 0; pool->queue_capacity = capacity; pool->head = 0; pool->tail = 0; pool->count = 0; pthread_mutex_init(&pool->lock, NULL); pthread_cond_init(&pool->task_cond, NULL); pthread_cond_init(&pool->space_cond, NULL); for (int i = 0; i < size; i++) { pthread_create(&pool->threads[i], NULL, (void *)worker_thread, pool); } } // 工作线程函数 void *worker_thread(void *arg) { ThreadPool *pool = (ThreadPool *)arg; while (1) { pthread_mutex_lock(&pool->lock); // 等待任务 while (pool->queue_size == 0) { pthread_cond_wait(&pool->task_cond, &pool->lock); } // 获取任务 Task task = pool->task_queue[pool->head]; pool->head = (pool->head + 1) % pool->queue_capacity; pool->queue_size--; pthread_cond_signal(&pool->space_cond); pthread_mutex_unlock(&pool->lock); // 执行任务 task.task_function(task.client_fd); // 处理完成后重新注册到epoll struct epoll_event event; event.events = EPOLLIN | EPOLLET; event.data.fd = task.client_fd; epoll_ctl(epoll_fd, EPOLL_CTL_MOD, task.client_fd, &event); } return NULL; } // 添加任务到线程池 void add_task(ThreadPool *pool, void (*task_function)(int), int client_fd) { pthread_mutex_lock(&pool->lock); // 等待队列空间 while (pool->queue_size == pool->queue_capacity) { pthread_cond_wait(&pool->space_cond, &pool->lock); } // 添加任务 pool->task_queue[pool->tail].task_function = task_function; pool->task_queue[pool->tail].client_fd = client_fd; pool->tail = (pool->tail + 1) % pool->queue_capacity; pool->queue_size++; pthread_cond_signal(&pool->task_cond); pthread_mutex_unlock(&pool->lock); } // 处理HTTP请求的函数 void handle_request(int client_fd) { char buffer[BUFFER_SIZE]; ssize_t bytes_read = recv(client_fd, buffer, BUFFER_SIZE - 1, 0); if (bytes_read > 0) { buffer[bytes_read] = '\0'; // 简单的HTTP响应 const char *response = "HTTP/1.1 200 OK\r\n" "Content-Type: text/plain\r\n" "Content-Length: 31\r\n\r\n" "Hello from C epoll + thread pool!"; send(client_fd, response, strlen(response), 0); } // 注意:实际应用中需要处理Keep-Alive close(client_fd); } int main() { // 创建服务器套接字 int server_fd = socket(AF_INET, SOCK_STREAM, 0); if (server_fd == -1) { perror("socket"); exit(EXIT_FAILURE); } // 设置SO_REUSEADDR int opt = 1; setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); // 绑定地址 struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; addr.sin_port = htons(PORT); if (bind(server_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) { perror("bind"); exit(EXIT_FAILURE); } // 监听 if (listen(server_fd, SOMAXCONN) == -1) { perror("listen"); exit(EXIT_FAILURE); } // 创建epoll实例 epoll_fd = epoll_create1(0); if (epoll_fd == -1) { perror("epoll_create1"); exit(EXIT_FAILURE); } // 添加服务器套接字到epoll struct epoll_event event; event.events = EPOLLIN | EPOLLET; event.data.fd = server_fd; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event) == -1) { perror("epoll_ctl: server_fd"); exit(EXIT_FAILURE); } // 初始化线程池 thread_pool_init(&thread_pool, THREAD_POOL_SIZE, 256); // 事件循环 struct epoll_event events[MAX_EVENTS]; printf("Server running on port %d...\n", PORT); while (1) { int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); if (nfds == -1) { perror("epoll_wait"); exit(EXIT_FAILURE); } for (int i = 0; i < nfds; i++) { // 新连接 if (events[i].data.fd == server_fd) { struct sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); int client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len); if (client_fd == -1) { perror("accept"); continue; } // 设置非阻塞 set_nonblocking(client_fd); // 添加到epoll event.events = EPOLLIN | EPOLLET; event.data.fd = client_fd; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &event) == -1) { perror("epoll_ctl: client_fd"); close(client_fd); } } // 客户端数据 else if (events[i].events & EPOLLIN) { int client_fd = events[i].data.fd; // 从epoll中暂时移除 epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client_fd, NULL); // 添加到线程池 add_task(&thread_pool, handle_request, client_fd); } } } close(server_fd); return 0; } 网页加载非常缓慢
08-05
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值