1. 基本概念
我们在上一篇文章里已经讲过了改进select和poll的两个角度,而epoll正好是实现了这两方面,因为C10k问题就可以使用epoll来解决,值得一提的是现在经常提到的不是C10k问题,而是C1000k问题,也就是百万级的并发服务器,这个问题我们应该后面会继续解决。
首先,epoll中使用红黑树来存储文件描述符结合,这使得增加、查找和修改文件描述符的时间复杂度都是O(logn)相对于select/poll有了质的提高,同时红黑树的O(logn)的修改操作使得每次只需要向内核传入待检测的fd就可以,不需要来回copy。
其次,epoll的e是event的缩写,也就是event驱动的poll。epoll的内部有一个wait_queue作为一个等待队列存储可以进行回调的fd,这样不需要遍历fd集合来获得可读可写的fd。
通过这两个方面的改进,epoll极大地提高了I/O多路复用的效率,也成功解决了C10K问题。
2. 代码分析
//epoll
int epfd = epoll_create(1/*大于0即可,遗留参数*/); //int size
//存储fd的数据结构
struct epoll_event ev;//成员: events, data
ev.events = EPOLLIN;
ev.data.fd = sockfd;
//监听套接字的fd加入内核的红黑树中
epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);
//设置fd上限
struct epoll_event events[1024] = {0};
while (1) {
//调用回调函数
int nready = epoll_wait(epfd, events, 1024, -1);
int i = 0;
for (i = 0; i < nready; i++) {
int connfd = events[i].data.fd;
//建立服务端连接,开始监听
if (sockfd == connfd) {
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
printf("clientfd: %d\n", clientfd);
//ev.events = EPOLLIN; 水平触发(默认)
ev.events = EPOLLIN | EPOLLET;//边缘触发
ev.data.fd = clientfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev);
} else if (events[i].events && EPOLLIN) {
char buffer[128] = {0};
int count = recv(connfd, buffer, 5, 0);
if (count == 0) { //调用close()
printf("disconnect\n");
epoll_ctl(epfd, EPOLL_CTL_DEL, connfd, NULL);
close(i);
continue;
}
send(connfd, buffer, count/*128*/, 0);
printf("sockfd: %d, clientfd: %d, count: %d, buffer: %s\n", sockfd, connfd, count, buffer);
}
}
}
3. 问题分析
针对epoll的学习,我们可以从以下三个问题入手:
1. epoll中有没有mmap,如何看出?
a. 首先在epoll源码中的__put_user()函数中可以看出内核会把当前事件copy给用户,所以没有mmap,和select/poll一样都存在copy操作。这一部分我们应该会在下一篇epoll源码分析中说明。
b. 其次,从理论上理解的话。epoll是异步通信,而mmap是用来同步通信,这就需要互斥锁来解决可能的并发问题,不同的通信机制不适合。其次,mmap可能带来安全问题,epoll不适用mmap可以实现一定程度的数据隔离,避免安全风险。
2. ET 边缘触发(发送大文件时,while一次全部读完,搭配非阻塞I/O) LT 水平触发(有数据时一直触发,可以用于分包Redis)
epoll默认是水平触发,也就是当客户端连接建立之后,recv操作会循环执行直到把缓冲区的所有数据都读完毕。边缘触发则是设定一个值,每次recv操作只读该值的数据,如果没读完则在下一次epoll_wait()回调函数调用是继续读上次未完成的和新进入缓冲区的。
3. epoll是否线程安全?
a. epoll的三类基本操作:epoll_create(), epoll_ctl(), epoll_wait()是线程安全的,可以在多线程中进行访问。
b. epoll中使用红黑树存储的fd集合,如果对该集合进行操作可能是不安全的,多个线程对fd集合读写可能会发生数据竞争。
本文介绍了Epoll的工作原理,包括其红黑树优化的文件描述符管理、event驱动的I/O处理方式,以及针对C10k问题的解决方案。同时,文章分析了epoll与mmap的关系、边缘触发与水平触发的区别以及线程安全问题。

433

被折叠的 条评论
为什么被折叠?



