IO复用之select

目录

一.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);}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/535956.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Xterminal:未来的终端体验

✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a; 开发环境篇 ✨特色专栏&#xff1a; M…

【前端】HTML常用标签

因为想当个全栈&#xff0c;所以巩固了一下HTML与CSS和JS基础&#xff0c;这一篇博客是HTML部分 文章目录 HTML 基础标签 1HTML 基础框架HTML 基础标签语义标签文本格式化标签div 与 span 标签图像标签超链接特殊字符 基础标签 2 | 表格表格的使用表格标签表格属性表格的头部与…

按键开关机的锂电池充放电解决方案

一、产品概述 TP4562 是一款集成线性充电管理、同步升压转换、电池电量指示和多种保护功能的单芯片电源管理SOC&#xff0c;为锂电池的充放电提供完整的单芯片电源解决方案。 TP4562 内部集成了线性充电管理模块、同步升压放电管理模块、电量检测与 LED 指示模块、保护模块、…

DAY by DAY 史上最全的Linux常用命令汇总----man

man是按照手册的章节号的顺序进行搜索的。 man设置了如下的功能键&#xff1a; 功能键 功能 空格键 显示手册页的下一屏 Enter键 一次滚动手册页的一行 b 回滚一屏 f 前滚一屏 q 退出man命令 h 列出所有功能键 /word 搜索word字符串 注意&#xff1a…

04-自媒体文章-自动审核

自媒体文章-自动审核 1)自媒体文章自动审核流程 1 自媒体端发布文章后&#xff0c;开始审核文章 2 审核的主要是审核文章的内容&#xff08;文本内容和图片&#xff09; 3 借助第三方提供的接口审核文本 4 借助第三方提供的接口审核图片&#xff0c;由于图片存储到minIO中&…

详解Python中%r和%s的区别及用法

首先看下面的定义&#xff1a; %r用rper()方法处理对象 %s用str()方法处理对象 函数str() 用于将值转化为适于人阅读的形式&#xff0c;而repr() 转化为供解释器读取的形式&#xff08;如果没有等价的语法&#xff0c;则会发生SyntaxError 异常&#xff09; 某对象没有适于人…

2024春招看了上百份程序员简历,这个工具写的简历最好!(附模板)

你们在制作简历时&#xff0c;是不是基本只关注两件事&#xff1a;简历模板&#xff0c;还有基本信息的填写。 当你再次坐下来更新你的简历时&#xff0c;可能会发现自己不自觉地选择了那个“看起来最好看的模板”&#xff0c;填写基本信息&#xff0c;却没有深入思考如何使简历…

【网络工程师进阶之路】BFD技术

个人名片&#xff1a;&#x1faaa; &#x1f43c;作者简介&#xff1a;一名大三在校生&#xff0c;喜欢AI编程&#x1f38b; &#x1f43b;‍❄️个人主页&#x1f947;&#xff1a;落798. &#x1f43c;个人WeChat&#xff1a;hmmwx53 &#x1f54a;️系列专栏&#xff1a;&a…

leetcode刷题(javaScript)——分治思想(二分查找、快速排序)相关场景题总结

分治思想是一种将问题分解成更小的子问题&#xff0c;然后解决子问题并将结果合并的算法设计策略。二分查找、快速排序和折半查找都属于分治思想的经典算法。在leetcode里&#xff0c;分治思想一般结合其他场景出现&#xff0c;构成复合型题目。但是在看题时一定要了解能否用分…

前端Vue列表组件 list组件:实现高效数据展示与交互

前端Vue列表组件 list组件&#xff1a;实现高效数据展示与交互 摘要&#xff1a;在前端开发中&#xff0c;列表组件是展示数据的重要手段。本文将介绍如何使用Vue.js构建一个高效、可复用的列表组件&#xff0c;并探讨其在实际项目中的应用。 效果图如下&#xff1a; 一、引言…

数码管的动态显示(二)

1.原理 这个十六进制是右边的dp为高位。 数码管的动态显示&#xff0c;在第一个计数周期显示个位&#xff0c;在第二个周期显示十位&#xff0c;在第三个周期显示百位由于人眼的视觉和数码管的特性&#xff0c;感觉就是显示了234&#xff0c;每个数码管的显示需要从输入的数据里…

windows系统玩游戏找不到d3dx9_43.dll缺失,无法启动此程序的解决方法

今日&#xff0c;我们要深入讨论d3dx9_43.dll文件的重要性及其缺失问题。最近&#xff0c;我也遇到了这个文件丢失的困扰&#xff0c;因此想借此机会与大家分享如何解决d3dx9_43.dll缺失的问题。 一.电脑d3dx9_43.dll丢失会提示什么&#xff1f; 关于电脑提示d3dx9_43.dll丢失…