前言
select的原理以及使用
提示:以下是本篇文章正文内容,下面案例可供参考
一、select
select系统调用的用途是:在一段指定时间内,监听用户感兴趣的文件描述符的可读、可写和异常等事件。
二、API接口:
1.int select(int maxfd, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);//select 成功时返回就绪(可读、可写和异常)文件描述符的总数。如果在超时时间内没有任何文件描述符就绪,select 将返回 0。select 失败是返回-1.如果在select 等待期间,程序接收到信号,则 select 立即返回-1,并设置 errno 为 EINTR。**maxfd 参数指定的被监听的文件描述符的总数。**它通常被设置为 select 监听的所有文件描述符中的最大值+1,readfds、writefds 和 exceptfds 参数分别指向可读、可写和异常等事件对应的文件描述符集合。应用程序调用 select 函数时,通过这 3 个参数传入自己感兴趣的文件描述符。select 返回时,内核将修改它们来通知应用程序哪些文件描述符已经就绪。
2.void FD_ZERO(fd_set *fdset); // 清除 fdset 的所有位
3.void FD_SET(int fd, fd_set *fdset); // 设置 fdset 的位 fd
4.void FD_CLR(int fd, fd_set *fdset); // 清除 fdset 的位 fd
5. int FD_ISSET(int fd, fd_set *fdset);// 测试 fdset 的位 fd 是否被设置
三、使用步骤
1.服务器端
代码如下(示例):
//ser.c
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<unistd.h>
#include<sys/time.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#define MAX 10
fd_set fdset;//创建select集合
void Init_fds(int*fds)//描述符数组初始化
{
assert(fds!=NULL);
for(int i=0;i<MAX;i++)
{
fds[i]=-1;
}
}
void Insert_fds(int*fds,int x)//添加描述符
{
assert(fds!=NULL);
for(int i=0;i<MAX;i++)
{
if(fds[i]==-1)
{
fds[i]=x;
break;
}
}
}
void Del_fds(int*fds,int x)//删除描述符
{
assert(fds!=NULL);
for(int i=0;i<MAX;i++)
{
if(fds[i]==x)
{
fds[i]=-1;
break;
}
}
}
int main()
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);
assert(sockfd!=-1);
struct sockaddr_in saddr,caddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family=AF_INET;
saddr.sin_port=htons(6000);
saddr.sin_addr.s_addr=inet_addr("127.0.0.1");
int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
assert(res!=-1);
res=listen(sockfd,5);
assert(res!=-1);
int fds[MAX]={};//创建描述符集合
Init_fds(fds);//初始化集合
Insert_fds(fds,sockfd);//先把监听套接字sockfd加入集合
fd_set fdset;//创建select集合
while(1)
{
FD_ZERO(&fdset);//初始化select集合
int max=-1;//记录文件描述符最大值
for(int i=0;i<MAX;i++)
{
if(fds[i]!=-1)
{
FD_SET(fds[i],&fdset);//将文件描述符添加到select集合中
if(fds[i]>max)
{
max=fds[i];//max保存文件描述符的最大值
}
}
}
struct timeval tv={5,0};//设置超时时间为5s
int n=select(max+1,&fdset,NULL,NULL,&tv);//等待读事件发生
if(n==0)//超时
{
printf("time out\n");
continue;
}
if(n==-1)//发生错误
{
printf("slect err\n");
continue;
}
else//n为返回值,代表有n个文件描述符上有读事件就绪
{
for(int i=0;i<MAX;i++)
{
if(fds[i]==-1)//文件描述符数组中无效位置不用处理
{
continue;
}
if(FD_ISSET(fds[i],&fdset))//检测是否是该描述符上有读事件
{
if(fds[i]==sockfd)//如果该描述符是sockfd,调用accept()
{
int len=sizeof(caddr);
int c=accept(sockfd,(struct sockaddr*)&caddr,&len);
if(c<0)
{
continue;
}
printf("accept(%d)\n",c);
Insert_fds(fds,c);//接收到的c插入到文件描述符集合中
}
else//如果不是sockfd,就是c上有数据需要接收
{
char buff[128]={0};
int num=recv(fds[i],buff,127,0);//调用accept
if(num<=0)
{
close(fds[i]);
printf("%d is offline\n",fds[i]);
Del_fds(fds,fds[i]);
continue;
}
printf("recv(%d): %s\n",num,buff);
send(fds[i],"ok",2,0);
}
}
}
}
}
}
2.客户端
代码如下(示例):
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<pthread.h>
int main()
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);
assert(sockfd!=-1);
struct sockaddr_in saddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family=AF_INET;
saddr.sin_port=htons(6000);
saddr.sin_addr.s_addr=inet_addr("127.0.0.1");
int res=connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
assert(res!=-1);
char buff[128]={0};
printf("input: \n");
while(1)
{
fgets(buff,128,stdin);
if(strncmp(buff,"end",3)==0)
{
break;
}
send(sockfd,buff,strlen(buff),0);
memset(&buff,0,sizeof(buff));
recv(sockfd,buff,127,0);
printf("buff=%s\n",buff);
}
close(sockfd);
exit(0);
}
总结
1.select需要手动的添加或删除文件描述符。
2.select集合的大小是1024,因为是用每一个bit位表示一个描述符。
3.当服务器设置每次只收一个字节的数据时,当接收缓冲区的数据没有被收完时,该文件描述符上会一直有读事件就绪,不会造成数据丢失。