如何快速理解Select原理
Posted 世_生
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何快速理解Select原理相关的知识,希望对你有一定的参考价值。
目录
五种IO模型
- 理解五种IO模型的基本概念
阻塞IO
- 内核把数据准备好之前,系统调用一直等待(所以的套接字编程,默认都是阻塞方式)
非阻塞IO
- 内核没有把数据准备好,系统调用依然直接返回,并返回并且返回EWOULDBLOCK错误码。
非阻塞IO往往需要程序员以循环的方式来读写文件描述符,这个过程称为轮询,对CPU来说是较大的浪费,一般只能在特定场合使用。
信号驱动IO
- 内核将数据准备好了之后,向进程发送SIGIP信号通知进程进行IO操作
IO多路转接
- 虽然从流程图上看起来和阻塞IO类似. 实际上最核心在于IO多路转接能够同时等待多个文件
描述符的就绪状态
异步IO
- 由内核在数据拷贝完成时, 通知应用程序(而信号驱动是告诉应用程序何时可以开始拷贝数据).
Select是什么
- select是操作系统提供的一个系统接口
- select可以监视多个文件描述符的状态变化
- select监测到文件描述符有就绪事件时,进程会对就绪事件进行IO操作
读事件就绪:内核中有数据已经准备好了,等待进程调用系统接口(recv、read)将数据从内核拷贝到用户空间。
写事件就绪:内核中已经有空间了,等待进行调用系统接口(send、write)将数据写入到内核。
Select只是在完成等这个过程,具有就绪事件通知机制。
Select函数中参数的意义
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
函数原型
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
fd_set结构:位图(其中位图对应的位置表示要关心的文件描述符)
fd_set的结构是变量咋select的函数中是个输入输出型参数
输入:如果要监视文件描述符是否有数据进行读操作。fd_set readfds的结构对应的比特位上会置为1。
输出:如果有文件描述符要进行读操作。fd_set readfds的结构对应的比特位上会置为1。
nfds:监视的文件描述符的数量+1
readfds:监视读就绪的文件描述符
writefds:监视写就绪的文件描述符
exceptfds:监控异常发生达文件描述符集合
timeout:定时阻塞监控时间,3种情况
- NULL,永远等下去
- 设置timeval,等待固定时间
- 设置timeval里时间均为0,检查描述字后立即返回,轮询
struct timeval
long tv_sec; /* seconds /
long tv_usec; / microseconds */
;
返回类型是int型,返回一个整形数.
成功:表示有多少个文件描述符有就绪事件发生。
错误:返回 -1
超时:返回 0
Select原理
原理:统一把文件描述符给select,让select帮我们来进行等,有就绪事件发生时,用户再进行拷贝。(等:交给内核的)
- 将我们的文件描述符按照需要的等待事件(读或者写或者读写)分别添加到readfds、writefds的位图中。
- select会将有事件发生的文件描述符写入readfds、writefds,返回给用户
- 我们按照readfds、writefds的位图来对有事件的文件描述符进行读写操作
select缺点
每次调用select, 都需要手动设置fd集合, 从接口使用角度来说也非常不便.
每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
select支持的文件描述符数量太小
Select的执行过程
写一段伪代码:
把需要监测的文件描述符都保存在一个数组中
for(;;)
1、对readfds、writefds进行清空
2、将数组中的文件描述符放入readfds、writefds中
switch(select(监视的文件描述符总数+1,readfds ,wrtifds,nullptr,时间))
case 0:
break;
case -1:
break;
default:
有就绪事件发生
1、有读事件发生,进行读操作
2、有写事件发生,进行写操作
循环查看数组中的文件描述符是否在readfds、writefds中,有则需要进行读写
break;
维护位图的函数
void FD_CLR(int fd, fd_set *set); // 用来清除描述词组set中相关fd 的位
int FD_ISSET(int fd, fd_set *set); // 用来测试描述词组set中相关fd 的位是否为真
void FD_SET(int fd, fd_set *set); // 用来设置描述词组set中相关fd的位
void FD_ZERO(fd_set *set); // 用来清除描述词组set的全部位
代码实现
void Start()
InitAdd();/初始化记录文件描述符的数组,全为-1
int max=-1;/文件描述符数量最大值
sockadd[0]=lisent_sock;/将监听文件描述符放在数组的第一个位置
for(;;)
FD_ZERO(&readfds);/清空readfds的全部标识位
for(auto i=0;i<NUM;i++)/添加文件描述符到readfds中
if(sockadd[i]==-1)
continue;
FD_SET(sockadd[i],&readfds);/添加加文件描述符到readfds中
if(max<sockadd[i])
max=sockadd[i];/算出文件描述符的数量的最多数量
struct timeval timeout=5,0;
switch(select(max+1,&readfds,nullptr,nullptr,&timeout))
case 0:/没有就绪事件发生
break;
case -1:/有异常退出
break;
default:/有就绪事件发生
HandlerEvent();/执行相关函数
break;
void HandlerEvent()
for(auto i=0;i<NUM;i++)
if(sockadd[i]==-1)
continue;
/连接请求事件
if(sockadd[i]==lisent_sock && FD_ISSET(sockadd[i],&readfds))
struct sockaddr_in peer;
socklen_t len=sizeof(peer);
int sock=accept(sockadd[i],(struct sockaddr*)&peer,&len);
if(sock<0)
continue;
uint16_t peer_port = htons(peer.sin_port);
std::string peer_ip = inet_ntoa(peer.sin_addr);
std::cout << "get a new link: " << peer_ip << ":" << peer_port << std::endl;
if(!addFd(sock))
std::cout<<"assfd 失败"<<std::endl;
close(sock);
std::cout<<sock<<std::endl;
else
/读写请求事件
if(FD_ISSET(sockadd[i],&readfds))
char buff[1024];
ssize_t s=recv(sockadd[i],buff,sizeof(buff)-1,0);
if(s==0)
std::cout<<"没有数据读取";
std::cout<<"client quit"<<std::endl;
close(sockadd[i]);
sockadd[i]=-1;
else if(s>0)
buff[s]=0;
std::cout<<"echo# "<<buff<<std::endl;
else
std::cout<<"read errce"<<std::endl;
close(sockadd[i]);
sockadd[i]=-1;
//fi
//fi
//rof
//nur
bool addFd(int sock)
for(auto i=0;i<NUM;i++)
if(sockadd[i]==-1)
sockadd[i]=sock;
return true;
return false;
文件描述符的最大数量为1024个。
因为:
sizeof(fd_set)-》128
128*8=1024
以上是关于如何快速理解Select原理的主要内容,如果未能解决你的问题,请参考以下文章