IO epoll的LT ET

发布时间 2023-11-11 04:37:53作者: LiviaYu

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模式高。