linux要用select的原因是什么

111次阅读
没有评论

共计 5036 个字符,预计需要花费 13 分钟才能阅读完成。

本篇内容介绍了“linux 要用 select 的原因是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让丸趣 TV 小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

因为 select 可以使开发者在同时等待多个文件缓冲区,可减少 IO 等待的时间,能够提高进程的 IO 效率。select()函数是 IO 多路复用的函数,允许程序监视多个文件描述符,等待所监视的一个或者多个文件描述符变为“准备好”的状态;所谓的”准备好“状态是指:文件描述符不再是阻塞状态,可以用于某类 IO 操作了,包括可读,可写,发生异常三种。

select 是一个计算机函数,位于头文件 #include sys/select.h。该函数用于监视文件描述符的变化情况——读写或是异常。

1. select 函数介绍

select 函数是 IO 多路复用的函数,它主要的功能是用来等文件描述符中的事件是否就绪,select 可以使我们在同时等待多个文件缓冲区,减少 IO 等待的时间,能够提高进程的 IO 效率。

select()函数允许程序监视多个文件描述符,等待所监视的一个或者多个文件描述符变为“准备好”的状态。所谓的”准备好“状态是指:文件描述符不再是阻塞状态,可以用于某类 IO 操作了,包括可读,可写,发生异常三种

2. select 函数参数的介绍

 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 中知道哪些文件描述符的事件就绪.

如果我们要不断轮询等待文件描述符,则应用进程需要不断的重新设置 readfds 和 writefds,因为每一次调用 select,内核会修改 readfds 和 writefds,所以我们需要在 应用程序 中 设置一个数组 来保存程序需要等待的文件描述符,保证调用 select 的时候 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(); }

测试:

5.Select 的缺陷

由于 fd_set 的上限是 1024,所以 select 能等待的读事件的文件描述符和写事件的文件描述是有上限的,如果作为一个大型服务器,能够同时链接的客户端是远远不够的。

每次应用进程调用一次 select 之前,都需要重新设定 writefds 和 readfds,如果进行轮询调用 select,这对影响 cpu 效率。

内核每一次等待文件描述符 都会重新扫描所有 readfds 或者 writefds 中的所有文件描述符,如果有较多的文件描述符,则会影响效率。

“linux 要用 select 的原因是什么”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注丸趣 TV 网站,丸趣 TV 小编将为大家输出更多高质量的实用文章!

正文完
 
丸趣
版权声明:本站原创文章,由 丸趣 2023-07-12发表,共计5036字。
转载说明:除特殊说明外本站除技术相关以外文章皆由网络搜集发布,转载请注明出处。
评论(没有评论)