linux select 多路复用机制
函數(shù)作用:
系統(tǒng)提供select函數(shù)來實現(xiàn)多路復(fù)用輸入/輸出模型。select系統(tǒng)調(diào)用是用來讓我們的程序監(jiān)視多個文件句柄的狀態(tài)變化的。程序會停在select這里等待,直到被監(jiān)視的文件句柄有一個或多個發(fā)生了狀態(tài)改變。關(guān)于文件句柄,其實就是一個整數(shù),我們最熟悉的句柄是0、1、2三個,0是標(biāo)準(zhǔn)輸入,1是標(biāo)準(zhǔn)輸出,2是標(biāo)準(zhǔn)錯誤輸出。0、1、2是整數(shù)表示的,對應(yīng)的FILE *結(jié)構(gòu)的表示就是stdin、stdout、stderr。
函數(shù)原型:
[cpp]?view plaincopy
參數(shù)說明:
參數(shù)maxfd是需要監(jiān)視的最大的文件描述符值+1;rdset,wrset,exset分別對應(yīng)于需要檢測的可讀文件描述符的集合,可寫文件描述符的集 合及異常文件描述符的集合。struct timeval結(jié)構(gòu)用于描述一段時間長度,如果在這個時間內(nèi),需要監(jiān)視的描述符沒有事件發(fā)生則函數(shù)返回,返回值為0。
下面的宏提供了處理這三種描述詞組的方式:
FD_CLR(inr fd,fd_set* set);用來清除描述詞組set中相關(guān)fd 的位
FD_ISSET(int fd,fd_set *set);用來測試描述詞組set中相關(guān)fd 的位是否為真
FD_SET(int fd,fd_set*set);用來設(shè)置描述詞組set中相關(guān)fd的位
FD_ZERO(fd_set *set);用來清除描述詞組set的全部位
參數(shù)timeout為結(jié)構(gòu)timeval,用來設(shè)置select()的等待時間,其結(jié)構(gòu)定義如下:
[cpp]?view plaincopy
如果參數(shù)timeout設(shè)為:
NULL,則表示select()沒有timeout,select將一直被阻塞,直到某個文件描述符上發(fā)生了事件。
0:僅檢測描述符集合的狀態(tài),然后立即返回,并不等待外部事件的發(fā)生。
特定的時間值:如果在指定的時間段里沒有事件發(fā)生,select將超時返回。
函數(shù)返回值:
執(zhí)行成功則返回文件描述詞狀態(tài)已改變的個數(shù),如果返回0代表在描述詞狀態(tài)改變前已超過timeout時間,沒有返回;當(dāng)有錯誤發(fā)生時則返回-1,錯誤原因存于errno,此時參數(shù)readfds,writefds,exceptfds和timeout的值變成不可預(yù)測。錯誤值可能為:
EBADF 文件描述詞為無效的或該文件已關(guān)閉
EINTR 此調(diào)用被信號所中斷
EINVAL 參數(shù)n 為負(fù)值。
ENOMEM 核心內(nèi)存不足
常見的程序片段如下:
fs_set readset;
FD_ZERO(&readset);
FD_SET(fd,&readset);
select(fd+1,&readset,NULL,NULL,NULL);
if(FD_ISSET(fd,readset){……}
理解select模型:
理解select模型的關(guān)鍵在于理解fd_set,為說明方便,取fd_set長度為1字節(jié),fd_set中的每一bit可以對應(yīng)一個文件描述符fd。則1字節(jié)長的fd_set最大可以對應(yīng)8個fd。
(1)執(zhí)行fd_set set; FD_ZERO(&set);則set用位表示是0000,0000。
(2)若fd=5,執(zhí)行FD_SET(fd,&set);后set變?yōu)?001,0000(第5位置為1)
(3)若再加入fd=2,fd=1,則set變?yōu)?001,0011
(4)執(zhí)行select(6,&set,0,0,0)阻塞等待
(5)若fd=1,fd=2上都發(fā)生可讀事件,則select返回,此時set變?yōu)?000,0011。注意:沒有事件發(fā)生的fd=5被清空。
基于上面的討論,可以輕松得出select模型的特點:
(1)可監(jiān)控的文件描述符個數(shù)取決與sizeof(fd_set)的值。我這邊服務(wù) 器上sizeof(fd_set)=512,每bit表示一個文件描述符,則我服務(wù)器上支持的最大文件描述符是512*8=4096。據(jù)說可調(diào),另有說雖 然可調(diào),但調(diào)整上限受于編譯內(nèi)核時的變量值。本人對調(diào)整fd_set的大小不太感興趣,參考http://www.cppblog.com?/CppExplore/archive/2008/03/21/45061.html中的模型2(1)可以有效突破select可監(jiān)控的文件描述符上 限。
(2)將fd加入select監(jiān)控集的同時,還要再使用一個數(shù)據(jù)結(jié)構(gòu)array保存放到select監(jiān)控集中的fd,一是用于再select 返回后,array作為源數(shù)據(jù)和fd_set進(jìn)行FD_ISSET判斷。二是select返回后會把以前加入的但并無事件發(fā)生的fd清空,則每次開始 select前都要重新從array取得fd逐一加入(FD_ZERO最先),掃描array的同時取得fd最大值maxfd,用于select的第一個 參數(shù)。
(3)可見select模型必須在select前循環(huán)array(加fd,取maxfd),select返回后循環(huán)array(FD_ISSET判斷是否有時間發(fā)生)。
下面給一個偽碼說明基本select模型的服務(wù)器模型:
[cpp]?view plaincopy
[cpp]?view plaincopy
利用Select模型,設(shè)計的web服務(wù)器:
[cpp]?view plaincopy
1 基本原理
注:select 原理圖,摘自?IBM iSeries 信息中心。
1 數(shù)據(jù)結(jié)構(gòu)與函數(shù)原型
1.1 select
- 函數(shù)原型 int select(int nfds,fd_set *readset,fd_set *writeset,fd_set* exceptset,struct timeval *timeout);
- 頭文件
- select位于: #include <sys/select.h>
- struct timeval位于: #include <sys/time.h>
- 返回值
返回對應(yīng)位仍然為1的fd的總數(shù)。
- 參數(shù)
- nfds:第一個參數(shù)是:最大的文件描述符值+1;
- readset:可讀描述符集合;
- writeset:可寫描述符集合;
- exceptset:異常描述符;
- timeout:select 的監(jiān)聽時長,如果這短時間內(nèi)所監(jiān)聽的 socket 沒有事件發(fā)生。
1.2 fd_set
1.2.1 清空描述符集合
FD_ZERO(fd_set *)1.2.2 向描述符集合添加指定描述符
FD_SET(int, fd_set *)1.2.3 從描述符集合刪除指定描述符
FD_CLR(int, fd_set *)1.2.4 檢測指定描述符是否在描述符集合中
FD_ISSET(int, fd_set *)1.2.5 描述符最大數(shù)量
#define FD_SETSIZE 10241.3 描述符集合
可讀描述符集合中可讀的描述符,為1,其他為0;可寫也類似。異常描述符集合中有異常等待處理的描述符的值為1,其他為0。
1.4 ioctl
-
函數(shù)原型:
int ioctl(int handle, int cmd,[int *argdx, int argcx]); -
頭文件:
#include <sys/ioctl.h> -
返回值:
- 0 - 成功
- 1 - 失敗
2 示例
程序各部分的解釋在注釋中。
#include <sys/socket.h> #include <string.h> #include <sys/time.h> #include <netinet/in.h> #include <sys/ioctl.h> #include <stdlib.h> #include <errno.h> #include <stdio.h> #include <unistd.h>#define TRUE 1 #define FALSE 0int main(int argc, char *argv[]) {int i, len, rc, on = TRUE;int listen_sd, new_sd = 0, max_sd;int desc_ready;char buffer[80];int close_conn, end_server = FALSE;struct sockaddr_in server_addr;struct timeval timeout;struct fd_set master_set, working_set;// Listenlisten_sd = socket(AF_INET, SOCK_STREAM, 0);if (listen_sd < 0){perror("socket() failed");exit(-1);}// Set socket optionsrc = setsockopt(listen_sd, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on));if (rc < 0){perror("setsockopt() failed");close(listen_sd);exit(-1);}// Set IO controlrc = ioctl(listen_sd, FIONBIO, (char *) &on);if (rc < 0){perror("ioctl() failed");close(listen_sd);exit(-1);}// Bindmemset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_addr.s_addr = htonl(INADDR_ANY);server_addr.sin_port = htons(atoi(argv[1]));rc = bind(listen_sd, (struct sockaddr *) &server_addr, sizeof(server_addr));if (rc < 0){perror("bind() failed\n");close(listen_sd);exit(-1);}// Listenrc = listen(listen_sd, 32);if (rc < 0){perror("listen() failed\n");close(listen_sd);exit(-1);}// Intialize sd setFD_ZERO(&master_set);max_sd = listen_sd;FD_SET(listen_sd, &master_set);timeout.tv_sec = 3 * 60;timeout.tv_usec = 0;// Startdo{// Copy master_set into working_setmemcpy(&working_set, &master_set, sizeof(master_set));printf("Waiting on select()...\n");rc = select(max_sd + 1, &working_set, NULL, NULL, &timeout);if (rc < 0){perror(" select() failed\n");break;}if (rc == 0){printf(" select() timed out. End program.\n");break;}desc_ready = rc; // number of sds ready in working_set// Check each sd in working_setfor (i = 0; i <= max_sd && desc_ready > 0; ++i){// Check to see if this sd is readyif (FD_ISSET(i, &working_set)){--desc_ready;// Check to see if this is the listening sdif (i == listen_sd){printf(" Listeing socket is readable\n");do{// Acceptnew_sd = accept(listen_sd, NULL, NULL);// Nothing to be acceptedif (new_sd < 0){// All have been acceptedif (errno != EWOULDBLOCK){perror(" accept() failed\n");end_server = TRUE;}break;}// Insert new_sd into master_setprintf(" New incoming connection - %d\n", new_sd);FD_SET(new_sd, &master_set);if (new_sd > max_sd){max_sd = new_sd;}}while (new_sd != -1);}// This is not the listening sdelse{close_conn = FALSE;printf(" Descriptor %d is avaliable\n", i);do{rc = recv(i, buffer, sizeof(buffer), 0);// Receive data on sd "i", until failure occursif (rc < 0){// Normal failureif (errno != EWOULDBLOCK){perror(" recv() failed\n");close_conn = TRUE;}break;}// The connection has been closed by the clientif (rc == 0){printf(" Connection closed\n");close_conn = TRUE;break;}/* Receiving data succeeded and echo it backthe to client */len = rc;printf(" %d bytes received\n", len);rc = send(i, buffer, len, 0);if (rc < 0){perror(" send() failed");close_conn = TRUE;break;}}while (TRUE);// If unknown failure occuredif (close_conn){// Close the sd and remove it from master_setclose(i);FD_CLR(i, &master_set);// If this is the max sdif (i == max_sd){// Find the max sd in master_set nowwhile (FD_ISSET(max_sd, &master_set) == FALSE){--max_sd;}} // End of if (i == max_sd)} // End of if (close_conn)}}}}while (end_server == FALSE);/* Close each sd in master_set */for (i = 0; i < max_sd; ++i){if (FD_ISSET(i, &master_set)){close(i);}}return 0; }總結(jié)
以上是生活随笔為你收集整理的linux select 多路复用机制的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux中kill,pkill,kil
- 下一篇: 基于select模型的TCP服务器