select系统调用的用途是:在一段时间内,监听当用户感兴趣的文件描述符上的可读,可写,可异常等事件。
《一》select系统调用原型:
Int select(int nfds,fd_set* readfds,fd_set* writefds,fd_set* exceptfds,struct timeval* time out);
成功将返回就绪文件描述符的个数,失败返回-1
- nfds:监听的最大文件描述符的值+1(原因:它是轮询检测的)
- readfds,writefds,exceptfds分别是可读,可写,异常事件,(select调用返回时,内核修改他们来通知应用程序哪些文件描述符已经就绪)
fd_set是一个整型数组,该数组的每一位都标记一个文件描述符,
fd_set能容纳的文件描述符数量由FD_SETSIZE指定1024
3 .timeout:超时时间 以微秒记
select成功时返回就绪(可读,可写和异常)文件描述符的总数,如果在超时时间内没有任何文件描述符就绪,select返回0.
《二》select的原理图如下:
《三》select调用的代码:
客户端:
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>
void main()
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);
assert(sockfd!=-1);
struct sockaddr_in ser,cli;
ser.sin_family=AF_INET;
ser.sin_port=htons(6000);
ser.sin_addr.s_addr=inet_addr("127.0.0.1");
int n=connect(sockfd,(struct sockaddr*)&ser,sizeof(ser));
assert(n!=-1);
while(1)
{
char buff[128]={0};
printf("input:\n");
fgets(buff,128,stdin);
if(strncmp(buff,"end",3)==0)
{
close(n);
break;
}
send(sockfd,buff,strlen(buff),0);
memset(buff,0,128);
recv(sockfd,buff,127,0);
printf("%s\n",buff);
}
close(sockfd);
}
服务器:
#include<stdio.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<assert.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<fcntl.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<errno.h>
#include<sys/select.h>
void main()
{
int listenfd=socket(AF_INET,SOCK_STREAM,0);
assert(listenfd!=-1);
struct sockaddr_in ser,cli;
ser.sin_family=AF_INET;
ser.sin_port=htons(6000);
ser.sin_addr.s_addr=inet_addr("127.0.0.1");
int res=bind(listenfd,(struct sockaddr*)&ser,sizeof(ser));
assert(res!=-1);
listen(listenfd,5);
fd_set reads;
int fds[10];
int p=0;
for(;p<10;p++)
{
fds[p]=-1;
}
fds[0]=listenfd;
while(1)
{
FD_ZERO(&reads);
int maxfd=-1;
int i=0;
for(;i<10;++i)
{
if(fds[i]!=-1)
{
FD_SET(fds[i],&reads);
if(fds[i]>maxfd)
{
maxfd=fds[i];
}
}
}
int n=select(maxfd+1,&reads,NULL,NULL,NULL);
printf("select back\n");
if(n==-1)
{
printf("select error\n");
break;
}
else
{
for(i=0;i<10;i++)
{
if(fds[i]!=-1 && FD_ISSET(fds[i],&reads))
{
if(fds[i]==listenfd)
{
int len=sizeof(cli);
int c=accept(listenfd,(struct sockaddr*)&cli,&len);
printf("one cli link seccess\n");
int j=0;
for(;j<10;++j)
{
if(fds[j]==-1)
{
fds[j]=c;
break;
}
}
}
else
{
char buff[128]={0};
int num=recv(fds[i],buff,127,0);
if(num<=0)
{
printf("one client break link\n");
close(fds[i]);
fds[i]=-1;
continue;
}
printf("%d: %s\n",fds[i],buff);
send(fds[i],"ok",2,0);
}
}
}
}
}
}
《四》Select的优缺点:
- 关注的 文件描述符的值的大小和文件描述符的个数受限制,值最大是1023,文件描述符最多有1024个,受宏FD_SETSIZE限制
- 会有两次拷贝
- Fd_set仅仅是一个文件描述符的集合,并没有将文件描述符和事件绑定,需要传入3中事件,所以Select不能处理更多类型的事件
- 由于内核对fd_set集合的在线修改,导致应用程序下次调用select前必须要重置fd_set集合
- Select调用都返回整个的事件集合(其中就包括就绪和未就绪的)采用轮询的方式来检测就绪文件描述符,所以索引就绪的文件描述符的时间复杂度为O(n)
- Select只能工作在相对低效的LT模式下