select句柄數限制 及總結

一。select函數總結

阻塞方式block,就是進程或是線程執行到這些函數時必須等待某個事件的發生,如果事件沒有發生,進程或線程就被阻塞,函數不能立即返回。使用Select就可以完成非阻塞non-block,就是進程或線程執行此函數時不必非要等待事件的發生,一旦執行肯定返回,以返回值的不同來反映函數的執行情況,如果事件發生則與阻塞方式相同,若事件沒有發生則返回一個代碼來告知事件未發生,而進程或線程繼續執行,所以效率較高。select能夠監視我們需要監視的文件描述符的變化情況。

(一)首先說明兩個結構體:
1:struct fd_set一個存放文件描述符(file descriptor),即文件句柄的聚合,實際上是一long類型的數組,
每一個數組元素都能與一打開的文件句柄(不管是Socket句柄,還是其他文件或命名管道或設備句柄)建立聯繫,建立聯繫的工作由程序員完成;

FD_ZERO(fd_set *fdset):清空fdset與所有文件句柄的聯繫。
FD_SET(int fd, fd_set *fdset):建立文件句柄fd與fdset的聯繫。
FD_CLR(int fd, fd_set *fdset):清除文件句柄fd與fdset的聯繫。
FD_ISSET(int fd, fdset *fdset):檢查fdset聯繫的文件句柄fd是否可讀寫,>0表示可讀寫。

2:struct timeval用來代表時間值,有兩個成員,一個是秒數tv_sec,另一個是毫秒數tv_usec。

(二)下面介紹select()函數原型:
1:int select(int nfds, fd_set *rdfds, fd_set *wtfds, fd_set *exfds, struct timeval *timeout)
2:ndfs:select中監視的文件句柄數,一般設爲要監視的文件中的最大文件號加一。
3:rdfds:select()監視的可讀文件句柄集合,當rdfds映象的文件句柄狀態變成可讀時系統告訴select函數返回。
這個集合中有一個文件可讀,select就會返回一個大於0的值,表示有文件可讀,
如果沒有可讀的文件,則根據timeout參數再判斷是否超時,
若超出timeout的時間,select返回0,若發生錯誤返回負值,
可以傳入NULL值,表示不關心任何文件的讀變化;
4:wtfds: select()監視的可寫文件句柄集合,當wtfds映象的文件句柄狀態變成可寫時系統告訴select函數返回。
如果這個集合中有一個文件可寫,select就會返回一個大於0的值,表示有文件可寫,
如果沒有可寫的文件,則根據timeout參數再判斷是否超時,
若超出timeout的時間,select返回0,若發生錯誤返回負值,
可以傳入NULL值,表示不關心任何文件的寫變化。
5:exfds:select()監視的異常文件句柄集合,當exfds映象的文件句柄上有特殊情況發生時系統會告訴select函數返回。
6:timeout:select()的超時結束時間。
這個參數它使select處於三種狀態,
第一,若將NULL以形參傳入,即不傳入時間結構,就是將select置於阻塞狀態,
一定等到監視文件描述符集合中某個文件描述符發生變化爲止;
第二,若將時間值設爲0秒0毫秒,就變成一個純粹的非阻塞函數,不管文件描述符是否有變化,
都立刻返回繼續執行,文件無變化返回0,有變化返回一個正值;
第三,timeout的值大於0,這就是等待的超時時間,即select在timeout時間內阻塞,
超時時間之內有事件到來就返回了,否則在超時後不管怎樣一定返回,返回值同上述。

7:返回值:負值:select錯誤
0:等待超時,沒有可讀寫或錯誤的文件
正值:某些文件可讀可寫或出錯

(三)下面是一個有三個套接字句柄的例子

int sa, sb, sc;
sa = socket(...);
connect(sa,...);
sb = socket(...);
connect(sb,...);
sc = socket(...);
connect(sc,...);
FD_SET(sa, &rdfds);/* 分別把3個句柄加入讀監視集合裏去 */
FD_SET(sb, &rdfds);
FD_SET(sc, &rdfds);
int maxfd = 0;
if(sa > maxfd) maxfd = sa;/* 獲取3個句柄的最大值 */
if(sb > maxfd) maxfd = sb;
if(sc > maxfd) maxfd = sc;
struct timeval tv;
tv.tv_sec   = ... ;
tv.tv_usec = ...;
ret = select(maxfd + 1, &rdfds, NULL, NULL, &tv); /* 注意是最大值加1 */
if(ret < 0)
{
    perror("select");  /* select函數出錯 */
}
else if(ret == 0)
{
    printf("超時\n"); /* 在設定的tv時間內,socket的狀態沒有發生變化 */
}
else
{
    printf("ret=%d\n", ret);
    if(FD_ISSET(sa, &rdfds)) /* 先判斷一下sa這個被監視的句柄是否真的變成可讀的了 */
    {
        recv(...);  /* 讀取socket句柄裏的數據 */
    }
    ......
}



二。select句柄數限制

connect中使用了select模型,有如下地方需要注意:
我們提供的server api中有很多地方用到了select,特別是在等超時的時候,
例如:

fd_set recv_fds;
int iNum= 0;
if (m_iSocket <0) return -1;
FD_ZERO( &recv_fds );
FD_SET( m_iSocket, &recv_fds );
iNum= select( m_iSocket+1, &recv_fds, NULL, NULL, timeout );
return iNum;


這段代碼對於cgi,或者簡單的邏輯server不會有問題。但是對於多線程或者複雜的server可能會導致server core掉。

原因是select默認只支持1024個句柄,每個句柄採用和1024個bit對應的關係,如果fd的值超過1024,那麼就會溢出。
也就是說,如果上面代碼的m_iSocket>1024,那麼後面的select就會溢出,即使只監聽一個句柄也會溢出。奇怪的是select也不會報錯。
而對於多線程或者大連接的server很有可能分配的fd超過1024.

所以建議以後寫的api儘量有poll或者epoll的方式。
有poll改寫了下面的代碼:

int waittime = (timeout->tv_sec*1000)+(timeout->tv_usec/1000.0);
struct pollfd  client;
client.fd = m_iSocket;
client.events = POLLIN;
return poll(&client, 1, waittime);

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章