一、什麼是 epoll?
Epoll 是 Linux 下一種可以處理大量文件描述符的 I/O 操作的機制,它可以在一個線程中監視多個文件描述符,當某個文件描述符就緒時,可以立即通知程序進行相應的操作。它採用了一種基於回調的事件驅動機制,使 I/O 處理效率更高。
二、使用 epoll 的優點
1、高效性:Epoll 採用了事件驅動機制,僅當有數據就緒時才會加以處理,避免了無效的等待。同時,它使用紅黑樹來存放文件描述符,所以當文件描述符數量較大時,查詢的效率比 select 和 poll 更高。
2、可擴展性:在實際使用中,我們可以通過配置參數來調整 epoll 實例的大小,從而實現更精細的控制。
3、線程安全:在多線程程序中,使用 epoll 監聽文件描述符的讀寫事件時,每個線程都會有自己的存儲空間,避免了競爭條件。
三、使用 epoll 的實例
1、epoll 實現 TCP 服務器
#include <sys/epoll.h> #include <stdio.h> #include <sys/socket.h> #include <arpa/inet.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #include <stdlib.h> #define LISTEN_BACKLOG 50 #define MAX_EPOLL_EVENTS 50 int set_nonblocking(int fd) { int flags; if ((flags = fcntl(fd, F_GETFL, 0)) == -1) { return -1; } flags |= O_NONBLOCK; if (fcntl(fd, F_SETFL, flags) == -1) { return -1; } return 0; } int main(int argc, char **argv) { struct sockaddr_in my_addr, client_addr; int listener, new_fd, epfd, nfds, n, i; char buf[BUFSIZ]; socklen_t client_len = sizeof(client_addr); struct epoll_event ev, events[MAX_EPOLL_EVENTS]; epfd = epoll_create(50); memset(&my_addr, 0, sizeof(struct sockaddr)); my_addr.sin_family = AF_INET; my_addr.sin_port = htons(7788); my_addr.sin_addr.s_addr = htonl(INADDR_ANY); if ((listener = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("socket create failed"); exit(1); } int opt = 1; setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); if (bind(listener, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1) { perror("bind failed!"); exit(1); } set_nonblocking(listener); if (listen(listener, LISTEN_BACKLOG) == -1) { perror("listen failed!"); exit(1); } ev.events = EPOLLIN | EPOLLET; ev.data.fd = listener; epoll_ctl(epfd, EPOLL_CTL_ADD, listener, &ev); while (1) { nfds = epoll_wait(epfd, events, MAX_EPOLL_EVENTS, -1); if (nfds == -1) { perror("epoll_wait error!"); exit(1); } for (i = 0; i < nfds; i++) { if (events[i].data.fd == listener) { while ((new_fd = accept(listener, (struct sockaddr *)&client_addr, &client_len)) != -1) { set_nonblocking(new_fd); ev.events = EPOLLIN | EPOLLET; ev.data.fd = new_fd; epoll_ctl(epfd, EPOLL_CTL_ADD, new_fd, &ev); } if (errno != EAGAIN && errno != ECONNABORTED && errno != EPROTO && errno != EINTR) { perror("accept error!"); } } else if (events[i].events & EPOLLIN) { while ((n = read(events[i].data.fd, buf, BUFSIZ)) > 0) { fprintf(stdout, "Data received: %s\n", buf); } if (n == 0) { fprintf(stdout, "Client closed by peer, fd: %d\n", events[i].data.fd); close(events[i].data.fd); epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, NULL); } else if (n == -1 && errno != EAGAIN) { perror("read error!"); close(events[i].data.fd); epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, NULL); } } } } close(listener); return 0; }
代碼實現了基於 epoll 的 TCP 服務器,其中設置了底層監聽端口,並通過 accept 接受連接,設置每個客戶端連接的事件類型,處理客戶端的讀取事件。它使用 epoll_wait 函數監聽客戶端連接狀態,當客戶端連接時,創建新的文件描述符並加入 epoll 的監聽隊列,當客戶端發來數據時,讀取數據並在控制台上輸出。
2、epoll 實現 UDP 服務器
#include <stdio.h> #include <sys/socket.h> #include <arpa/inet.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #include <stdlib.h> #include <sys/epoll.h> #define LISTEN_BACKLOG 50 #define MAX_EPOLL_EVENTS 50 int set_nonblocking(int fd) { int flags; if ((flags = fcntl(fd, F_GETFL, 0)) == -1) { return -1; } flags |= O_NONBLOCK; if (fcntl(fd, F_SETFL, flags) == -1) { return -1; } return 0; } int main(int argc, char **argv) { struct sockaddr_in my_addr, client_addr; int listener, new_fd, epfd, nfds, n, i, len; char buf[BUFSIZ]; socklen_t client_len = sizeof(client_addr); struct epoll_event ev, events[MAX_EPOLL_EVENTS]; epfd = epoll_create(50); memset(&my_addr, 0, sizeof(struct sockaddr)); my_addr.sin_family = AF_INET; my_addr.sin_port = htons(7788); my_addr.sin_addr.s_addr = htonl(INADDR_ANY); if ((listener = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { perror("socket create failed"); exit(1); } int opt = 1; setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); if (bind(listener, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1) { perror("bind failed!"); exit(1); } set_nonblocking(listener); ev.events = EPOLLIN | EPOLLET; ev.data.fd = listener; epoll_ctl(epfd, EPOLL_CTL_ADD, listener, &ev); while (1) { nfds = epoll_wait(epfd, events, MAX_EPOLL_EVENTS, -1); if (nfds == -1) { perror("epoll_wait error!"); exit(1); } for (i = 0; i < nfds; i++) { if (events[i].data.fd == listener) { len = sizeof(struct sockaddr); n = recvfrom(listener, buf, 256, 0, (struct sockaddr *)&client_addr, &len); if (n == -1) { perror("recvfrom error!"); continue; } printf("Data received: %s\n", buf); } } } close(listener); return 0; }
代碼實現了基於 epoll 的 UDP 服務器,其中設置了底層監聽端口,並通過 recvfrom 接收來自客戶端的數據,使用 epoll_wait 函數監聽客戶端的讀事件。當客戶端發來數據時,讀取數據並在控制台上輸出。
四、總結
本文對使用 epoll 進行高效 I/O 處理進行了詳細的介紹,包括了 epoll 的優點以及示例代碼實現。使用 epoll 可以大大提高 I/O 處理的效率,特別適用於大量文件描述符的 I/O 處理。同時,epoll 還具有可擴展性和線程安全性等優點。在實際使用中,我們可以根據需要來調整 epoll 實例的大小,提高控制的精確度。
原創文章,作者:PUDFB,如若轉載,請註明出處:https://www.506064.com/zh-hk/n/372549.html