epoll的两种工作模式:ET(边缘触发)和LT(持续触发)
代码
struct epoll_event event;
event.events = EPOLLIN | EPOLLET;
监听事件默认是LT模式,因此如果要设置为ET模式,需要手动设置epoll_event.events
从头说起
我们知道,将监听事件委托给内核之后,内核会维护一个缓冲区用来从另一头的套接字中读取数据;在服务器端的代码中可以通过read来读这个缓冲区。
在ET模式下,epoll_wait仅在有连接到达时才返回,我们来看看,下面是主工作循环。这里专门将read的字节数设置为MAXLINE/2,即每次只读取缓冲区保存数据的一半。按照ET模式,我们只能读到缓冲区的前MAXLINE/2个数据,而后面的一半则无法读取。[ps:MAXLINE=10]
while (1) {
epoll_wait(efd, res_event, 10, -1);
if (res_event[0].data.fd == conn_fd)
{
len = read(conn_fd, buf, MAXLINE / 2);
write(STDOUT_FILENO, buf, len);
}
}
启动server,并连接一个client,
![18ea91fe677f29623b876793e48344bf.png](https://i-blog.csdnimg.cn/blog_migrate/f651c35f6a426fa7ff139ff1dcb4ebc2.png)
让client输入1234567890[10个数据],
![5cd08c2f573ceaba1c0375c1fc31f0fe.png](https://i-blog.csdnimg.cn/blog_migrate/3fd27211f0158d10f216d97eb27a8c27.png)
我们观察服务器端的输出,
![928c51114b4734699f336948a81d47fe.png](https://i-blog.csdnimg.cn/blog_migrate/d483c3b0b128a20b27ce7ced0e7ad438.png)
可见,这次读取只读到了一半的数据,并且server再次阻塞到epoll_wat。
现在,把epoll_wait的模式设置为LT模式,然后在做同样的事情,
![84d502330da461b040046b82a1e70fe1.png](https://i-blog.csdnimg.cn/blog_migrate/433da9476589e56860c614300275a5d1.png)
可见,epoll_wait对一次client的输入返回了两次。
非阻塞IO
当然,我们希望每次都能完整的获得client的传输的数据,但这意为着只能使用LT模式吗?这就要引入epoll的非阻塞IO模式,它需要配合
- 对连接的套接字(accpet返回的)的读操作设置为非阻塞模式文件
- 对内核缓冲区进行轮询read操作
首先,我们先尝试只做2而不做1,即在一次epoll_wait返回后对缓冲区轮询而不将连接的套接字设置为非阻塞。
while (1) {
res = epoll_wait(efd, res_event, 10, -1);
printf("wait res = %dn", res);
if (res_event[0].data.fd == conn_fd)
while ((len = read(conn_fd, buf, MAXLINE/2)) > 0)
write(STDOUT_FILENO, buf, len);
}
这时的效果
![8b8c5c0e4ae48a0219a5f93de91ca43e.png](https://i-blog.csdnimg.cn/blog_migrate/5e246a53b821ee4dbb446ef043c84cf3.png)
可以看到直接读出了缓冲区所有的数据,并且阻塞了。但是注意,此时server是阻塞在了read上而非epoll_wait,因此无法对别的client响应。我们可以通过gdb调试发现这一点
![1ac07af5cacd97e06f60d854e5519b21.png](https://i-blog.csdnimg.cn/blog_migrate/6ed3d4fd4be286c4f3b6bd32a57a856e.png)
在写完最有一个数据后,进程阻塞在read上。如果此时有别的client访问server,那么就会死锁。
鉴于此,需要将建立连接的套接字文件设置为非阻塞模式
int fd_flag = fcntl(conn_fd, F_GETFL);
fd_flag |= O_NONBLOCK;
fcntl(conn_fd, F_SETFL, fd_flag);
这样,当client传输完毕后,read就可以返回,继续进行epoll_wait。