8.2 套接字I/O模型
共有五種類(lèi)型的套接字I / O模型,可讓W(xué)i n s o c k應(yīng)用程序?qū) / O進(jìn)行管理,它們包括: s e l e c t(選擇)、W S A A s y n c S e l e c t(異步選擇)、W S A E v e n t S e l e c t(事件選擇)、o v e r l a p p e d(重疊)以及completion port(完成端口)
8.2.1 select模型
s e l e c t(選擇)模型是Wi n s o c k中最常見(jiàn)的I / O模型。之所以稱(chēng)其為“ s e l e c t模型”,是由于它的“中心思想”便是利用s e l e c t函數(shù),實(shí)現(xiàn)對(duì)I / O的管理!最初設(shè)計(jì)該模型時(shí),主要面向的是某些使用U n i x操作系統(tǒng)的計(jì)算機(jī),它們采用的是B e r k e l e y套接字方案。s e l e c t模型已集成到Winsock 1.1中,它使那些想避免在套接字調(diào)用過(guò)程中被無(wú)辜“鎖定”的應(yīng)用程序,采取一種有序的方式,同時(shí)進(jìn)行對(duì)多個(gè)套接字的管理。由于Winsock 1.1向后兼容于B e r k e l e y套接字實(shí)
施方案,所以假如有一個(gè)B e r k e l e y套接字應(yīng)用使用了s e l e c t函數(shù),那么從理論角度講,毋需對(duì)其進(jìn)行任何修改,便可正常運(yùn)行。
利用s e l e c t函數(shù),我們判斷套接字上是否存在數(shù)據(jù),或者能否向一個(gè)套接字寫(xiě)入數(shù)據(jù)。之所以要設(shè)計(jì)這個(gè)函數(shù),唯一的目的便是防止應(yīng)用程序在套接字處于鎖定模式中時(shí),在一次I / O綁定調(diào)用(如s e n d或r e c v)過(guò)程中,被迫進(jìn)入“鎖定”狀態(tài);同時(shí)防止在套接字處于非鎖定模式中時(shí),產(chǎn)生W S A E W O U L D B L O C K錯(cuò)誤。除非滿(mǎn)足事先用參數(shù)規(guī)定的條件,否則s e l e c t函數(shù)會(huì)在進(jìn)行I / O操作時(shí)鎖定。s e l e c t的函數(shù)原型如下:
int select(
??????int nfds,
??????fd_set FAR * readfds,
??????fd_set FAR * writefds,
??????fd_set FAR * exceptfds,
??????const struct timeval FAR * timeout
?????);
?????
其中,第一個(gè)參數(shù)n f d s會(huì)被忽略。之所以仍然要提供這個(gè)參數(shù),只是為了保持與早期的B e r k e l e y套接字應(yīng)用程序的兼容。大家可注意到三個(gè)f d _ s e t參數(shù):一個(gè)用于檢查可讀性(r e a d f d s),一個(gè)用于檢查可寫(xiě)性(w r i t e f d s),另一個(gè)用于例外數(shù)據(jù)(e x c e p t f d s)。從根本上說(shuō),f d _ s e t數(shù)據(jù)類(lèi)型代表著一系列特定套接字的集合。其中, r e a d f d s集合包括符合下述任何一個(gè)條件的套接字:
■ 有數(shù)據(jù)可以讀入。
■ 連接已經(jīng)關(guān)閉、重設(shè)或中止。
■ 假如已調(diào)用了l i s t e n,而且一個(gè)連接正在建立,那么a c c e p t函數(shù)調(diào)用會(huì)成功。
w r i t e f d s集合包括符合下述任何一個(gè)條件的套接字:
■ 有數(shù)據(jù)可以發(fā)出。
■ 如果已完成了對(duì)一個(gè)非鎖定連接調(diào)用的處理,連接就會(huì)成功。
最后,e x c e p t f d s集合包括符合下述任何一個(gè)條件的套接字:
■ 假如已完成了對(duì)一個(gè)非鎖定連接調(diào)用的處理,連接嘗試就會(huì)失敗。
■ 有帶外(O u t - o f - b a n d,O O B)數(shù)據(jù)可供讀取。
例如,假定我們想測(cè)試一個(gè)套接字是否“可讀”,必須將自己的套接字增添到r e a d f d s集合,
再等待s e l e c t函數(shù)完成。s e l e c t完成之后,必須判斷自己的套接字是否仍為r e a d f d s集合的一部分。若答案是肯定的,便表明該套接字“可讀”,可立即著手從它上面讀取數(shù)據(jù)。在三個(gè)參數(shù)中(r e a d f d s、w r i t e f d s和e x c e p t f d s),任何兩個(gè)都可以是空值( N U L L);但是,至少有一個(gè)不能為空值!在任何不為空的集合中,必須包含至少一個(gè)套接字句柄;否則, s e l e c t函數(shù)便沒(méi)有
任何東西可以等待。最后一個(gè)參數(shù)t i m e o u t對(duì)應(yīng)的是一個(gè)指針,它指向一個(gè)t i m e v a l結(jié)構(gòu),用于決定s e l e c t最多等待I / O操作完成多久的時(shí)間。如t i m e o u t是一個(gè)空指針,那么s e l e c t調(diào)用會(huì)無(wú)限期地“鎖定”或停頓下去,直到至少有一個(gè)描述符符合指定的條件后結(jié)束。對(duì)t i m e v a l結(jié)構(gòu)的
定義如下:
struct timeval
{
?long tv_sec;
?long tv_usec;
};
其中,t v _ s e c字段以秒為單位指定等待時(shí)間; t v _ u s e c字段則以毫秒為單位指定等待時(shí)間。
若將超時(shí)值設(shè)置為( 0 , 0),表明s e l e c t會(huì)立即返回,允許應(yīng)用程序?qū) e l e c t操作進(jìn)行“輪詢(xún)”。
出于對(duì)性能方面的考慮,應(yīng)避免這樣的設(shè)置。s e l e c t成功完成后,會(huì)在f d _ s e t結(jié)構(gòu)中,返回剛好有未完成的I / O操作的所有套接字句柄的總量。若超過(guò)t i m e v a l設(shè)定的時(shí)間,便會(huì)返回0。不管由于什么原因,假如s e l e c t調(diào)用失敗,都會(huì)返回S O C K E T _ E R R O R。
用s e l e c t對(duì)套接字進(jìn)行監(jiān)視之前,在自己的應(yīng)用程序中,必須將套接字句柄分配給一個(gè)集合,設(shè)置好一個(gè)或全部讀、寫(xiě)以及例外f d _ s e t結(jié)構(gòu)。將一個(gè)套接字分配給任何一個(gè)集合后,再來(lái)調(diào)用s e l e c t,便可知道一個(gè)套接字上是否正在發(fā)生上述的I / O活動(dòng)。Wi n s o c k提供了下列宏操作,可用來(lái)針對(duì)I / O活動(dòng),對(duì)f d _ s e t進(jìn)行處理與檢查:
■ FD_CLR(s, *set):從s e t中刪除套接字s。
■ FD_ISSET(s, *set):檢查s是否s e t集合的一名成員;如答案是肯定的是,則返回T R U E。
■ FD_SET(s, *set):將套接字s加入集合s e t。
■ F D _ Z E R O ( * s e t ):將s e t初始化成空集合。
例如,假定我們想知道是否可從一個(gè)套接字中安全地讀取數(shù)據(jù),同時(shí)不會(huì)陷于無(wú)休止的
“鎖定”狀態(tài),便可使用F D _ S E T宏,將自己的套接字分配給f d _ r e a d集合,再來(lái)調(diào)用s e l e c t。要想檢測(cè)自己的套接字是否仍屬f d _ r e a d集合的一部分,可使用F D _ I S S E T宏。采用下述步驟,便可完成用s e l e c t操作一個(gè)或多個(gè)套接字句柄的全過(guò)程:
1) 使用F D _ Z E R O宏,初始化自己感興趣的每一個(gè)f d _ s e t。
2) 使用F D _ S E T宏,將套接字句柄分配給自己感興趣的每個(gè)f d _ s e t。
3) 調(diào)用s e l e c t函數(shù),然后等待在指定的f d _ s e t集合中,I / O活動(dòng)設(shè)置好一個(gè)或多個(gè)套接字句柄。
s e l e c t完成后,會(huì)返回在所有f d _ s e t集合中設(shè)置的套接字句柄總數(shù),并對(duì)每個(gè)集合進(jìn)行相應(yīng)的更新。
4) 根據(jù)s e l e c t的返回值,我們的應(yīng)用程序便可判斷出哪些套接字存在著尚未完成(待決)的I / O操作—具體的方法是使用F D _ I S S E T宏,對(duì)每個(gè)f d _ s e t集合進(jìn)行檢查。
5) 知道了每個(gè)集合中“待決”的I / O操作之后,對(duì)I / O進(jìn)行處理,然后返回步驟1 ),繼續(xù)進(jìn)行s e l e c t處理。
s e l e c t返回后,它會(huì)修改每個(gè)f d _ s e t結(jié)構(gòu),刪除那些不存在待決I / O操作的套接字句柄。這正是我們?cè)谏鲜龅牟襟E( 4 )中,為何要使用F D _ I S S E T宏來(lái)判斷一個(gè)特定的套接字是否仍在集合中的原因。在程序清單8 - 4中,我們向大家闡述了為一個(gè)(只有一個(gè))套接字設(shè)置s e l e c t模型所需的一系列基本步驟。若想在這個(gè)應(yīng)用程序中添加更多的套接字,只需為額外的套接字維護(hù)它們的一個(gè)列表,或維護(hù)它們的一個(gè)數(shù)組即可。
程序清單8-4 用s e l e c t管理一個(gè)套接字上的I / O操作
SOCKET s;
fd_set fdread;
int ret;
//create a socket ,and accept a connection
...
//manage i/o on the socket
while(TRUE)
{
?//aways clear the read set before calling
?//select()
?FD_ZERO(&fdread);
?//add socket s to the read set
?FD_SET(s,&fdread);
?if((ret = select(0,&fdread,NULL,NULL,NULL))== SOCKET_ERROR)
?{
??//ERROR CONDITION
?}
?if(ret > 0)
?
?{
??//for this simple case,select() should return the value 1.
??//an appliation dealing with more than one socket could get a value greater than 1;
??//at this point,your appliation should check to see whether the socket is part of a set.
??if(FD_ISSET(s.&fdread))
??{
???//a read event has occurred on a socket s
??}
?}
}
??????