LT 水平模式(默认模式)
水平模式可以简称为LT模式,LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket。
在这种做法中,内核通知使用者哪些文件描述符已经就绪,之后就可以对这些已就绪的文件描述符进行IO操作了。
如果我们不作任何操作,内核还是会继续通知使用者。
特点:
- 读事件:如果文件描述符对应的读缓冲区还有数据,读事件就会被触发,epoll_wait()解除阻塞
- 当读事件被触发,epoll_wait()解除阻塞,之后就可以接收数据了
- 如果接收数据的buf很小,不能全部将缓冲区数据读出,那么读事件会继续被触发,直到数据被全部读出,如果接收数据的内存相对较大,读数据的效率也会相对较高(减少了读数据的次数)
- 因为读数据是被动的,必须要通过读事件才能知道有数据到达了,因此对于读事件的检测是必须的
- 写事件:如果文件描述符对应的写缓冲区可写,写事件就会被触发,epoll_wait()解除阻塞
- 当写事件被触发,epoll_wait()解除阻塞,之后就可以将数据写入到写缓冲区了
- 写事件的触发发生在写数据之前而不是之后,被写入到写缓冲区中的数据是由内核自动发送出去的
- 如果写缓冲区没有被写满,写事件会一直被触发
- 因为写数据是主动的,并且写缓冲区一般情况下都是可写的(缓冲区不满),因此对于写事件的检测不是必须的
示例代码(buf较小,一次无法接收所有数据)
#include <stdio.h>
#include <ctype.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>
int main()
{
printf("%s 向你问好!\n", "IOepoll");
int lfd = socket(AF_INET, SOCK_STREAM, 0);
if (lfd == -1)
{
perror("socket");
return -1;
}
sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(9999);
saddr.sin_addr.s_addr = INADDR_ANY;
int ret = bind(lfd, (sockaddr*)&saddr, sizeof(saddr));
if (ret == -1)
{
perror("bind error");
return -1;
}
ret = listen(lfd, 128);
if (ret == -1)
{
perror("listen error");
return -1;
}
//创建epoll示例
int epfd = epoll_create(1);
if (epfd == -1)
{
perror("epoll_create error");
return -1;
}
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = lfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
struct epoll_event evs[1024];
int size = sizeof(evs) / sizeof(evs[0]);
while (1)
{
int num = epoll_wait(epfd, evs, size, -1);
printf("num = %d \n", num);
for (int i = 0; i < num; i++)
{
int fd = evs[i].data.fd;
if (fd == lfd)
{
int cfd = accept(fd, NULL, NULL);
//struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = cfd;
//这一步会做拷贝,所以说ev用一个也是可以的
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
}
else//通信的描述符
{
char buf[5];
int len = recv(fd, buf, sizeof(buf), 0);
if (len == -1)
{
perror("recv error");
return -1;
}
else if (len == 0)
{
printf("client disconnect...\n");
//先删除再做操作
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
close(fd);
break;
}
printf("read buf = %s \n", buf);
for (int i = 0; i < len; i++)
{
buf[i] = toupper(buf[i]);
}
printf("after buf = %s \n", buf);
ret = send(fd, buf, strlen(buf) + 1, 0);
if (ret == -1)
{
perror("send error");
return -1;
}
}
}
}
close(lfd);
return 0;
}
这样如果发送了很多数据,第一次没有处理完的数据就会留在cfd的读缓冲区,epoll的lt模式就会在下一次中直接通知epoll事件
例如客户端发送
123adfadfaf
服务端就会显示
num = 1
num = 1
read buf = 123ad
after buf = 123AD
num = 1
read buf = fadfa
after buf = FADFA
num = 1
read buf = f
after buf = F
ET 边沿模式
边沿模式可以简称为ET模式,ET(edge-triggered)是高速工作方式,只支持no-block socket。
在这种模式下,当文件描述符从未就绪变为就绪时,内核会通过epoll通知使用者。
然后它会假设使用者知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知(only once)。
如果我们对这个文件描述符做IO操作,从而导致它再次变成未就绪,当这个未就绪的文件描述符再次变成就绪状态,内核会再次进行通知,并且还是只通知一次。
ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高。