select介绍
我们先来看一下select的接口。
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
1
2
3
4
5
1
2
3
4
5
从上面的这些接口我们应该能有写认识,首先我么来看select系统调用的参数的含义。
参数 功能
nfds 被监听的文件描述符的总数。通常是文件描述符最大值加1
readfds 可读事件的文件描述符集合
writefds 可写事件的文件描述符集合
exceptfds 异常事件的文件描述符集合
timeout 设定超时时间
值得注意的是,后面的三个参数即是输入型参数,又是输出型参数,输入表示关心那些文件描述符对应的特定事件发生。输出表示的是那些文件描述符对应的事件就绪。当就绪后,内核将会去修改这些文件描述符的集合。这三个参数都是fd_set结构体类型
typedef struct
{
/*XPG4.2requiresthismembername.Otherwiseavoidthename
fromtheglobalnamespace.*/
#ifdef__USE_XOPEN
__fd_maskfds_bits[__FD_SETSIZE/__NFDBITS];
#define__FDS_BITS(set)((set)->fds_bits)
#else
__fd_mask__fds_bits[__FD_SETSIZE/__NFDBITS];
#define__FDS_BITS(set)((set)->__fds_bits)
#endif
}fd_set;
1
2
3
4
5
6
7
8
9
10
11
12
13
1
2
3
4
5
6
7
8
9
10
11
12
13
可以看出fd_set就是一个结构体数组,这个结构体数组的每一位就是一个文件描述符的标记,配套提供了一些对于fd_set操作的宏。
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
1
2
3
4
1
2
3
4
宏 功能
FD_CLR 进行对应位fd
FD_ISSET 进行判断对应位fd
FD_SET 设置fd的对应位置
FD_ZERO 进行清空fd_set
最后要说一下的就是timeout参数,这个参数用来设置超时时间,它也是一个结构体,它用来告诉应用程序select等待多久,这里的单位是微秒级别的。
timeout参数 说明
0 立即返回,即轮询
NULL 阻塞监视文件描述符,当有时间就绪才返回
大于0的时间 超时时间设置
select调用时内核级别的,select的轮询方式是和非阻塞轮询方式是不同的,select的轮询方式是同时可以对多个I/O端口进行监听,任何一个端口数据好了,这个时候就可以读了。然后通过系统调用,就可以把数据从内核拷贝到用户进程。
![enter description here][1]
select缺点
1、 单个进程可监视的fd数量被限制,即能监听端口的大小有限。
一般来说这个数目和系统内存关系很大,具体数目可以cat /proc/sys/fs/file-max查看,有宏FD_SETSIZE进行限制fd的数量。32位机默认是1024个。64位机默认是2048.
2、 对socket进行扫描时是线性扫描,即采用轮询的方法,效率较低:
当套接字比较多的时候,每次select()都要通过遍历FD_SETSIZE个Socket来完成调度,不管哪个Socket是活跃的,都遍历一遍。这会浪费很多CPU时间。如果能给套接字注册某个回调函数,当他们活跃时,自动完成相关操作,那就避免了轮询,这正是epoll与kqueue做的。
3、需要维护一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大
select代码示例:
6 #include<sys/socket.h>
7 #include<sys/types.h>
8 #include<netinet/in.h>
9
10 void usage(const char* str)
11 {
12 printf("usage:%[local_IP] [localP_port]\n",str);
13 }
14
15
16 int startup(char* IP, int port)
17 {
18 int sock=socket(AF_INET,SOCK_STREAM,0);
19 if(sock<0){
20 perror("sock");
21 exit(2);
22 }
23
24 struct sockaddr_in ser_addr;
25 ser_addr.sin_family=AF_INET;
26 ser_addr.sin_port=htons(port);
27 ser_addr.sin_addr.s_addr=inet_addr(IP);
28 if(bind(sock,(struct sockaddr*)&ser_addr,sizeof(ser_addr))<0){
29 perror("bind");
30 exit(3);
31 }
32
33 if(ret<0){
34 perror("listen");
35 exit(4);
36 }
37 return sock;
38 }
39 int array_fds[1024];
40 int maxfd;
41
42
43 int main(int argc, char* argv[])
44 {
45 if(argc!=3){
46 usage(argv[0]);
47 exit(0);
48 }
49
50 int listen_sock=startup(argv[1],atoi(argv[2]));
51 int i=0;
52 for(;i<1024;i++){
53 array_fds[i]=-1;
54 }
55
56 array_fds[0]=listen_sock;
57 fd_set reads;
58 // fd_set writes;
59 struct timeval timeout;
60 while(1){
61 FD_ZERO(&reads);
-- INSERT -- 33,1 28%
60 while(1){
61 FD_ZERO(&reads);
62 // FD_ZERO(&writes);
63 int maxfd=0;
64 timeout.tv_sec=10;
65 timeout.tv_usec=0;
66
67 for(i=0;i<1024;i++){
68 // if(array_fds[i]>0){
69 // FD_SET(array_fds[i],&reads);
70 // FD_SET(array_fds[i],&writes);
71 if(array_fds[i]==-1){
72 continue;
73 }
74 FD_SET(array_fds[i],&reads);
75 if(array_fds[i]>maxfd){
76 maxfd=array_fds[i];
77 }
78 }//FD
79 }//for
80 int j=0;
81 switch(select(maxfd+1,&reads,/*&writes*/NULL,NULL,&timeout)){
82 case 0:
83 printf("time quit.....\n");
84 break;
85 case -1:
86
86 perror("select");
87 break;
88 default:{
89 for(j=0;j<1024;j++){
90 if(array_fds[j]<0){
91 continue;
92 }
93 if(j==0&&FD_ISSET(array_fds[0],&reads)){
94 struct sockaddr_in client;
95 socklen_t len=sizeof(client);
96 int new_sock=accept(array_fds[0],(struct sockaddr *)&client,&len);
97 if(new_sock<0){
98 perror("accept");
99 continue;
100 }
101
102 printf("get a new client :(%s:%d)\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
103 int k=1;
104 for(;k<1024;k++){
105 if(array_fds[k]<0)
106
107 break;
108 }
109
110 if(j==1024){
111 printf("client full\n");
112 close(new_sock);
113 }
114
114 }else{
115 if(j!=0&&FD_ISSET(array_fds[j],&reads)){116 printf("***read start***");
117 char buf[1024];
118 ssize_t s=read(array_fds[j],buf,sizeof(buf)-1);
119 if(s<0){
120 perror("read");
121 close(array_fds[j]);
122 array_fds[j]=-1;
123
124 }else if(s==0){
125 printf("client quit...\n");
126 close(array_fds[j]);
127 array_fds[j]=-1;
128
129 }else {
130 buf[s]=0;
131 printf("client # %s\n",buf);
132 }
133 }else{
134
135 }
136 }
137 }
138 break;
139 }//while
140 }
141 return 0;
142 }
select优点:
1.一次可以等待多个文件描述符,减少了平均等待时间
2.客户越来越多时,减轻了进程调度的压力(相较于多进程多线程服务器)
select缺点:
1.能监听的文件描述符有上限,这个上限是由fd_set决定的。
2.它返回的只是就绪事件的个数,要判断是那个事件满足,需要遍历文件描述符。
3.select监听的集合是输入输出参数,每次监听都需要重新初始化。
4.每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
5.内核采用轮询(遍历fd集合)的方式来检测就绪事件,这个开销在fd很多时也很大
6.select和poll都只能工作在低效的LT(水平触发)模式