[网络编程]epoll的基础用法
select和epoll都是提供多路I/O复用的手段,以前我在学习网络编程过程中只使用过select(主要是学习的《UNIX网络编程这本书》),后来才知道还有一种算是更高效的I/O复用的方法叫做epoll,于是今天照着网上的教程撸了一遍代码先了解一下epoll的使用,下面记录一下今天学习过程中我觉得还蛮重要的点。
参考的博客:http://blog.csdn.net/ljx0305/article/details/4065058
1、select和epoll
首先是select和epoll方式的区别,在使用select的时候,有一个叫做fd_set的集合,这个集合用来存放所有需要进行复用的I/O(文件描述符),select首先会将这个fd_set集合拷贝进内核,然后内核就会遍历这个集合(阻塞)直到某一个文件描述符满足条件(可读、可写、异常)才返回。select的这种复用方式当文件描述符的数量很大的时候就会变得很低效(每次select都要把所有的fd拷贝进内核,开销非常大,同时遍历fd集合的开销也很大),并且select还有一个非常大的限制就是它支持的文件描述符数量有上限,默认是1024(想要修改这个值需要重新编译内核= =.)。
epoll不知道是不是针对select的缺点才提出的,因为epoll很好地解决了select存在的问题。
因为之前没有接触过epoll,所以首先还是来好好地回味一下epoll使用过程中需要掌握的三个函数:
1 int epoll_create(int size); 2 3 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 4 5 int epoll_wait(int epfd, struct epoll_event *event, int maxevents, int timeout);
函数的功能其实已经很好地体现在它的名字上了,epoll_create()负责创建文件描述符集合,epoll_ctl()(“control”)负责操作这个文件描述符集合(插入,删除,更改等),epoll_wait()负责等待某个文件描述符满足条件返回,个人感觉函数名字还是很好记的,就是后两个函数的参数多了一点,尤其是还有一个epoll_event结构体,需要详细了解。
- int epoll_create(int size);
参数size告诉内核epoll监听的数量有多大,需要注意的是这个函数返回的也是一个文件描述符(就是epoll后两个函数中用到的参数epfd),我个人觉得它应该是代表了内核中的某一个文件描述符集合。为什么需要返回这么一个文件描述符呢,这与epoll的实现机制有关,也就是为什么epoll会比select高效的原因。上面提到select每次操作时都要把所有文件描述符拷贝进内核,再由内核轮询选择出满足条件的文件描述符。epoll也需要将文件描述符先拷贝进内存,但是它只做一次,epoll_create()就好像先在内核中开辟出一个固定大小的空的文件描述符集合,之后再将相应的文件描述符放进去或者从中将某一个文件描述符删掉。简单的来说,就是select拷贝进内核的文件描述符集在轮询完之后就没了,所以每次使用都需要再一次拷贝,而epoll拷贝进内核的文件描述符集在epoll使用期间会一直存在,并且就是由epoll_create()返回的这个文件描述符来标记的(为了后续的使用)。
- int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll_ctl()函数就是用来对内核中的那个文件描述符集来进行操作的,第一个参数就是epoll_create()返回的文件描述符,表示该操作是在哪个文件描述符集合上进行的。
第二个参数op(option),顾名思义,就是表示想做什么样的操作,这个参数有三个宏可以选择:
EPOLL_CTL_ADD //添加新的fd EPOLL_CTL_MOD //修改现有的fd EPOLL_CTL_DEL //删除现有的fd
添加和删除比较好理解,修改具体是做什么的我没有实际使用到(我想应该是用来修改fd的监听事件的)。
第三个参数就是需要进行操作的文件描述符值。
这个函数中比较重要的就是这第四个参数了struct epoll_event,首先来看一下它的结构:
1 typedef union epoll_data { 2 void *ptr; 3 int fd; 4 __uint32_t u32; 5 __uint64_t u64; 6 } epoll_data_t; 7 8 struct epoll_event { 9 __uint32_t events; /* Epoll events */ 10 epoll_data_t data; /* User data variable */ 11 };
可以看到,epoll_event结构体中有两个成员,data成员存放的就是函数第三个参数值fd(不知道为什么需要重复),而events成员表示你想在这个fd上监听什么样的事件,它的取值可以是以下宏的集合:
1 EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭); 2 EPOLLOUT:表示对应的文件描述符可以写; 3 EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来); 4 EPOLLERR:表示对应的文件描述符发生错误; 5 EPOLLHUP:表示对应的文件描述符被挂断; 6 EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。 7 EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
目前只用到了EPOLLIN和EPOLLOUT,剩下的宏还没有仔细研究过。单纯使用EPOLLIN和EPOLLOUT的话感觉就和在select函数中设置第二个和第三个参数一样。
- int epoll_wait(int epfd, struct epoll_event *event, int maxevents, int timeout);
介绍完epoll_ctl(),再来看epoll_wait()就很好理解了,第一个参数依然是epoll_create()的返回值,表示你想对内核中的哪个文件描述符集合进行操作。
第二、第三个参数是一起的,第二个参数其实是一个epoll_event结构体的数组指针,maxevents是这个数组的大小(不能超过epoll_create()中参数size的大小),内核会把满足条件的文件描述符相应的event信息存到这个数组中,然后用户就可以从这个数组中读取就绪的文件描述符并进行后续操作。
最后一个参数timeout和select的最后一个参数一样,可以用来设置该操作的超时时间(阻塞或非阻塞的)。
该函数的返回值:
1 -1 : 出错 2 0 : 超时 3 other: 满足条件的fd个数
(未完)