IO多路復(fù)用介紹
- IO 多路復(fù)用是一種同步IO模型,實(shí)現(xiàn)一個線程可以監(jiān)視多個文件句柄娄帖;
- 一旦某個文件句柄就緒也祠,就能夠通知應(yīng)用程序進(jìn)行相應(yīng)的讀寫操作;
- 沒有文件句柄就緒就會阻塞應(yīng)用程序近速,交出CPU诈嘿。
- IO多路復(fù)用的三種實(shí)現(xiàn)方式 select/poll/epoll
epoll
epoll的全稱是eventpoll,它是基于event事件進(jìn)行實(shí)現(xiàn)的数焊,是linux特有的I/O復(fù)用函數(shù)永淌。
- 有點(diǎn)
文件描述符沒有限制(使用鏈表存儲) - 三個核心函數(shù)
epoll_create(建立一個epoll對象)
epoll_ctl(epoll的事件注冊函數(shù))
epoll_wait(等待事件的產(chǎn)生,類似于select()調(diào)用)
int epoll_create(int size)
在內(nèi)核中創(chuàng)建epoll實(shí)例并返回一個epoll文件描述符佩耳。 在最初的實(shí)現(xiàn)中遂蛀,調(diào)用者通過 size 參數(shù)告知內(nèi)核需要監(jiān)聽的文件描述符數(shù)量。如果監(jiān)聽的文件描述符數(shù)量超過 size, 則內(nèi)核會自動擴(kuò)容干厚。而現(xiàn)在 size 已經(jīng)沒有這種語義了李滴,但是調(diào)用者調(diào)用時 size 依然必須大于 0螃宙,以保證后向兼容性。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
向 epfd 對應(yīng)的內(nèi)核epoll 實(shí)例添加所坯、修改或刪除對 fd 上事件 event 的監(jiān)聽谆扎。
- epfd:epoll_create的返回值
- op:表示動作
EPOLL_CTL_ADD:添加新的事件到文件描述符。
EPOLL_CTL_MOD: 修改文件描述符上的事件類型
EPOLL_CTL_DEL: 刪除一個事件
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
等待事件
當(dāng) timeout 為 0 時芹助,epoll_wait 永遠(yuǎn)會立即返回堂湖。而 timeout 為 -1 時,epoll_wait 會一直阻塞直到任一已注冊的事件變?yōu)榫途w状土。當(dāng) timeout 為一正整數(shù)時无蜂,epoll 會阻塞直到計(jì)時 timeout 毫秒終了或已注冊的事件變?yōu)榫途w。因?yàn)閮?nèi)核調(diào)度延遲蒙谓,阻塞的時間可能會略微超過 timeout 毫秒斥季。
- demo
#include<stdio.h>
#include<arpa/inet.h>
#include<sys/epoll.h>
#include<unistd.h>
#include<ctype.h>
#define MAXLEN 1024
#define SERV_PORT 8000
#define MAX_OPEN_FD 1024
int main(int argc,char *argv[])
{
int listenfd,connfd,efd,ret;
char buf[MAXLEN];
struct sockaddr_in cliaddr,servaddr;
socklen_t clilen = sizeof(cliaddr);
struct epoll_event tep,ep[MAX_OPEN_FD];
listenfd = socket(AF_INET,SOCK_STREAM,0);
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
listen(listenfd,20);
// 創(chuàng)建一個epoll fd
efd = epoll_create(MAX_OPEN_FD);
tep.events = EPOLLIN;tep.data.fd = listenfd;
// 把監(jiān)聽socket 先添加到efd中
ret = epoll_ctl(efd,EPOLL_CTL_ADD,listenfd,&tep);
// 循環(huán)等待
for (;;)
{
// 返回已就緒的epoll_event,-1表示阻塞,沒有就緒的epoll_event,將一直等待
size_t nready = epoll_wait(efd,ep,MAX_OPEN_FD,-1);
for (int i = 0; i < nready; ++i)
{
// 如果是新的連接,需要把新的socket添加到efd中
if (ep[i].data.fd == listenfd )
{
connfd = accept(listenfd,(struct sockaddr*)&cliaddr,&clilen);
tep.events = EPOLLIN;
tep.data.fd = connfd;
ret = epoll_ctl(efd,EPOLL_CTL_ADD,connfd,&tep);
}
// 否則,讀取數(shù)據(jù)
else
{
connfd = ep[i].data.fd;
int bytes = read(connfd,buf,MAXLEN);
// 客戶端關(guān)閉連接
if (bytes == 0){
ret =epoll_ctl(efd,EPOLL_CTL_DEL,connfd,NULL);
close(connfd);
printf("client[%d] closed\n", i);
}
else
{
for (int j = 0; j < bytes; ++j)
{
buf[j] = toupper(buf[j]);
}
// 向客戶端發(fā)送數(shù)據(jù)
write(connfd,buf,bytes);
}
}
}
}
return 0;
}
epoll_event
typedef union epoll_data
{
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event
{
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
} __attribute__ ((__packed__));
原理
可以看到
- epoll_create 創(chuàng)建了一個對象,這個對象包含一個event_poll線程池(紅黑樹)和rdlist就緒對列表(雙向鏈表)
- epoll_ctl 執(zhí)行epoll_ctl()時,如果增加socket句柄累驮,則檢查在紅黑樹中是否存在酣倾,存在立即返回,不存在則添加到樹干上谤专,然后向內(nèi)核注冊回調(diào)函數(shù)躁锡,用于當(dāng)中斷事件來臨時向準(zhǔn)備就緒鏈表中插入數(shù)據(jù)
- epoll_wait 執(zhí)行epoll_wait()時立刻返回準(zhǔn)備就緒鏈表里的數(shù)據(jù)即可。
當(dāng)事件就緒后置侍,就被加入到 rdlist(就緒鏈表)中稚铣。epoll_wait 檢查是否有事件發(fā)生時,僅僅需要檢查 rdlist 中是否有數(shù)據(jù)即可墅垮。
ep_poll_callback
ep_poll_callback函數(shù)核心功能是將被目標(biāo)fd的就緒事件到來時,將fd對應(yīng)的epitem實(shí)例添加到就緒隊(duì)列耕漱。當(dāng)應(yīng)用調(diào)用epoll_wait()時算色,內(nèi)核會將就緒隊(duì)列中的事件報(bào)告給應(yīng)用
參考
https://www.modb.pro/db/250807
https://www.cnblogs.com/tangxin-blog/p/5470791.html
https://sites.uclouvain.be/SystInfo/usr/include/sys/epoll.h.html
https://zhuanlan.zhihu.com/p/389407114