linux中IO多路複用技術主要有三種:select 、poll 、epoll。
1. 什麼是多路IO複用:
其實就是通過一種機制,可以監視多個描述符(Linux中一切皆文件,打開一個文件就需要一個文件描述符,同樣,在套接字中建立一個連接也需要一個描述符,這個描述符在計算機中是有個數限制的),一旦某個描述符可讀或可寫或某些我們需要的狀態發生變化,則這種機制就能通知程序,從而進行讀寫或其它操作。
本節只做select函數說明,poll、epoll將在之後說明
2. 網絡多路IO複用模型之select
使用select可以完成非阻塞方式工作的程序,它能夠監測需要被監視的文件描述符的變化情況---讀寫或者異常。但是select監視文件描述符的個數是固定的(1024/2048)。
select函數原型:
int select(int nfd, fd_set *readfd, fd_set *writefd,fd_set *errfd, struct timeval *timeout);
maxfd : 指明需要監視所有文件描述符的最大值,並且要+1
readfd: 是一個指向fd_set結構的指針,fd_set可以理解爲一個集合,該集合中存放一些文件描述符,這個變量裏存放了一些要監視讀變化的描述符。
writefd: 同readfd,只不過,這裏的文件描述符監視的是寫變化的(如果有一個文件可寫,則select返回大於0的數)
errfd: 同理,監視發生錯誤的文件描述符號
timeout: 設置超時時間,主要有三種狀態,A.傳入NULL,一直阻塞,直到狀態變化 B.傳入0,則一直返回,無變化爲0,有變化爲正值,C.大於0,則是超時等待的時間,狀態無變化,則超時返回,返回值爲0,否則爲正值
3. 示例,使用select提高服務器處理能力(服務端代碼)
int func_select(int serverfd)
{//serverfd爲服務端套接字描述符,該函數實現服務端監聽客戶端連接,並監聽已連接客戶端的發送數據
fd_set client_fdset; //監聽文件描述符集合
int maxsock = serverfd; //文件描述符中最大文件描述符號
struct timeval timeout; //超時時間
int client_fd[5] = {0}; //已連接客戶端套接字
int connectcount = 0; //描述符數量
char buff[1024];
int ret = 0;
const int connecte = 10; //可連接最大數
while(1)
{
FD_ZERO(&client_fdset); //初始化集合
FD_SET(serverfd,&client_fdset); //將該描述符加入到描述符集合中
timeout.tv_sec = 30; //設置select超時時間
timeout.tv_usec = 0;
for(int i = 0;i<connecte;i++) //將可能存在的描述符加入監聽
{
if(client_fd[i] != 0)
{
FD_SET(client_fd[i],&client_fdset);
}
}
/*select只監聽可讀狀態*/
ret = select(maxsock+1,&client_fdset,NULL,NULL,&timeout);
if(ret == -1)
break;
else if(ret == 0)
continue;
//遍歷已經連接描述符,檢查是否有可都狀態
for(int i = 0;i<connectcount;i++)
{
if(FD_ISSET(client_fd[i],&client_fdset)) //判斷當前描述符是否加入監視集合
{
ret = recv(client_fd[i],buff,1024,0);
if(ret <= 0)
{
close(client_fd[i]);
FD_CLR(client_fd[i],&client_fdset);
client_fd[i] = 0;
}
else
{
printf("%d -->recv data %s\n",i,buff);
}
}
}
if(FD_ISSET(serverfd,&client_fdset)) //檢查是否有新的連接
{
struct sockaddr_in addr_cl;
size_t socksize = sizeof(struct sockaddr_in);
int sockclient = accept(serverfd,(struct sockaddr*)&addr_cl,&socksize);
if(sockclient <= 0)
{
printf("accept err!");
continue;
}
if(connectcount < connecte)
{
client_fd[connectcount++] = sockclient; //保存新連接,方便加入描述符集合
bzero(buff,1024);
strcpy(buff,"this is server! welcome!\n");
send(sockclient,buff,1024,0);
bzero(buff,sizeof(buff));
ret = recv(sockclient,buff,1024,0);
if(ret < 0)
{
printf("recv error!");
close(sockclient);
return 0;
}
printf("recv data:%s\n",buff);
if(sockclient > maxsock)
{
maxsock = sockclient; //記錄最大套接字描述符
}
else
{
printf("max connect ::quit:::\n");
}
}
}
}
return 0;
}
上面代碼主要介紹瞭如何使用select函數來實現服務端的併發能力,當用戶調用了select,則當前進程被阻塞,內核會檢測select集合中的套接字描述符,如果有任何一個要監聽的狀態發生變化,則select會返回。
select的優勢並不是對單個連接處理的更快,所以,連接數太少的化,性能可能比多線程阻塞IO更低,但是它的優點是處理很多連接。
select的優點:
1.可以監視多個文件描述符,減少了平均等待時間
2.處理大批量連接時,有效減輕進程調度的壓力
select缺點:
1.監聽文件描述符有上限,這個上限是由fd_set決定的。
2.它返回的只是就緒事件的個數,要判斷是那個事件滿足,需要遍歷文件描述符。
3.select監聽的集合是輸入輸出參數,每次監聽都需要重新初始化。
4.每次調用select,都需要把fd集合從用戶態拷貝到內核態,這個開銷在fd很多時會很大
5.內核採用遍歷fd集合的方式來檢測就緒事件,這個開銷在fd很多時也很大
6.select和poll都只能工作在低效的LT(水平觸發)模式