目录
一.select方法介绍
2.1 select 系统调用的原型
2.2 集合的数据结构
2.2.1 fd_set 结构如下:
2.2.2 关于集合fd_set的解析
2.3 select第一个参数
2.4 select方法之超时时间timeout
2.5 select方法的用法简述及返回值
2.6 如何检测集合中有哪些描述符有事件就绪
三.select应用
3.1 select小实例
3.2 结合tcp编程
一.select方法介绍
select 系统调用的用途是:在一段指定时间内,监听用户感兴趣的文件描述符的可读、可写和异常等事件。
2.1 select 系统调用的原型
select 成功时返回就绪(可读、可写和异常)文件描述符的总数。如果在超时时间内没有任何文件描述符就绪,select将返回 0。select 失败是返回-1.如果在 select 等待期间,程序接收到信号,则select 立即返回-1,并设置errno 为EINTR。
#include <sys/select.h>
int select(int maxfd, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
- maxfd 参数指定的被监听的文件描述符的总数。它通常被设置为 select 监听的所有文件描述符中的最大值+1。
- readfds、 writefds 和 exceptfds 参数分别指向可读、可写和异常等事件对应的文件描述符集合。应用程序调用 select 函数时,通过这 3 个参数传入自己感兴趣的文件描述符。
- select 返回时,内核将修改它们来通知应用程序哪些文件描述符已经就绪
2.2 集合的数据结构
select如何通知应用程序有哪些描述符数据就绪了,用户又是如何将这些描述符添加到select这个集合中?具体看下面关于集合fd_set的解析;
2.2.1 fd_set 结构如下:
#define __FD_SETSIZE 1024
typedef long int fd_mask;
#define __NFDBITS(8*(int)sizeof(__fd_mask))typedef struct
{#ifdef __USE_XOPEN__fd_mask fds_bits[__FD_SETSIZE/__NFDBITS];//这是一个数组,依次是数组类型 数组名[数组个数]# define __FDS_BITS(set)((set)->fds_bits)#else__fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];# define __FDS_BITS(set)((set)->__fds_bits)#endif
}fd_set;
2.2.2 关于集合fd_set的解析
select如何通知应用程序有哪些描述符数据就绪了,用户又是如何将这些描述符添加到select这个集合中?我们首先来看关于集合fd_set的解析 :
也就是说这个结构体fd_set实际定义了1024个位。
通过下列宏可以访问 fd_set 结构中的位:
FD_ZERO(fd_set *fdset);// 清除 fdset 的所有位,就是将所有的位都置为0;FD_SET(int fd,fd_set *fdset);// 设置 fdset 的位 fd,就是将某个描述符对应的位设置为1,就是将某个描述符添加到这个集合中;FD_CLR(int fd,fd_set *fdset);// 清除 fdset 的位 fd,就是将某个位清零;int FD_ISSET(int fd,fd_set *fdset);// 测试 fdset 的位 fd 是否被设置,其实就是测试某个描述符对应的位是不是1,如果被设置为1返回值为真,否则返回值为假;
2.3 select第一个参数
select第一个参数通常被设置为描述符的最大值+1
也就是说书上maxfd是描述符的总数目,其实应该理解为描述符的最大值+1;也就是需要关注的位的总数目,比如上面的例子是8;
2.4 select方法之超时时间timeout
timeout 参数用来设置 select 函数的超时时间。它是一个 timeval 结构类型的指针,采用指针参数是因为内核将修改它以告诉应用程序 select 等待了多久。
timeval结构的定义如下:
struct timeval
{long tv_sec;//秒数long tv_usec;// 微秒数
};
也就是我们能够精确到微秒,实际上它还要看系统能不能达到;
1秒=1000毫秒,1毫秒=1000微秒
如果给 timeout 的两个成员都是 0,则 select 将立即返回。如果 timeout 传递NULL,则 select 将一直阻塞,直到某个文件描述符就绪
2.5 select方法的用法简述及返回值
我们先将描述符添加到集合中,将集合传参给select,select返回以后我们就要关注它的返回值;
返回值如果等于0,就说明超时了:
如果返回值为n,n>0,那么就说明这个集合中有n(n>0)个描述符有事件就绪;
如果返回值为-1,说明出错了;
2.6 如何检测集合中有哪些描述符有事件就绪
三.select应用
3.1 select小实例
select检查键盘是否有数据
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/time.h>#define STDIN 0
int main()
{int fd=STDIN;fd_set fdset;while(1){FD_ZERO(&fdset);FD_SET(fd,&fdset);struct timeval tv={5,0};int n=select(fd+1,&fdset,NULL,NULL,&tv);if(n==-1){printf("select error!\n");continue;}else if(n==0){printf("time out!\n");continue;}else{if(FD_ISSET(fd,&fdset)){char buff[128]={0};read(fd,buff,127);printf("read:%s\n",buff);}}}
}
3.2 实现select的tcp服务器端
#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <string.h>#include <assert.h>#include <sys/socket.h>#include <arpa/inet.h>#include <netinet/in.h>#include <fcntl.h>#include <sys/select.h>#include <sys/time.h>#define MAXFD 100int create_socket(){int sockfd=socket(AF_INET,SOCK_STREAM,0);if(sockfd==-1){return -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=bind(sockfd,(struct sockaddr *)&saddr,sizeof(saddr));if(res==-1){return -1;}res=listen(sockfd,5);if(res==-1){return -1;}return sockfd;}void fds_init(int fds[]){for(int i=0;i<MAXFD;i++){fds[i]=-1;}}void fds_add(int fds[],int fd){for(int i=0;i<MAXFD;i++){if(fds[i]==-1){fds[i]=fd;break;}}}void fds_del(int fds[],int fd){for(int i=0;i<MAXFD;i++){if(fds[i]==fd){fds[i]=-1;break;}}}int main(){int sockfd=create_socket();assert(sockfd!=-1);int fds[MAXFD];fds_init(fds);fds_add(fds,sockfd);fd_set fdset;while(1){FD_ZERO(&fdset);int maxfd=-1;for(int i=0;i<MAXFD;i++){if(fds[i]!=-1){FD_SET(fds[i],&fdset);if(maxfd<fds[i]){maxfd=fds[i];}}}struct timeval tv={5,0};int n=select(maxfd+1,&fdset,NULL,NULL,&tv);if(n==-1){printf("select error!\n");continue;}else if(n==0){printf("time out!\n");continue;}else{for(int i=0;i<MAXFD;i++){if(fds[i]==-1){continue;}if(FD_ISSET(fds[i],&fdset)){if(fds[i]==sockfd){//accept;struct sockaddr_in caddr;int len=sizeof(caddr);int c=accept(sockfd,(struct sockaddr*)&caddr,&len);if(c==-1){continue;}printf("accept c=%d\n",c);fds_add(fds,c);}else{//recvchar buff[128]={0};int res=recv(fds[i],buff,127,0);if(res<=0){close(fds[i]);fds_del(fds,fds[i]);printf("one client over!\n");}else{printf("buff(c=%d)=%s\n",fds[i],buff);send(fds[i],"ok",2,0);}}}}}}exit(0);}
封装函数版(跟上面的代码作用是一样的)
//select_ser.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/select.h>#define MAX_FD 128
#define DATALEN 1024//初始化服务器端的sockfd套接字
int InitSocket()
{int sockfd=socket(AF_INET,SOCK_STREAM,0);if(sockfd==-1){return -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=bind(sockfd,(struct sockaddr *)&saddr,sizeof(saddr));if(res==-1){return -1;}res=listen(sockfd,5);if(res==-1){return -1;}return sockfd;
}//初始化记录服务器套接字的数组
void InitFds(int fds[],int n)
{int i=0;for(;i<n;++i){fds[i]=-1;}
}//将套接字描述符添加到数组中
void AddFdToFds(int fds[],int fd,int n)
{int i=0;for(;i<n;++i){if(fds[i]==-1){fds[i]=fd;break;}}
}//删除数组中的套接字描述符
void DelFdFromFds(int fds[],int fd,int n)
{int i=0;for(;i<n;++i){if(fds[i]==fd){fds[i]=-1;break;}}
}//将数组中的套接字描述符设置到fd_set变量中,并返回当前最大的文件描述符值
int SetFdToFdset(fd_set *fdset,int fds[],int n)
{FD_ZERO(fdset);int i=0,maxfd=fds[0];for(;i<n;++i){if(fds[i]!=-1){FD_SET(fds[i],fdset);if(fds[i]>maxfd){maxfd=fds[i];}}}return maxfd;
}void GetClientLink(int sockfd,int fds[],int n)
{struct sockaddr_in caddr;memset(&caddr,0,sizeof(caddr));socklen_t len=sizeof(caddr);int c=accept(sockfd,(struct sockaddr *)&caddr,&len);if(c<0){return ;}printf("A client connection was successful\n");AddFdToFds(fds,c,n);//新的链接套接字c添加进去集合中
}//处理客户端数据
void DealClientData(int fds[],int n,int clifd)
{char data[DATALEN]={0};int num=recv(clifd,data,DATALEN-1,0);if(num<=0){DelFdFromFds(fds,clifd,n);close(clifd);printf("A client disconnected\n");}else{printf("%d:%s\n",clifd,data);send(clifd,"Ok",2,0);}
}//处理select返回的就绪事件
void DealReadyEvent(int fds[],int n,fd_set*fdset,int sockfd)
{int i=0;for(;i<n;++i){if(fds[i]!=-1&& FD_ISSET(fds[i],fdset)){if(fds[i]==sockfd){GetClientLink(sockfd,fds,n);}else{DealClientData(fds,n,fds[i]);}}}
}int main()
{int sockfd=InitSocket();assert(sockfd!=-1);fd_set readfds;int fds[MAX_FD];InitFds(fds,MAX_FD);AddFdToFds(fds,sockfd,MAX_FD);while(1){int maxfd=SetFdToFdset(&readfds,fds,MAX_FD);struct timeval timeout;timeout.tv_sec=5;//秒数timeout.tv_usec=0;//微秒int n=select(maxfd+1,&readfds,NULL,NULL,&timeout);if(n<0){printf("select error\n");break;}else if(n==0){printf("time out\n");continue;}DealReadyEvent(fds,MAX_FD,&readfds,sockfd);}exit(0);}