I/O多路复用:单个线程可以同时处理多个I/O;
一、select:成功则返回已准备好的文件描述符个数,超时则返回0,出错则返回-1;
(1)头文件包含:#include 、、;
(2)select返回后集合fdset中只有事件发生了的fd对应的位被置为1,其他位都被清空为0,所以需要依次用FD_ISSET来判断是否被置为1即事件是否发生;
int select(int maxfdp1, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *tvptr);
int FD_ZERO(fd_set *fdset);---删除集合fdset中的所有文件描述符
int FD_SET(int fd, fd_set *fdset);---加入fd到集合fdset中
int FD_CLR(int fd, fd_set *fdset);---删除集合fdset中的fd
int FD_ISSET(int fd, fd_set *fdset);---判断fd为是否可读写
1 #include
2 #include
3
4 int main(int argc, char *argv[])5 {6 fd_set readset, writeset;7 int ret = 0;8 FD_ZERO(&readset);9 FD_ZERO(&writeset);10 FD_SET(3, &readset);11 FD_SET(5, &writeset);12
13 ret = select(6, &readset, &writeset, NULL, NULL);14 switch(ret)15 {16 case -1: //error
17 break;18 case 0: //timeout
19 break;20 default: //success
21 if(FD_ISSET(3, &readset))22 {23 ;//read fd(3)
24 }25 if(FD_ISSET(5, &writeset))26 {27 ;//write fd(5)
28 }29 break;30 }31 return 0;32 }
View Code
二、poll:成功则返回已准备好的文件描述符个数,超时则返回0,出错则返回-1;
(1)头文件包含:#include
(2)struct pollfd
{
int fd;
short events; //input: interested event
short revents; //output: occured event
};
int poll(struct pollfd fds[], nfds_t nfds, int timeout);
1 #include
2 #define EVENTS_NUM 3
3
4 int main(int argc, char *argv[])5 {6 structpollfd pfds[EVENTS_NUM];7 int ret = 0;8
9 pfds[0].fd = 10;10 pfds[0].events =POLLIN;11 pfds[1].fd = 11;12 pfds[1].events =POLLOUT;13 pfds[2].fd = 12;14 pfds[2].events =POLLERR;15
16 ret = poll(pfds, EVENTS_NUM, -1);17 switch(ret)18 {19 case -1: //error
20 break;21 case 0: //timeout
22 break;23 default: //success
24 for(int i = 0; i < EVENTS_NUM; i++)25 {26 if(pfds[i].revents &POLLIN)27 {28 //read pfds[i].fd
29 }30 else if(pfds[i].revents &POLLOUT)31 {32 //write pfds[i].fd
33 }34 else if(pfds[i].revents &POLLERR)35 {36 //handle pfds[i].fd
37 }38 }39 break;40 }41 return 0;42 }
View Code
三、epoll:成功则返回已准备好的文件描述符个数,超时则返回0,出错则返回-1;
(1)头文件包含:#include ,epoll是一种当文件描述符的内核读缓冲区非空的时候发出可读signal通知进程;当内核写缓冲区不满的时候发出可写signal通知进程的机制,即事件驱动的机制;
(2)LT:水平触发(Level Triggered),如果对就绪的文件描述符fd不做操作,则内核会一直通知直到fd被处理为止;默认值,传统的select/poll也采用该机制;
(3)ET:边缘触发(Edge Triggerred),不管有没有对就绪的文件描述符fd做操作,内核之通知一次;
int epoll_create(int size);
int epoll_ctl(int efpd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
int close(int epfd);
1 #include
2 #include //eventfd,create a fd which is used to event-notify
3 #define EVENT_NUM 3
4
5 int main(int argc, char *argv[])6 {7 int epfd, ret = 0;8 struct epoll_event tmpEvt, occuredEvts[20];9
10 epfd = epoll_create(256);11 if(epfd < 0)12 {13 //error
14 }15 for(int i = 0; i < EVENT_NUM; i++)16 {17 tmpEvt.data.fd = eventfd(0, EFD_CLOEXEC |EFD_NONBLOCK);18 //tmpEvt.data.fd = i;
19 tmpEvt.events =EPOLLIN;20 ret = epoll_ctl(epfd, EPOLL_CTL_ADD, i, &tmpEvt);21 if(ret < 0)22 {23 //error
24 }25 }26 ret = epoll_wait(epfd, occuredEvts, EVENT_NUM, -1);27 switch(ret)28 {29 case -1: //error
30 break;31 case 0: //timeout
32 break;33 default: //success
34 for(int i = 0; i < ret; i++) //traverse ret!!!!!!
35 {36 if(occuredEvts[i].events &EPOLLIN)37 {38 //read occuredEvts[i].data.fd
39 }40 else if(occuredEvts[i].events &EPOLLOUT)41 {42 //write occuredEvts[i].data.fd
43 }44 else if(occuredEvts[i].events &EPOLLERR)45 {46 //handle occuredEvts[i].data.fd
47 }48 }49 break;50 }51
52 return 0;53 }
View Code
四、select/poll/epoll之间的比较
1 select:需要将用户传入的集合拷贝到内核空间,然后遍历每个fd对应的设备状态,如果设备就绪就更新设备的状态并继续遍历;如果遍历完没有发现就绪的设备则挂起当前进程,直到有设备就绪或者超时,进程被唤醒后又要遍历每个fd;最大连接数1024或2048;
2 poll:和select机制类似,唯一不同的就是poll没有最大连接数的限制,因为poll内部是基于链表(传入数组头指针+长度)来存储的;
3 epoll:内核监听到某个fd就绪时,就会采用callback函数的机制把该fd放入到内核的就绪队列中,这样epoll_wait返回的时候就只需要遍历该就绪队列即可,而无需遍历所有的fd;内核用红黑树来管理传入的epfd即多个fd;
(1)没有最大连接数的限制,1G的内存可以监听10万个fd;
(2)当fd就绪时它只会遍历已经就绪的fd,时间复杂度O(1),而不会像select/poll那样线性遍历所有的fd,所以效率得到了提升;
(3)采用内存映射(mmap)技术,减少了内存拷贝的开销;