共計 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 小編將為大家輸出更多高質量的實用文章!