select網絡模型知識總結


select模型支持IO多路復用,select函數如下

int select (
 IN int nfds,                           //windows下無意義,linux有意義
 IN OUT fd_set* readfds,      //檢查可讀性
 IN OUT fd_set* writefds,     //檢查可寫性
 IN OUT fd_set* exceptfds,  //例外數據
 IN const struct timeval* timeout);    //函數的返回時間

逐個解釋每個參數意義:

nfds:一個整型變量,表示比最大文件描述符+1

readfds: 這個集合監測讀事件的描述符,將要監聽

讀事件的文件描述符放入readfds中,通過調用select,

readfds中將沒有就緒的讀事件文件描述符清除,留下

就緒的讀事件描述符,可以通過read或者recv來處理

writefds:這個集合監測寫事件的描述符,將要監聽的

寫事件的文件描述符放入writefds中,通過調用select,

writefds中沒有就緒的寫事件文件描述符被清除,留下

就緒的寫事件描述符,可以通過write或者send來處理。

execptfds:這個集合在調用select后會存有錯誤的文件

描述符。根據Linux網絡網絡編程第二版中介紹,可以

監視帶外數據OOB,帶外數據使用MSG_OOB標志發送

到套接字上,當select()函數返回的時候,readfds將清除

其中的其他文件描述符,留下OOB數據

函數返回值:

當返回0時表示超時,-1表示有錯誤,大於0表示沒有錯誤。

當監視文件集中有文件描述符符合要求,即讀文件描述符集

合中有文件可讀,寫文件描述符集合中有文件可寫,或者

錯誤文件描述符集合中有錯誤的描述符,都會返回大於0的數。

 

timeval結構體解釋

struct  timeval {
        long    tv_sec;        //
        long    tv_usec;     //毫秒
};

 

timeval指針為NULL,表示一直等待,直到有符合條件的描述符

觸發select返回

如果timeval中個參數均為0,表示立即返回,否則在select沒有

符合條件的描述符,等待對應的時間和,然后返回。

 

 另外需要了解一些select的操作宏函數

fd_set是一個SOCKET隊列,以下宏可以對該隊列進行操作:
FD_CLR( s, fd_set *set) 從隊列set刪除句柄s;
FD_ISSET( s,fd_set *set) 檢查句柄s是否存在與隊列set中;
FD_SET( s, fd_set *set )把句柄s添加到隊列set中;
FD_ZERO( fd_set *set ) 把set隊列初始化成空隊列.

看一個select的使用示例

//前面是服務器socket的創建,綁定和監聽

int  listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);     
 // Bind     
  
 local.sin_family = AF_INET;     
 local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);     
 local.sin_port = htons(PORT);     
 bind(listenSocket, (sockaddr*)&local, sizeof(SOCKADDR_IN));     
  
 // Listen     
  
 listen(listenSocket, 3);
 //設置非阻塞
 fcntl(listenSocket, F_SETFL, O_NONBLOCK );
 
 //這個使用來統計所有已經連接上來的文件描述符,
 //也可以用數組表示,只是遍歷的時候要根據數據結構進行更改
FD_SET socketSet;  
FD_SET writeSet;  
FD_SET readSet;
 
//清空 socketSet
FD_ZERO(&socketSet);
//將文件描述符放入 socketSet,
//用於accept  
FD_SET(listenSocket,&socketSet); 
//統計最大的socket 
int maxfd = listenSocket;
int conNum = 1;
//數組存儲連接的socket
int connectArray[1024]={0};
while(true)  
{  
    //清空讀寫集合
    FD_ZERO(&readSet);  
    FD_ZERO(&writeSet);  
    //讀寫都監聽
    readSet=socketSet;  
    writeSet=socketSet;  
  
    //同時檢查套接字的可讀可寫性。 
    //為等待時間傳入NULL,則永久等待。傳入0立即返回。不要勿用。
    int ret=select(maxfd,&readSet,&writeSet,NULL,NULL);  
    if(ret==-1)  
    {  
        return false;  
    }  
    sockaddr_in addr;  
    int len=sizeof(addr);  
    //是否存在客戶端的連接請求。  
    //在readset中會返回已經調用過listen的套接字
    if(FD_ISSET(listenSocket,&readSet))
    {  
        acceptSocket=accept(listenSocket,(sockaddr*)&addr,&len);  
        if(acceptSocket==INVALID_SOCKET)  
        {  
            return false;  
        }  
        else  
        {  
            //大於我們最大的監聽數量了
            if(conNum > 1024)
            {
                return false;
            }
            //更新數組
            connectArray[conNum] = acceptSocket;
            //設置非阻塞
            fcntl(connectArray[conNum], F_SETFL, O_NONBLOCK );
            //加到socketset里,以后賦值給讀寫集合
            FD_SET(connectArray[conNum],&socketSet);
            if(acceptSocket > maxfd)
            {
                maxfd = acceptSocket;
            }
            conNum++;
        }  
    }  
  
    for(int i=0;i<conNum;i++)  
    {  
        //判斷是否有讀事件
       if(FD_ISSET(connectArray[i],&readSet))  
        {  
            //調用recv,接收數據。  
            //判斷recv結果,為0則客戶端斷開,
            //那么調用FD_CLR並關閉對應的socket
        }  
        //判斷是否有寫事件
        if(FD_ISSET(connectArray[i],&writeSet)  
        {  
            //調用send,發送數據。
            //判斷send結果,為0客戶端斷開,
            //那么調用FD_CLR並關閉對應的socket    
        }  
    }  
}  
 

 

上面的例子結合了網上提供的一些demo,其實writeSet不一定要放入socket,當某個socket需要send內容時

再調用FD_SET(socket,&writeSet),寫成功后再調用FD_CLR(socket,&writeSet);避免造成busyloop,

因為當緩沖區非空時,寫事件是一直就緒的。

我的微信公眾號,定期推送一些技術總結,一起努力吧。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM