一、select函数说明
原理: 借助内核, select 来监听客户端连接、数据通信事件。
函数原型:
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
其中 fd_set 实际上是一个位图,每一个特定二进制位标志相应文件描述符
nfds:监听的所有文件描述符中,最大文件描述符+1
readfds: 读 文件描述符监听集合。 传入、传出参数
writefds:写 文件描述符监听集合。 传入、传出参数
exceptfds:异常 文件描述符监听集合 传入、传出参数
timeout: > 0: 设置监听超时时长。
NULL: 阻塞监听
0: 非阻塞监听,轮询
返回值:
> 0: 所有监听集合(3个)中, 满足对应事件的总数。
0: 没有满足监听条件的文件描述符
-1: 出错
fd_set参数的相关辅助函数:
void FD_ZERO(fd_set *set); --- 清空一个文件描述符集合。
void FD_SET(int fd, fd_set *set); --- 将待监听的文件描述符,添加到监听集合中
void FD_CLR(int fd, fd_set *set); --- 将一个文件描述符从监听集合中 移除。
int FD_ISSET(int fd, fd_set *set); --- 判断一个文件描述符是否在监听集合中。
FD_ISSET返回值:
1)文件描述符在监听集合中:返回1;
2)文件描述符不在监听集合中:返回0;
二、select特点
它仅仅知道了,有I/O事件发生了,却并不知道是哪那几个流(可能有一个,多个,甚至全部),我们只能无差别轮询所有流,找出能读出数据,或者写入数据的流,对他们进行操作。所以select具有O(n)的无差别轮询复杂度,同时处理的流越多,无差别轮询时间就越长。
优点
跨平台。win、linux、macOS、Unix、类Unix、mips等操作系统均可使用。
缺点
select本质上是通过设置或者检查存放fd标志位的数据结构来进行下一步处理。这样所带来的缺点是:
1、 单个进程可监视的fd数量被限制,即能监听端口的大小有限。
一般来说这个数目和系统内存关系很大,具体数目可以cat /proc/sys/fs/file-max察看。32位机默认是1024个。64位机默认是2048.
2、 对socket进行扫描时是线性扫描,即采用轮询的方法,效率较低:
当套接字比较多的时候,每次select()都要通过遍历FD_SETSIZE个Socket来完成调度,不管哪个Socket是活跃的,都遍历一遍。这会浪费很多CPU时间。如果能给套接字注册某个回调函数,当他们活跃时,自动完成相关操作,那就避免了轮询,这正是epoll与kqueue做的。
3、需要维护一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大 参考链接 select、poll、epoll之间的区别(搜狗面试)
三、demo
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <sys/select.h>
#include "wrap.h"
#define SERV_PORT 6666
int main(int argc
, char *argv
[])
{
int i
, j
, n
, maxi
;
int nready
, client
[FD_SETSIZE
];
int maxfd
, listenfd
, connfd
, sockfd
;
char buf
[BUFSIZ
], str
[INET_ADDRSTRLEN
];
struct sockaddr_in clie_addr
, serv_addr
;
socklen_t clie_addr_len
;
fd_set rset
, allset
;
listenfd
= Socket(AF_INET
, SOCK_STREAM
, 0);
int opt
= 1;
setsockopt(listenfd
, SOL_SOCKET
, SO_REUSEADDR
, &opt
, sizeof(opt
));
bzero(&serv_addr
, sizeof(serv_addr
));
serv_addr
.sin_family
= AF_INET
;
serv_addr
.sin_addr
.s_addr
= htonl(INADDR_ANY
);
serv_addr
.sin_port
= htons(SERV_PORT
);
Bind(listenfd
, (struct sockaddr
*)&serv_addr
, sizeof(serv_addr
));
Listen(listenfd
, 128);
maxfd
= listenfd
;
maxi
= -1;
for (i
= 0; i
< FD_SETSIZE
; i
++)
client
[i
] = -1;
FD_ZERO(&allset
);
FD_SET(listenfd
, &allset
);
while (1) {
rset
= allset
;
nready
= select(maxfd
+1, &rset
, NULL, NULL, NULL);
if (nready
< 0)
perr_exit("select error");
if (FD_ISSET(listenfd
, &rset
)) {
clie_addr_len
= sizeof(clie_addr
);
connfd
= Accept(listenfd
, (struct sockaddr
*)&clie_addr
, &clie_addr_len
);
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET
, &clie_addr
.sin_addr
, str
, sizeof(str
)),
ntohs(clie_addr
.sin_port
));
for (i
= 0; i
< FD_SETSIZE
; i
++)
if (client
[i
] < 0) {
client
[i
] = connfd
;
break;
}
if (i
== FD_SETSIZE
) {
fputs("too many clients\n", stderr);
exit(1);
}
FD_SET(connfd
, &allset
);
if (connfd
> maxfd
)
maxfd
= connfd
;
if (i
> maxi
)
maxi
= i
;
if (--nready
== 0)
continue;
}
for (i
= 0; i
<= maxi
; i
++) {
if ((sockfd
= client
[i
]) < 0)
continue;
if (FD_ISSET(sockfd
, &rset
)) {
if ((n
= Read(sockfd
, buf
, sizeof(buf
))) == 0) {
Close(sockfd
);
FD_CLR(sockfd
, &allset
);
client
[i
] = -1;
} else if (n
> 0) {
for (j
= 0; j
< n
; j
++)
buf
[j
] = toupper(buf
[j
]);
Write(sockfd
, buf
, n
);
Write(STDOUT_FILENO
, buf
, n
);
}
if (--nready
== 0)
break;
}
}
}
Close(listenfd
);
return 0;
}