io/select模型

爲什麼會出現select模型?

先看一下下面的這句代碼:
int iResult = recv(s, buffer,1024);
這是用來接收數據的,在默認的阻塞模式下的套接字裏,recv會阻塞在那裏,直到套接字連接上有數據可讀,把數據讀到buffer裏後recv函數纔會返回,不然就會一直阻塞在那裏。在單線程的程序裏出現這種情況會導致主線程(單線程程序裏只有一個默認的主線程)被阻塞,這樣整個程序被鎖死在這裏,如果永遠沒數據發送過來,那麼程序就會被永遠鎖死。這個問題可以用多線程解決,但是在有多個套接字連接的情況下,這不是一個好的選擇,擴展性很差。
再看代碼:
int iResult = ioctlsocket(s, FIOBIO, (unsigned long *)&ul);
iResult = recv(s, buffer,1024);

這一次recv的調用不管套接字連接上有沒有數據可以接收都會馬上返回。原因就在於我們用ioctlsocket把套接字設置爲非阻塞模式了。不過你跟蹤一下就會發現,在沒有數據的情況下,recv確實是馬上返回了,但是也返回了一個錯誤:WSAEWOULDBLOCK,意思就是請求的操作沒有成功完成。看到這裏很多人可能會說,那麼就重複調用recv並檢查返回值,直到成功爲止,但是這樣做效率很成問題,開銷太大。

select模型的出現就是爲了解決上述問題。

select模型的關鍵是使用一種有序的方式,對多個套接字進行統一管理與調度

看核心代碼:(這裏只給出服務端的)

while ( 1 )
{
// 初始化fdset
FD_ZERO( &fdsRead );

// 將server套接字添加到可讀集合中
FD_SET( sockServer, &fdsRead );

// 調用select
select( 0, &fdsRead, NULL, NULL, &tv );

// 判斷server套接字的狀態,如果套接字還在可讀集合中,
// 說明有數據可以讀入,則建立套接字可以成功
if ( FD_ISSET( sockServer, &fdsRead ) )
{
sockAccept = accept( sockServer, (sockaddr*)&addr, &nLen );
// 有數據可讀,進行相關處理
}

當然了,這裏演示的只是最基礎的select的用法。網絡通信中的I/O複用的相關問題還很多,還需要慢慢學習與深入。

 

 

 

Select模型

講一下套接字模式和套接字I/O模型的區別。先說明一下,只針對Winsock,如果你要骨頭裏挑雞蛋把UNIX下的套接字概念來往這裏套,那就不關我的事。
套接字模式:阻塞套接字和非阻塞套接字。或者叫同步套接字和異步套接字。
套接字模型:描述如何對套接字的I/O行爲進行管理。
Winsock提供的I/O模型一共有五種:
select,WSAAsyncSelect,WSAEventSelect,Overlapped,Completion。今天先講解select。

1:select模型(選擇模型)
先看一下下面的這句代碼:
int iResult = recv(s, buffer,1024);
這是用來接收數據的,在默認的阻塞模式下的套接字裏,recv會阻塞在那裏,直到套接字連接上有數據可讀,把數據讀到buffer裏後recv函數纔會返回,不然就會一直阻塞在那裏。在單線程的程序裏出現這種情況會導致主線程(單線程程序裏只有一個默認的主線程)被阻塞,這樣整個程序被鎖死在這裏,如果永遠沒數據發送過來,那麼程序就會被永遠鎖死。這個問題可以用多線程解決,但是在有多個套接字連接的情況下,這不是一個好的選擇,擴展性很差。Select模型就是爲了解決這個問題而出現的。
再看代碼:

int iResult = ioctlsocket(s, FIOBIO, (unsigned long *)&ul);
iResult = recv(s, buffer,1024);

這一次recv的調用不管套接字連接上有沒有數據可以接收都會馬上返回。原因就在於我們用ioctlsocket把套接字設置爲非阻塞模式了。不過你跟蹤一下就會發現,在沒有數據的情況下,recv確實是馬上返回了,但是也返回了一個錯誤:WSAEWOULDBLOCK,意思就是請求的操作沒有成功完成。看到這裏很多人可能會說,那麼就重複調用recv並檢查返回值,直到成功爲止,但是這樣做效率很成問題,開銷太大。
感謝天才的微軟工程師吧,他們給我們提供了好的解決辦法。
先看看select函數
int select(
int nfds,
fd_set FAR *readfds,
fd_set FAR *writefds,
fd_set FAR *exceptfds,
const struct timeval FAR *timeout
);
第一個參數不要管,會被系統忽略的。第二個參數是用來檢查套接字可讀性,也就說檢查套接字上是否有數據可讀,同樣,第三個參數用來檢查數據是否可以發出。最後一個是檢查是否有帶外數據可讀取。
參數詳細的意思請去看MSDN,這裏限於篇幅不詳細解釋了。
最後一個參數是用來設置select等待多久的,是個結構:


struct timeval {
long tv_sec; // seconds
long tv_usec; // and microseconds
};
如果將這個結構設置爲(0,0),那麼select函數會馬上返回。
說了這麼久,select的作用到底是什麼?
他的作用就是:防止在在阻塞模式的套接字裏被鎖死,避免在非阻塞套接字裏重複檢查WSAEWOULDBLOCK錯誤。
他的工作流程如下:
1:用FD_ZERO宏來初始化我們感興趣的fd_set,也就是select函數的第二三四個參數。
2:用FD_SET宏來將套接字句柄分配給相應的fd_set。
3:調用select函數。
4:用FD_ISSET對套接字句柄進行檢查,如果我們所關注的那個套接字句柄仍然在開始分配的那個fd_set裏,那麼說明馬上可以進行相應的IO操作。比如一個分配給select第一個參數的套接字句柄在select返回後仍然在select第一個參數的fd_set裏,那麼說明當前數據已經來了,馬上可以讀取成功而不會被阻塞。

下面給出一個簡單的select模型的服務端套接字。

#include “iostream.h”
#include “winsock2.h”
#include “windows.h”


#define InternetAddr "127.0.0.1"
#define iPort 5055

#pragma comment(lib, "ws2_32.lib")


void main()
{
    WSADATA wsa;
    WSAStartup(MAKEWORD(2,2), &wsa);
    
    SOCKET fdServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = inet_addr(InternetAddr);
    server.sin_port = htons(iPort);
    
    int ret = bind(fdServer, (sockaddr*)&server, sizeof(server));
    ret = listen(fdServer, 4);


    SOCKET AcceptSocket;
    fd_set fdread;
    timeval tv;
    int nSize;

    while(1)
    {
        
        FD_ZERO(&fdread);//初始化fd_set
        FD_SET(fdServer, &fdread);//分配套接字句柄到相應的fd_set
        
        
        tv.tv_sec = 2;//這裏我們打算讓select等待兩秒後返回,避免被鎖死,也避免馬上返回
        tv.tv_usec = 0;
        
        select(0, &fdread, NULL, NULL, &tv);
        
        nSize = sizeof(server);
        if (FD_ISSET(fdServer, &fdread))//如果套接字句柄還在fd_set裏,說明客戶端已經有connect的請求發過來了,馬上可以accept成功
        {
            AcceptSocket = accept(fdServer,( sockaddr*) &server, &nSize);
            break;
        }
        
        else//還沒有客戶端的connect請求,我們可以去做別的事,避免像沒有用select方式的阻塞套接字程序被鎖死的情況,如果沒用select,當程序運行到accept的時候客戶端恰好沒有connect請求,那麼程序就會被鎖死,做不了任何事情
        {
            //do something
            ::MessageBox(NULL, "waiting...", "recv", MB_ICONINFORMATION);//別的事做完後,繼續去檢查是否有客戶端連接請求
        }
    }

    char buffer[128];
    ZeroMemory(buffer, 128);

    ret = recv(AcceptSocket,buffer,128,0);//這裏同樣可以用select,用法和上面一樣

    ::MessageBox(NULL, buffer, "recv", MB_ICONINFORMATION);

    closesocket(AcceptSocket);
    WSACleanup();
    
    return;

 

 

 io/select模型

#include<stdio.h>
#include<winsock2.h>
#pragma comment(lib,"WS2_32")

int main()
{
 WSADATA wsaData;
 WORD sockVersion=MAKEWORD(2,2);
 if(::WSAStartup(sockVersion,&wsaData)!=0)
 {
   exit(0);
 }

 SOCKET sListen=::socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
 sockaddr_in sin;
 sin.sin_port=htons(4567);
 sin.sin_family=AF_INET;
 sin.sin_addr.S_un.S_addr=INADDR_ANY;
 if(::bind(sListen,(sockaddr*)&sin,sizeof(sin))==SOCKET_ERROR)
 {
  printf("Failed bind()/n");
  return -1;
 }
 if(::listen(sListen,5))
 {
  printf("sListen listening......");
 }
 fd_set fdSocket;
 FD_ZERO(&fdSocket);
 FD_SET(sListen,&fdSocket);
 while(true)
 {
  fd_set fdRead=fdSocket;
  int nRet=::select(0,&fdRead,NULL,NULL,NULL);
  if(nRet>0)
  {
   for(int i=0;i<(int)fdSocket.fd_count;i++)
   {
    if(FD_ISSET(fdSocket.fd_array[i],&fdRead))
    {
     if(fdSocket.fd_array[i]==sListen)
     {
      if(fdSocket.fd_count<FD_SETSIZE)
      {
       sockaddr_in addrRemote;
       int nAddrLen=sizeof(addrRemote);
       SOCKET sNew=::accept(sListen,(SOCKADDR*)&addrRemote,&nAddrLen);
       FD_SET(sNew,&fdSocket);/*爲什麼這裏要把sNew添加到fdSocket中?*/
       printf("Accept connected from(%s)/n",::inet_ntoa(addrRemote.sin_addr));

      }
      else
      {
       printf("Too much connections!/n");
       continue;
      }
     }
     else
     {
      char szText[256];
      int nRecv=::recv(fdSocket.fd_array[i],szText,strlen(szText),0);
      if(nRecv>0)
      {
       szText[nRecv]='/0';
       printf("accept data:%s/n",szText);
      }
      else
      {
       ::closesocket(fdSocket.fd_array[i]);
       FD_CLR(fdSocket.fd_array[i],&fdSocket);
      }
     }
    }
   }
 
  }
  else
  {
   printf("Failed select()/n");
   break;
  }
 }
 return 0;
}

誰能幫忙分析一下select()函數嗎?
謝謝...
 
發帖:2009-11-10 13:14:00   鮮花(0)  雞蛋(0)  
 
 獨孤劍聖   
 
 
  等 級:初級劍客
  積 分:17726
  專家分:11614
  提問帖:1/1
  回答帖:806
  總帖數:890
  經驗值:1024
  注 冊:2007-11-25
       第2樓
 

 sock中select的用法
Select在Socket編程中還是比較重要的,可是對於初學Socket的人來說都不太愛用Select寫程序,他們只是習慣寫諸如connect、accept、recv或recvfrom這樣的阻塞程序(所謂阻塞方式 block,顧名思義,就是進程或是線程執行到這些函數時必須等待某個事件的發生,如果事件沒有發生,進程或線程就被阻塞,函數不能立即返回)。可是使用 Select就可以完成非阻塞(所謂非阻塞方式non-block,就是進程或線程執行此函數時不必非要等待事件的發生,一旦執行肯定返回,以返回值的不同來反映函數的執行情況,如果事件發生則與阻塞方式相同,若事件沒有發生則返回一個代碼來告知事件未發生,而進程或線程繼續執行,所以效率較高)方式工作的程序,它能夠監視我們需要監視的文件描述符的變化情況——讀寫或是異常。下面詳細介紹一下!

Select的函數格式(我所說的是Unix系統下的伯克利socket編程,和windows下的有區別,一會兒說明):

int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);

先說明兩個結構體:

第 一,struct fd_set可以理解爲一個集合,這個集合中存放的是文件描述符(file descriptor),即文件句柄,這可以是我們所說的普通意義的文件,當然Unix下任何設備、管道、FIFO等都是文件形式,全部包括在內,所以毫無疑問一個socket就是一個文件,socket句柄就是一個文件描述符。fd_set集合可以通過一些宏由人爲來操作,比如清空集合 FD_ZERO(fd_set *),將一個給定的文件描述符加入集合之中FD_SET(int ,fd_set *),將一個給定的文件描述符從集合中刪除FD_CLR(int ,fd_set*),檢查集合中指定的文件描述符是否可以讀寫FD_ISSET(int ,fd_set* )。一會兒舉例說明。

第二,struct timeval是一個大家常用的結構,用來代表時間值,有兩個成員,一個是秒數,另一個是毫秒數。

具體解釋select的參數:

int maxfdp是一個整數值,是指集合中所有文件描述符的範圍,即所有文件描述符的最大值加1,不能錯!在Windows中這個參數的值無所謂,可以設置不正確。

fd_set *readfds是指向fd_set結構的指針,這個集合中應該包括文件描述符,我們是要監視這些文件描述符的讀變化的,即我們關心是否可以從這些文件中讀取數據了,如果這個集合中有一個文件可讀,select就會返回一個大於0的值,表示有文件可讀,如果沒有可讀的文件,則根據timeout參數再判斷是否超時,若超出timeout的時間,select返回0,若發生錯誤返回負值。可以傳入NULL值,表示不關心任何文件的讀變化。

fd_set *writefds是指向fd_set結構的指針,這個集合中應該包括文件描述符,我們是要監視這些文件描述符的寫變化的,即我們關心是否可以向這些文件中寫入數據了,如果這個集合中有一個文件可寫,select就會返回一個大於0的值,表示有文件可寫,如果沒有可寫的文件,則根據timeout參數再判斷是否超時,若超出timeout的時間,select返回0,若發生錯誤返回負值。可以傳入NULL值,表示不關心任何文件的寫變化。

fd_set *errorfds同上面兩個參數的意圖,用來監視文件錯誤異常。

struct timeval* timeout是select的超時時間,這個參數至關重要,它可以使select處於三種狀態,第一,若將NULL以形參傳入,即不傳入時間結構,就是將select置於阻塞狀態,一定等到監視文件描述符集合中某個文件描述符發生變化爲止;第二,若將時間值設爲0秒0毫秒,就變成一個純粹的非阻塞函數,不管文件描述符是否有變化,都立刻返回繼續執行,文件無變化返回0,有變化返回一個正值;第三,timeout的值大於0,這就是等待的超時時間,即 select在timeout時間內阻塞,超時時間之內有事件到來就返回了,否則在超時後不管怎樣一定返回,返回值同上述。

返回值:

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

在有了select後可以寫出像樣的網絡程序來!舉個簡單的例子,就是從網絡上接受數據寫入一個文件中。

例子:

main()

{

int sock;

FILE *fp;

struct fd_set fds;

struct timeval timeout={3,0}; //select等待3秒,3秒輪詢,要非阻塞就置0

char buffer[256]={0}; //256字節的接收緩衝區

 

while(1)

{

FD_ZERO(&fds); //每次循環都要清空集合,否則不能檢測描述符變化

FD_SET(sock,&fds); //添加描述符

FD_SET(fp,&fds); //同上

maxfdp=sock>fp?sock+1:fp+1; //描述符最大值加1

switch(select(maxfdp,&fds,&fds,NULL,&timeout)) //select使用

{

case -1: exit(-1);break; //select錯誤,退出程序

case 0:break; //再次輪詢

default:

if(FD_ISSET(sock,&fds)) //測試sock是否可讀,即是否網絡上有數據

{

recvfrom(sock,buffer,256,.....);//接受網絡數據

if(FD_ISSET(fp,&fds)) //測試文件是否可寫

fwrite(fp,buffer...);//寫入文件

buffer清空;

}// end if break;

}// end switch

}//end while

}//end main

 

--------------------------------------------------------------------------------
 

來源: http://www.programbbs.com/bbs/view35-24507-1.htm

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