一、select函数
select函数是IO多路复用的函数,它主要的功能是用来等文件描述符中的事件是否就绪,select可以使我们在同时等待多个文件缓冲区 ,减少IO等待的时间,能够提高进程的IO效率。
select()函数允许程序监视多个文件描述符,等待所监视的一个或者多个文件描述符变为“准备好”的状态。所谓的”准备好“状态是指:文件描述符不再是阻塞状态,可以用于某类IO操作了,包括可读,可写,发生异常三种
二、参数介绍
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
ndfs
等待的文件描述符的最大值+1,例如:应用进程想要去等待文件描述符3,5,8的事件,则
nfds=max(3,5,8)+1;
fd_set类型
readfds和writefds,exceptfds的类型都是fd_set,那么fd_set类型是什么呢?
- fd_set类型本质是一个位图,位图的位置 表示 相对应的文件描述符,内容表示该文件描述符是否有效,1代表该位置的文件描述符有效,0则表示该位置的文件描述符无效。
- 如果将文件描述符2,3设置位图当中,则位图表示的是为1100。
- fd_set的上限是1024个文件描述符。
readfds
- readfds是 等待读事件的文件描述符集合,.如果不关心读事件(缓冲区有数据),则可以传NULL值。
- 应用进程和内核都可以设置readfds,应用进程设置readfds是为了通知内核去等待readfds中的文件描述符的读事件.而 内核设置readfds是为了告诉应用进程哪些读事件生效
writefds
与readfds类似,writefds是等待写事件(缓冲区中是否有空间)的集合,如果不关心写事件,则可以传值NULL。
exceptfds
如果内核等待相应的文件描述符发生异常,则将失败的文件描述符设置进exceptfds中,如果不关心错误事件,可以传值NULL。
timeout
设置select在内核中阻塞的时间,如果想要设置为非阻塞,则设置为NULL。如果想让select阻塞5秒,则将创建一个struct timeval time={5,0};
其中struct timeval的结构体类型是:
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
返回值
- 如果没有文件描述符就绪就返回0;
- 如果调用失败返回-1;
- 如果timeout中中readfds中有事件发生,则返回timeout剩下的时间
3.select的工作流程
应用进程和内核都需要从readfds和writefds获取信息,其中,内核需要从readfds和writefds知道哪些文件描述符需要等待,应用进程需要从readfds和writefds中知道哪些文件描述符的事件就绪.
4.Select服务器
如果是一个select服务器进程,则服务器进程会不断的接收有新链接,每个链接对应一个文件描述符,如果想要我们的服务器能够同时等待多个链接的数据的到来,我们监听套接字listen_sock读取新链接的时候,我们需要将新链接的文件描述符保存到read_arrys数组中,下次轮询检测的就会将新链接的文件描述符设置进readfds中,如果有链接关闭,则将相对应的文件描述符从read_arrys数组中拿走。
一张图看懂select服务器:
简易版的select服务器:
server.hpp文件:
#pragma once
#include<iostream>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<string.h>
using std::cout;
using std::endl;
#define BACKLOG 5
namespace sjp{
class server{
public:
static int Socket(){
int sock=socket(AF_INET,SOCK_STREAM,0);
if(sock>0)
return sock;
if(sock<0)
exit(-1);
W> }
static bool Bind(int sockfd,short int port){
struct sockaddr_in lock;
memset(&lock,'\0',sizeof(lock));
lock.sin_family=AF_INET;
lock.sin_port=htons(port);
lock.sin_addr.s_addr=INADDR_ANY;
if(bind(sockfd,(struct sockaddr*)&lock,(socklen_t)sizeof(lock))<0){
exit(-2);
}
return true;
}
static bool Listen(int sockfd){
if(listen(sockfd,BACKLOG)<0){
exit(-3);
}
return true;
}
};
}
select_server.hpp文件
#pragma once
#include<vector>
#include"server.hpp"
#include<unistd.h>
#include<time.h>
namespace Select{
class select_server{
private:
int listen_sock;//监听套接字
int port;
public:
select_server(int _port):port(_port){}
//初始化select_server服务器
void InitServer(){
listen_sock=sjp::server::Socket();
sjp::server::Bind(listen_sock,port);
sjp::server::Listen(listen_sock);
}
void Run(){
std::vector<int> readfds_arry(1024,-1);//readfds_arry保存读事件的文件描述符
readfds_arry[0]=listen_sock;//将监听套接字保存进readfds_arry数组中
fd_set readfds;
while(1){
FD_ZERO(&readfds);
int nfds=0;
//将read_arry数组中的文件描述符设置进程readfds_arry位图中
for(int i=0;i<1024;i++)
{
if(readfds_arry[i]!=-1){
FD_SET(readfds_arry[i],&readfds);
if(nfds<readfds_arry[i]){
nfds=readfds_arry[i];
}
}
}
//调用select对readfds中的文件描述符进行等待数据
switch(select(nfds+1,&readfds,NULL,NULL,NULL)){
case 0:
//没有一个文件描述符的读事件就绪
cout<<"select timeout"<<endl;
break;
case -1:
//select失败
cout<<"select error"<<endl;
default:
{
//有读事件发生
Soluation(readfds_arry,readfds);
break;
}
}
}
}
void Soluation(std::vector<int>& readfds_arry,fd_set readfds){
W> for(int i=0;i<readfds_arry.size();i++){
if(FD_ISSET(readfds_arry[i],&readfds))
{
if(readfds_arry[i]==listen_sock){
//有新链接到来
struct sockaddr peer;
socklen_t len;
int newfd=accept(listen_sock,&peer,&len);
cout<<newfd<<endl;
//将新链接设置进readfds_arry数组中
AddfdsArry(readfds_arry,newfd);
}
else{
//其他事件就绪
char str[1024];
int sz=recv(readfds_arry[i],&str,sizeof(str),MSG_DONTWAIT);
switch(sz){
case -1:
//读取失败
cout<<readfds_arry[i]<<": recv error"<<endl;
break;
case 0:
//对端关闭
readfds_arry[i]=-1;
cout<<"peer close"<<endl;
break;
default:
str[sz]='\0';
cout<<str<<endl;
break;
}
}
}
}
}
void AddfdsArry(std::vector<int>& fds_arry,int fd){
W> for(int i=0;i<fds_arry.size();i++){
if(fds_arry[i]==-1){
fds_arry[i]=fd;
break;
}
}
}
};
}
select_server.cc文件
#include"select_server.hpp"
int main(int argv,char* argc[]){
if(argv!=2){
cout<<"./selectserver port"<<endl;
exit(-4);
}
int port=atoi(argc[1]);//端口号
Select::select_server* sl=new Select::select_server(port);
sl->InitServer();
sl->Run();
}
五.Select的缺陷
- 由于fd_set的上限是1024,所以select能等待的读事件的文件描述符和写事件的文件描述是有上限的,如果作为一个大型服务器,能够同时链接的客户端是远远不够的。
- 每次应用进程调用一次select之前,都需要重新设定writefds和readfds,如果进行轮询调用select,这对影响cpu效率。
- 内核每一次等待文件描述符 都会重新扫描所有readfds或者writefds中的所有文件描述符,如果有较多的文件描述符,则会影响效率。