Created: March 16, 2022 3:15 PM
Tags: epoll, io复用, tcp, 网络编程
修改和转载自,下面这一些列blog
写的特别清晰
彻底学会使用epoll(四)–ET的写操作实例分析-lvyilong316-ChinaUnix博客
LT 模式的触发方式
读操作
缓冲区有数据即触发
缓冲区有数据,下面这个程序当键盘键入数据,但是程序没有处理可读的情况。即缓冲区一直有数据,则会产生死循环。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
#include <unistd.h> #include <stdio.h> #include <sys/epoll.h>
int main(void) { int epfd,nfds; struct epoll_event ev,events[5]; epfd = epoll_create(1); ev.data.fd=STDIN_FILENO; ev.events=EPOLLIN; epoll_ctl(epfd,EPOLL_CTL_ADD,STDIN_FILENO,&ev); for(;;) { nfds=epoll_wait(epfd,events,5,-1); for(int i=0;i<nfds;i++) { if(events[i].data.fd == STDIN_FILENO) { printf("hello world!\n"); } } }
return 0; }
|
当缓冲区的数据被读出后,在进行写入即正常,不会重复提醒
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
#include <unistd.h> #include <stdio.h> #include <sys/epoll.h>
int main(void) { int epfd,nfds; char buf[256]; struct epoll_event ev,events[5]; epfd=epoll_create(1); ev.data.fd = STDIN_FILENO; ev.events = EPOLLIN; epoll_ctl(epfd,EPOLL_CTL_ADD,STDIN_FILENO,&ev); for(;;) { nfds=epoll_wait(epfd,events,5,-1); for(int i=0;i<nfds;i++) { if(events[i].data.fd==STDIN_FILENO) { read(STDIN_FILENO,buf,sizeof(buf)); printf("hello world!\n"); } } } return 0; }
|
读出后正常
写操作
缓冲区里有空间即触发
向buffer
写入”hello world!”
后,虽然buffer
没有输出清空,但是LT
模式下只有buffer
有写空间就返回写就绪,所以会一直输出”hello world!”
,当buffer
满的时候,buffer
会自动刷清输出,同样会造成epoll_wait
返回写就绪
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
#include <stdlib.h> #include <stdio.h> #include <sys/epoll.h> #include <unistd.h> int main(){ int fd, epfd; int max_epoll_size = 5, nready; epfd = epoll_create(max_epoll_size); struct epoll_event ev,events[max_epoll_size]; fd = STDOUT_FILENO; ev.data.fd = fd; ev.events = EPOLLOUT; epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev); for(;;){ nready = epoll_wait(epfd, events,max_epoll_size,-1); for(int i =0; i < nready; i++){ if(events[i].data.fd == fd){ printf("Hello world!"); } } } return 0; }
|
ET 模式的触发方式
读操作
- 从不可读到可读
- 有新的数据到达
缓冲区读操作
在ET模式下,缓冲区从空到有数据,但是数据一直不读取。如果没有新的数据到达,它将不再提醒。如果有新的数据到达,它便会提醒
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
|
#include <unistd.h> #include <stdio.h> #include <sys/epoll.h> #include <errno.h>
int main(void) { int errno; int epfd,nfds; struct epoll_event ev,events[5]; epfd=epoll_create(1); ev.data.fd = STDIN_FILENO; ev.events = EPOLLIN|EPOLLET; epoll_ctl(epfd,EPOLL_CTL_ADD,STDIN_FILENO,&ev); for(;;) { nfds = epoll_wait(epfd, events,5,-1); for(int i = 0; i< nfds;i++) { if(events[i].data.fd == STDIN_FILENO) printf("Hello world!\n"); } } return 0; }
|
写操作
- 从不可写到可写
- 缓冲区数据变少
缓冲区写操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
#include <stdlib.h> #include <stdio.h> #include <sys/epoll.h> #include <unistd.h> int main(){ int fd, epfd; int max_epoll_size = 5, nready; epfd = epoll_create(max_epoll_size); struct epoll_event ev,events[max_epoll_size]; fd = STDOUT_FILENO; ev.data.fd = fd; ev.events = EPOLLOUT | EPOLLET; epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev); for(;;){ nready = epoll_wait(epfd, events,max_epoll_size,-1); for(int i =0; i < nready; i++){ if(events[i].data.fd == fd){ printf("Hello world!\n"); } } } return 0; }
|
这个程序的功能是只要标准输出写就绪,就输出“hello world!”
。
我们发现这将是一个死循环。下面具体分析一下这个程序的执行过程:
(1) 首先初始buffer
为空,buffer
中有空间可写,这时无论是ET
还是LT
都会将对应的epitem
加入rdlist
,导致epoll_wait
就返回写就绪。
(2) 程序想标准输出输出”hello world!”
和换行符,因为标准输出为控制台的时候缓冲是“行缓冲”,所以换行符导致buffer
中的内容清空,这就对应第二节中ET
模式下写就绪的第二种情况——当有旧数据被发送走时,即buffer中待写的内容变少得时候会触发fd状态的改变。所以下次epoll_wait
会返回写就绪。之后重复这个过程一直循环下去。
如果将 printf("Hello world!\n");
→ printf("Hello world!");
则会出现下面的情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
#include <stdlib.h> #include <stdio.h> #include <sys/epoll.h> #include <unistd.h> int main(){ int fd, epfd; int max_epoll_size = 5, nready; epfd = epoll_create(max_epoll_size); struct epoll_event ev,events[max_epoll_size]; fd = STDOUT_FILENO; ev.data.fd = fd; ev.events = EPOLLOUT | EPOLLET; epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev); for(;;){ nready = epoll_wait(epfd, events,max_epoll_size,-1); for(int i =0; i < nready; i++){ if(events[i].data.fd == fd){ printf("Hello world!"); } } } return 0; }
|
我们看到程序成挂起状态。因为第一次epoll_wait
返回写就绪后,程序向标准输出的buffer
中写入“hello world!”
,但是因为没有输出换行,所以buffer
中的内容一直存在,下次epoll_wait
的时候,虽然有写空间但是ET
模式下不再返回写就绪。回忆第一节关于ET
的实现,这种情况原因就是第一次buffer
为空,导致epitem
加入rdlist
,返回一次就绪后移除此epitem
,之后虽然buffer
仍然可写,但是由于对应epitem
已经不再rdlist
中,就不会对其就绪fd
的events
的在检测了。
如果重新修改事件监听 epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
|
#include <stdlib.h> #include <stdio.h> #include <sys/epoll.h> #include <unistd.h> int main(){ int fd, epfd; int max_epoll_size = 5, nready; epfd = epoll_create(max_epoll_size); struct epoll_event ev,events[max_epoll_size]; fd = STDOUT_FILENO; ev.data.fd = fd; ev.events = EPOLLOUT | EPOLLET; epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev); for(;;){ nready = epoll_wait(epfd, events,max_epoll_size,-1); for(int i =0; i < nready; i++){ if(events[i].data.fd == fd){ printf("Hello world!"); epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev); } } } return 0; }
|