作用:
select函数是用来监视一个或多个文件句柄的状态变化的,可阻塞也可不阻塞。
函数原型:
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
函数参数意义:
fd_max:传入的监视文件描述符集合中最大的文件描述符数值 + 1,因为select是从0开始一直遍历到数值最大的标识符。
readfds:文件描述符集合,检查该组文件描述符的可读性。
writefds:文件描述符集合,检查该组文件描述符的可写性。
exceptfds:文件描述符集合,检查该组文件描述符的异常条件。
timeout: 时间结构体
struct timeval {
time_t tv_sec; /* 秒 */
time_t tv_usec; /* 微秒 */
};
所需头文件:
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
timeout的值为NULL,则将select()函数置为阻塞状态,当监视的文件描述符集合中的某一个描述符发生变化才会返回结果并向下执行。
timeout的值等于0,则将select()函数置为非阻塞状态,执行select()后立即返回,无论文件描述符是否发生变化。
timeout的值大于0,则将select()函数的超时时间设为这个值,在超时时间内阻塞,超时后返回结果。
文件描述符集合设置:
FD_ZERO(fd_set *fd); /* 清空该组文件描述符集合 */
FD_CLR(inr fd,fd_set *fd); /* 清除该组文件描述符集合中的指定文件描述符 */
FD_ISSET(int fd,fd_set *fd); /* 测试指定的文件描述符是否在该文件描述符集合中 */
FD_SET(int fd,fd_set *fd); /* 向该文件描述符集合中添加文件描述符 */
返回值:
- -1:发生错误,并将所有描述符集合清0,可通过errno输出错误详情。
- 0:超时。
- 正数:发生变化的文件描述符数量。
注意:
每次调用完select()函数后需要将文件描述符集合清空并重新设置(也可以利用另外一个变量保存原来的集合),也就是设置的文件描述符集合是一次性使用的。原因是调用完select()后文件描述符集合可能发生改变。
优点:跨平台。
缺点:
1.每次调用 select(),都需要把 fd 集合从用户态拷贝到内核态,这个开销在 fd 很多时会很大,同时每次调用 select() 都需要在内核遍历传递进来的所有 fd,这个开销在 fd 很多时也很大。
2.单个进程能够监视的文件描述符的数量存在最大限制,在 Linux 上一般为 1024,可以通过修改宏定义甚至重新编译内核的方式提升这一限制,但是这样也会造成效率的降低
例子:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <sys/select.h>
#define SERV_PORT 8989
int main(int argc, const char* argv[])
{
int lfd, cfd;
struct sockaddr_in serv_addr, clien_addr;
int serv_len, clien_len;
// 创建套接字
lfd = socket(AF_INET, SOCK_STREAM, 0);
// 初始化服务器 sockaddr_in
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET; // 地址族
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听本机所有的IP
serv_addr.sin_port = htons(SERV_PORT); // 设置端口
serv_len = sizeof(serv_addr);
// 绑定IP和端口
bind(lfd, (struct sockaddr*)&serv_addr, serv_len);
// 设置同时监听的最大个数
listen(lfd, 36);
printf("Start accept ......\n");
int ret;
int maxfd = lfd;
// reads 实时更新,temps 内核检测
fd_set reads, temps;
FD_ZERO(&reads);
FD_SET(lfd, &reads);
while(1)
{
temps = reads;
ret = select(maxfd+1, &temps, NULL, NULL, NULL);
if(ret == -1)
{
perror("select error");
exit(1);
}
// 判断是否有新连接
if(FD_ISSET(lfd, &temps))
{
// 接受连接请求
clien_len = sizeof(clien_len);
int cfd = accept(lfd, (struct sockaddr*)&clien_addr, &clien_len);
// 文件描述符放入检测集合
FD_SET(cfd, &reads);
// 更新最大文件描述符
maxfd = maxfd < cfd ? cfd : maxfd;
}
// 遍历检测的文件描述符是否有读操作
for(int i=lfd+1; i<=maxfd; ++i)
{
if(FD_ISSET(i, &temps))
{
// 读数据
char buf[1024] = {0};
int len = read(i, buf, sizeof(buf));
if(len == -1)
{
perror("read error");
exit(1);
}
else if(len == 0)
{
// 对方关闭了连接
FD_CLR(i, &reads);//将该文件描述符去掉
close(i);
if(maxfd == i)
{
maxfd--;
}
}
else
{
printf("read buf = %s\n", buf);
for(int j=0; j<len; ++j)
{
buf[j] = toupper(buf[j]);
}
printf("--buf toupper: %s\n", buf);
write(i, buf, strlen(buf)+1);
}
}
}
}
close(lfd);
return 0;
}