一、概述
epoll是Linux內核提供的一種高效的I/O多路復用機制,是使用Linux進行網路編程的必備技能之一。相比於select和poll,epoll具有更高的性能和更強的擴展性,能夠支持更多的並發連接,並且不會因為連接數的增加而導致性能下降。
它的運作原理是通過一個文件描述符,將多個socket文件描述符上的I/O事件集中到一個地方進行處理,而不像select和poll那樣需要每個socket文件描述符都進行輪詢並處理。
二、epoll的基本使用
使用epoll的步驟比較簡單,大致可以分為以下幾步:
1. 創建一個epoll實例,並監聽文件描述符
int epoll_create(int size); int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
其中,epoll_create用於創建一個epoll實例,size指定這個實例中所能包含的最大文件描述符數。而epoll_ctl用於向這個epoll實例中添加或刪除文件描述符,op參數指定添加或刪除,fd是需要添加或刪除的文件描述符,event結構體則用於指定這個文件描述符所關注的事件類型,如可讀事件、可寫事件等。
2. 調用epoll_wait進行等待
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
epoll_wait會阻塞直到有一個或多個文件描述符上發生了關注的事件,或者超時,或者被信號中斷。它將等待的事件填充到events數組中,並返回實際觸發事件的文件描述符數目。
三、epoll的高級應用
1. 邊緣觸發模式
epoll可以選擇邊緣觸發和水平觸發兩種模式,邊緣觸發模式是指只有當有新的狀態變化時才會觸發,而水平觸發則是一直觸發,直到該事件被處理。邊緣觸發可以減少不必要的事件處理,提高效率,但是需要注意邊緣觸發的處理方式,需要一次性處理完所有的事件。
2. ET與LT模式
epoll的工作模式可以分為ET和LT兩種模式,ET模式是指只有當I/O事件的狀態發生變化的時候,epoll才會通知用戶應用程序來讀取或者寫入數據。而LT模式則是在需要就緒的時候會每次通知一次用戶應用程序來處理,也就是無論是否讀寫完成都會通知用戶一次。使用ET模式需要一次性讀完所有數據,否則不能再次觸發事件。
3. 優化epoll性能
在使用epoll時,需要注意一些可以優化性能的地方。比如使用epoll的時候建議使用EPOLLONESHOT,可以保證同一時刻只有一個線程處理同一事件。
四、代碼示例
下面是一個使用epoll的簡單示例:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include<sys/epoll.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#define MAX_EVENTS 1024
#define PORT 8888
int main(int argc, char **argv)
{
int listenfd,connfd,epfd,nfds,i,n;
ssize_t nready;
char buf[BUFSIZ];
struct sockaddr_in servaddr,cliaddr;
socklen_t cliaddrlen;
struct epoll_event ev, events[MAX_EVENTS];
// 創建socket並綁定監聽
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(PORT);
bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
listen(listenfd, 20);
// 創建epoll實例並添加監聽描述符
epfd = epoll_create(MAX_EVENTS);
ev.data.fd = listenfd;
ev.events = EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);
// 循環等待,直到有事件觸發
while (1)
{
nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);
if (nfds == -1)
{
perror("epoll_wait");
continue;
}
// 循環處理所有事件
for (n = 0; n < nfds; ++n)
{
// 如果事件是listenfd,則處理新連接
if (events[n].data.fd == listenfd)
{
cliaddrlen = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddrlen);
if (connfd < 0)
{
perror("accept");
continue;
}
ev.data.fd = connfd;
ev.events = EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);
printf("new connection from %s:%d\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));
}
// 否則處理已連接的客戶端
else
{
if ((nready = read(events[n].data.fd, buf, BUFSIZ)) < 0)
{
if (errno == ECONNRESET)
{
close(events[n].data.fd);
epoll_ctl(epfd, EPOLL_CTL_DEL, events[n].data.fd, NULL);
}
else
{
perror("read error");
}
}
else if (nready == 0)
{
close(events[n].data.fd);
epoll_ctl(epfd, EPOLL_CTL_DEL, events[n].data.fd, NULL);
printf("connection closed\n");
}
else
{
write(STDOUT_FILENO, buf, nready);
write(events[n].data.fd, buf, nready);
}
}
}
}
close(listenfd);
return 0;
}
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/289386.html
微信掃一掃
支付寶掃一掃