因為select可以使開發者在同時等待多個檔案緩衝區,可減少IO等待的時間,能夠提高程序的IO效率。select()函數是IO多路複用的函數,允許程式監視多個檔案描述符,等待所監視的一個或者多個檔案描述符變為「準備好」的狀態;所謂的」準備好「狀態是指:檔案描述符不再是阻塞狀態,可以用於某類IO操作了,包括可讀,可寫,發生異常三種。
本教學操作環境:linux7.3系統、Dell G3電腦。
select是一個計算機函數,位於標頭檔案#include <sys/select.h> 。該函數用於監視檔案描述符的變化情況——讀寫或是異常。
select函數是IO多路複用的函數,它主要的功能是用來等檔案描述符中的事件是否就緒,select可以使我們在同時等待多個檔案緩衝區 ,減少IO等待的時間,能夠提高程序的IO效率。
select()函數允許程式監視多個檔案描述符,等待所監視的一個或者多個檔案描述符變為「準備好」的狀態。所謂的」準備好「狀態是指:檔案描述符不再是阻塞狀態,可以用於某類IO操作了,包括可讀,可寫,發生異常三種
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
登入後複製
等待的檔案描述符的最大值+1,例如:應用程序想要去等待檔案描述符3,5,8的事件,則
nfds=max(3,5,8)+1;
登入後複製
readfds和writefds,exceptfds的型別都是fd_set,那麼fd_set型別是什麼呢?
與readfds類似,writefds是等待寫事件(緩衝區中是否有空間)的集合,如果不關心寫事件,則可以傳值NULL。
如果核心等待相應的檔案描述符發生異常,則將失敗的檔案描述符設定進exceptfds中,如果不關心錯誤事件,可以傳值NULL。
設定select在核心中阻塞的時間,如果想要設定為非阻塞,則設定為NULL。如果想讓select阻塞5秒,則將建立一個struct timeval time={5,0};
其中struct timeval的結構體型別是:
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
登入後複製
應用程序和核心都需要從readfds和writefds獲取資訊,其中,核心需要從readfds和writefds知道哪些檔案描述符需要等待,應用程序需要從readfds和writefds中知道哪些檔案描述符的事件就緒.
如果我們要不斷輪詢等待檔案描述符,則應用程序需要不斷的重新設定readfds和writefds,因為每一次呼叫select,核心會修改readfds和writefds,所以我們需要在 應用程式 中 設定一個陣列 來儲存程式需要等待的檔案描述符,保證呼叫 select 的時候readfds 和 writefds中的將如下:
如果是一個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();
}
登入後複製
測試:
推薦學習:Linux視訊教學
以上就是linux為什麼要用select的詳細內容,更多請關注TW511.COM其它相關文章!