I/O多路复用:单个线程可以同时处理多个I/O;
一、select:成功则返回已准备好的文件描述符个数,超时则返回0,出错则返回-1;
(1)头文件包含:#include <sys/select.h>、<unistd.h>、<sys/time.h>;
(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 <sys/select.h> 2 #include <unistd.h> 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 }
二、poll:成功则返回已准备好的文件描述符个数,超时则返回0,出错则返回-1;
(1)头文件包含:#include <poll.h>
(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 <poll.h> 2 #define EVENTS_NUM 3 3 4 int main(int argc, char *argv[]) 5 { 6 struct pollfd 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 }
三、epoll:成功则返回已准备好的文件描述符个数,超时则返回0,出错则返回-1;
(1)头文件包含:#include <sys/epoll.h>,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 <sys/epoll.h> 2 #include <sys/eventfd.h> //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 }
四、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)技术,减少了内存拷贝的开销;