函数功能:在一段时间内,监听用户感兴趣的文件描述符上的可读、可写与异常事件
函数原型:int select(int nfds,struct fd_set *readfds, struct fd_set *writefds,struct fd_set *execptfds,struct timeval * timeout)
nfds:要监听的最大文件描述符加1; readfds:监听用户感兴趣的文件描述符上的可读事件
writefds:监听用户感兴趣的文件描述符上的可写事件 execptfds:监听用户感兴趣的文件描述符上的异常事件
timeout:select函数的超时时间
fd_set结构体只包含一个整型数组,并且数组大小为32,用数组中每个元素的每个位对应一个文件描述符;
内核中关于此结构体的定义:
#define FD_SETSIZE 1024
#include <sys/select.h>
#define FD_SETSIZE _FD_SETSIZE
typedef long int _fd_maksk;
#undef _NFDBITS
#define _NFDBITS (8 *(int)sizeof(_fd_mask)) //32
typedef struct
{
#ifdef _USE_XOPEN 1024 32
_fd_maksk _fds_bits[_FD_SETSIZE /_NFDBITS ]
#define _FDBITS(set) ((set)->fds_bits)
#else
_fd_maksk _fds_bits[_FD_SETSIZE /_NFDBITS ]
#define _FDBITS(set) ((set)->fds_bits)
#endif
}fd_set;
由于fd_set表示的特性,表示最多可监听文件描述符的个数为1024个,最大文件描述符值为1023,因为从0开始
并且select提供了设置位的宏函数访问fd_set
FD_ZERO(fd_set* fdset) 清空fdset的所有位
FD_SET(int fd,fd_set* fdset) 设置fdset的fd位
FD_CLR(int fd,fd_set* fdset) 清除fdset的fd位
int FD_ISSET(int fd,fd_set* fdset) 测试fdset的位fd是否被设置
比如:
FD_SET(5,&redset);则readset的第五位被置为1
FD_SET(32,&redset);则readset的第32位被置为1
函数功能实现过程
(1)首先select函数的三个fd_set类型参数会将自己感兴趣的文件描述符注册到对应的结构中
(2)调用select系统调用,这个三个结构被传给内核程序,内核程序轮询查找三个结构体中的文件描述符是否有就绪事件,实践复杂度为O(n);并且直接修改这3个结构上的内容来通知应用程序哪些文件描述符已经就绪,最后内核程序将这三个结构返回给应用程序
(3)应用程序拿到返回的结构之后采用轮询法遍历这三个结构,找到就绪的文件描述符,此查找过程时间复杂度为O(n);
select函数的缺点
1.从函数原型看,只能监听可读、可写、异常三种事件;
2.内核程序和应用程序都需要才有轮询法查找就绪文件描述符,时间复杂度为O(n);
3.内核程序是直接修改传入的结构体中的内容,所以下一次调用时又必须重新设置结构体;
4.从内核定义来看,单个进程所打开的FD是有一定限制的,它由FD_SETSIZE设置,默认值是1024;
具体数目可以cat /proc/sys/fs/file-max察看。32位机默认是1024个。64位机默认是2048. 所以在不同位的系统下,能打开的文件描述符个数也不相同;
5.只支持LT模式
select函数简单应用代码:
void addfds(int* fds,int c)
{
int i = 0;
for(; i < 128;++i)
{
if(fds[i] == -1)
{
fds[i] = c;
break;
}
}
}
void delfds(int*fds,int c)
{
int i = 0;
for(; i < 128 ;++i)
{
if(fds[i] == c)
{
fds[i] = -1;
break;
}
}
}
void main()
{
int listen_fd = socket(AF_INET,SOCK_STREAM,0);//创建监听套接字
assert(listen_fd != -1);
struct sockaddr_in ser,cli;//在绑定函数中需要的结构体,用来记录客户端的iip地址和端口号
ser.sin_family = PF_INET;//地址族:TCP/IP
ser.sin_port = htons(6000);//将客户端端口号转化为网络字节序
ser.sin_addr.s_addr = inet_addr("127.0.0.1");//将客户端的ip地址转化为网络字节序;注意这里输入的ip地址是链接本机;
int ret = bind(listen_fd,(struct sockaddr*)&ser,sizeof(ser));//绑定监听套接字
assert(ret != -1);
listen(listen_fd,5);//监听
printf("listen finish\n");
//设置select中read的结构;
int maxfd = 0;
fd_set read;
//用一个数组来记录所有关注的文件描述符,此连接分为监听套接字和已完成连接的套接字
int fds[128];
//初始化数组
int i = 0;
for(;i < 128;++i)
{
fds[i] = -1;
}
fds[0] = listen_fd;
while(1)
{
FD_ZERO(&read);
int i = 0;
//找出select第一个参数的值
for(; i < 128;++i)
{
if(fds[i] != -1)
{
if(fds[i] > maxfd)
{
maxfd = fds[i];
}
FD_SET(fds[i],&read);
}
}
//通过select数组中那些链接有可读事件
int n = select(maxfd+1,&read,NULL,NULL,NULL);
if( n == 0)//连接超时
{
printf("time out \n");
}
if(n < 0)//连接出错
{
exit(0);
}
for(i = 0;i < 128;++i)
{
if(fds[i] != -1)
{
if(FD_ISSET(fds[i],&read))//判断链接是否就绪
{
if(fds[i] == listen_fd)//如果是监听连接,则进行accept获得客户链接,并将客户链接添加到数组中
{
int len = sizeof(cli);
int c = accept(listen_fd,(struct sockaddr*)&cli,&len);
assert(c != -1);
addfds(fds,c);
}
else
{
char buff[128] = {0};
int n = recv(fds[i],buff,128,0);
if(n <= 0)
{
close(fds[i]);
delfds(fds,fds[i]);
}
printf("buff::%s\n",buff);
send(fds[i],"0k",2,0);
break;
}
}
}
}
}
}