(1)nfds是需要监视的最大的文件描述符值+1,因为文件描述符是从0开始计数的。
(2)rdset,wrset,exset分别对应于需要检测的可读文件描述符集合,可写文件描述符集合和异常文件描述符集合。
(3)timeout为结构timeval,用来设置select()的等待时间 timeout取值:
(1)NULL:select()没有timeout。select将一直被阻塞,直到某个文件描述符上发生了事件。
(2)0:只检测文件描述符的状态,然后立即返回。 (3)特定的时间值:如果在指定时间里没有事件发生,select将超时返回。
操作fd_set的接口:
FD_CLR(int fd, fd_set *set); 用来清除描述词组set中相关fd的位.
FD_ISSET(int fd,fd_set *set): 用来测试描述词组set中相关fd的位是否为真.
FD_SET(int fd,fd_set* set); 用来设置描述词组set中的相关fd位
FD_ZERO(fd_set *set); 用来清除描述词组set的全部位
返回值:
(1)执行成功则返回文件描述词状态已改变的个数
(2)如果返回0代表在描述符状态改变前已经超过timeout时间,没有返回。
(3)当有错误发生时返回-1.
当select函数返回的时候,它会将读,写,异常fd_set当中有我们关心事件但是没有发生事件的文件描述符置0,所以每一次select返回后,我们需要重新置我们需要关心事件的fd_set.
select缺点:
(1)每次调用select,都需要手动设置fd集合,从接口实用角度来说不方便
(2)每次调用select,都需要把fd集合从用户态拷贝到内核态,开销在fd很多时会很大
(3)每次调用select都需要在内核遍历传进来的所有fd (4)select支持文件描述符数量太小。
select优点:
(1)select资源占用比较少
(2)用户量较多的时候它的性能和效率比较好。
(1)fsd是一个poll函数监听的结构列表,每一个元素中,包含了三个部分:文件描述符,监听事件集合,返回事件集合。
这个结构列表是poll函数提高效率的一个关键,fd是文件描述符,fd上面需要关心的事件都存在events里面而revents就是用来存放实际发生了那些事件用来返回的。
与select()函数不同,调用select函数之后会清空它所检测的socket描述符集合,导致每次调用select()之前都必须把socket描述符重新加载到待检测的集合中,因此select适合检测一个socket描述符的情况,而poll函数适合大量socket描述符的情况。
(2)nfds表示fds数组的长度。 >0 timeout表示poll函数的超时时间,单位是毫秒。
-1 会造成poll永远等待。
=0 测试所有的描述符,并且poll立刻返回。
I/O多路转接之epoll
epoll三部曲:
(1)epoll_create创建一个epoll句柄
(2)epoll_ctl,将要监控的文件描述符进行注册
(3)epoll_wait等待文件描述符就绪
epoll_create函数:
自从linux2.6.8之后,size参数是被忽略的。
用完之后,必须调用close()关闭。
(1)第一个参数是epoll_creat()的返回值
(2)第二个参数表示动作,用三个宏来表示
EPOLL_CTL_ADD:注册新的fd到epfd中:
EPOLL_CTL_MOD;修改已经注册的fd的监听事件:
EPOLL_CTL_DEL:从epfd中删除一个fd;
(3)第三个参数是需要监听的fd
(4)第四个参数是告诉内核需要监听什么事
epoll_wait函数:
参数:
(1)epfd:由epoll_create生成的epoll专用的文件描述符;
(2)epoll_event:用于回传代处理事件的数组;
(3)maxevents:每次能处理的事件数;maxevents的值不能大于创建epoll_craete()时的size。
(4)timeout:等待I/O事件发生的超时值。
epoll在内核中底层原理:
优化机制:
(1)select和poll存储文件描述符或者关心的事件是使用数组存储,遍历事件的复杂度是O(n);
epoll底层使用红黑树来存储文件描述符以及关心的事件类型,事件复杂度为O(NlogN),极大增加的效率。
(2)回调机制。.操作系统以前需要主动轮询看文件描述符中的某个事件类型是否就绪,回调机制后,当某个节点事件就绪后操作系统就激活该节点,即就是将发生事件的文件描述符和事件类型依次拷贝到生产就绪队列当中。
(3)就绪队列。即内核当中的一个链表,存储的是在红黑树中被激活的节点。用户提取数据的时间复杂度为O(1).
epoll优点:
(1)文件描述符无上限:红黑树管理所有需要监控的文件描述符。
(2)基于事件的就绪通知方式:一旦被监听的某个文件描述符就绪内核采用回调机制迅速激活这个文件描述符,这样随着文件描述符的增加也不会影响判定就绪的性能。
(3)维护就绪队列:调用epoll_wait获取就绪文件描述符的时候,只取队列中的元素即可,操作时间复杂度为O(1).
epoll工作方式:
(1)水平触发模式(LT):
默认模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处改事件,下次调用epoll_wait,会再次响应应用程序并通知此事件。
支持阻塞读写和非阻塞读写
(2)边缘触发模式(ET):
高效工作模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。
只支持非阻塞读写
epoll的使用场景:
对于多连接且多连接中只有一部分连接比较活跃时,比较适合使用epoll。eg:典型的一个需要处理上万个客户端的服务器,例如各种互联网的APP的入口服务。
epoll中的惊群问题:
在多进程或者多线程环境下,有些人为了提高程序的稳定性,往往会让多个线程或者多个进程同时在epoll_wait监此听socket描述符。当一个新的链接请求进来时,操作系统不知道该选派哪个线程或者进程处理此事件,则干脆将其中的几个线程或者进程给唤醒,而实际上只有一个进程或者线程能够处理accept事件,其他线程都将失败,这种现象称为惊群效应。
简易的epoll服务器:
1 #include
2 #include
3 #include
4 #include
5 #include
6 #include
7 #include
8 #include
9 #include
10 #include
11 #include
12 #define SIZE 60
13 void usage(const char*argv)
14 {
15 printf("%s:[ip][port]\n",argv);
16 }
17 //创建套接字
18 int startup(char* ip,int port)
19 {
20 int sock = socket(AF_INET,SOCK_STREAM,0);
21 if(sock<0)
22 {
23 perror("sock");
24 exit(2);
25 }
26 int opt = 1;
27 setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
28
29 struct sockaddr_in local;
30 local.sin_family = AF_INET;
31 local.sin_port = htons(port);
32 local.sin_addr.s_addr = inet_addr(ip);
33
34 if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
35 {
36 perror("bind");
37 exit(3);
38 }
39 if(listen(sock,10)<0)
40 {
41 perror("listen");
42 exit(4);
43 }
44 return sock;
45 }
46 int main(int argc,char* argv[])
47 {
48 if(argc!=3)
49 {
50 usage(argv[0]);
51 exit(1);
52 }
53 int listen_sock = startup(argv[1],atoi(argv[2]));
54 int epoll = epoll_create(500);//创建epoll
57 ev.events = EPOLLIN;
58 ev.data.fd = listen_sock;
59
60 epoll_ctl(epoll,EPOLL_CTL_ADD,listen_sock,&ev);
61 struct epoll_event revs[SIZE];
62 int num = 0;
63 int i = 0;
64 while(1)
65 {
66 switch(num = epoll_wait(epoll,revs,SIZE,-1))
67 {
68 case -1:
69 {
70 perror("wait");
71 break;
72 }
73 case 0:
74 {
75 perror("time out");
76 break;
77 }
78 default:
79 {
80 struct sockaddr_in client;
81 socklen_t len = sizeof(client);
82
83 for(i = 0;i < num;i++)
84 {
85 int rsock = revs[i].data.fd;
86 if(rsock == listen_sock)
87 {
88 int new_sock = accept(listen_sock,(struct sockaddr*)&client,&len);
92 ev.events = EPOLLIN|EPOLLET;
93 ev.data.fd = new_sock;
94 epoll_ctl(epoll,EPOLL_CTL_ADD,new_sock,&ev);
95 }
96 else
97 {
98 if(revs[i].events&EPOLLIN)//读事件就绪
99 {
100 char buf[1024];
101 ssize_t s = read(rsock,buf,sizeof(buf)-1);
102 if(s>0)
103 {
104 buf[s]='\0';
105 printf("client ->#%s\n",buf);
106
107 ev.events = EPOLLOUT;
108 ev.data.fd = rsock;
109 epoll_ctl(epoll,EPOLL_CTL_MOD,rsock,&ev);
110 }
111 else if(s == 0)
112 {
113 printf("client :%d is quit\n",rsock);
114 epoll_ctl(epoll,EPOLL_CTL_DEL,rsock,NULL);
115 close(rsock);
116 }
117 else
118 {
119 perror("read");
120 }
121 }
122 else if(revs[i].events& EPOLLOUT)
123 {
124 const char* msg = "HTTP/1.0 200 OK\r\n\r\nshuowoailimengting
\r\n";
125 write(rsock,msg,strlen(msg));
126 epoll_ctl(epoll,EPOLL_CTL_DEL,rsock,NULL);
127 close(rsock);
128 }
129 else
130 {
131 }
132 }
133 }
134 break;
135 }
136 }
137 }
138 }
139