天天看點

【Windows程式設計】-I/O模型-01.Select模型實作

1.傳統阻塞的Socket中存在的問題

  1. 每個Socket連接配接在Recv過程中,存在無資料一直阻塞問題;

    (當然,這個可以通過ioctlsocket()函數修改Socket屬性為非阻塞,但會産生WSAEWOULDBLOCK錯誤。)

  2. 多個Socket管理不友善問題,不能在一個線程中同時監視多個Socket;

    (注意,我們可以通過為每個Socket都建立一個線程的辦法實作同步監視(即一Socket一線程的方法),但這樣過于消耗系統資源。)

基于傳統的Socket中存在的問題,我們就要就要設計出相應的解決方案。在Windows下,主要有以下5種I/O模型:

  1. Select模型;
  2. 異步選擇(WSAAsyncSelect)模型;
  3. 事件選擇(WSAEventSelect)模型;
  4. 重疊I/O(Overlapped I/O);
  5. 完成端口(Completion Port);

本文主要介紹最基本的Select模型。

2.Select模型設計思路

【Windows程式設計】-I/O模型-01.Select模型實作

Select模型是Winsock中最常見的 I/O模型。核心便是利用 select 函數,實作對 I/O的管理。

每次調用Select之前,需将需要監聽的Socket放入一個fd_set結構中,利用 select 函數來判斷某Socket上是否有資料可讀/可寫,其(Select)内部通過輪詢的方式,實作了同時等待多個套接字,當某個或者多個套接字滿足可讀寫條件時,Select函數傳回。這樣,同時防止了在套接字處于非阻塞模式中時,産生WSAEWOULDBLOCK錯誤。

//fd_set 結構的定義如下:
typedef struct fd_set 
{ 
    u_int fd_count;
    SOCKET fd_array[FD_SETSIZE];  // 其實是将Socket放入數組中
} fd_set;
           
// Select模型實作 - 核心代碼如下:
//(因與Linux下的Select相似,此處直接借用Linux下的C代碼)
// C語言代碼實作
    while ()
    {
        // 定義fd_set集合
        fd_set client;
        FD_ZERO(&client);

        /* fill client 将所有需要監視的Socket填充進fd_set集合(内含Socket數組)
         * 變量說明:client_count為client_set中所包含的Socket數量。
         * 變量說明:client_set數組中是用戶端已連接配接的Socket。
         */
        for (i = ; i < *(p->client_count);++i)
        {
            FD_SET(p->client_set[i] , &client);  
        }

        /* call select
         * 如果有Socket發生了事件,就将那些沒有發生事件的Socket從fd_set集合中清除 (注意這裡是把集合的位址傳入的 &client)
         *
         * Q:Select内部是如何實作這種監視的?是通過Socket主動發送信号相應信号麼?還是輪詢?
         *  這裡留了這麼一個疑問,如果哪位夥伴知道,請在下方留言 ~~ 感激 ~~
         */

        ret = select(get_max(p->client_set , *(p->client_count)) + , &client , NULL , NULL , &time_val);   
        if ( SOCKET_ERROR == ret)
        {
            if (errno == EINVAL)
            {
                usleep();
                continue;
            }
            printf("select fail![%s]\n" , strerror(errno));
            break;
        }

        // check 檢視傳回的集合中,有哪些Socket還在,就說明有關于ta的事件發生。
        for (i =  ; i < *(p->client_count); ++i)
        {
            if (FD_ISSET(p->client_set[i] , &client) > )
            {
                // recv
                ret = recv(p->client_set[i] , buf , sizeof(buf) , );
                if (DEF_STD_ERROR == ret ||  == ret)
                {
                    // TODO:clear current client
                    continue;
                }
                printf("recv data:%s\n" , buf);
            }
        }

        // 處理完畢,下次循環将會重新填充fd_set集合,并再次輪詢。
    }
           

select 函數傳回值:

select 成功完成後,會在 fdset 結構中,傳回剛好有未完成的 I/O操作的所有套接字句柄的總量。

若超過 timeval 設定的時間,便會傳回0。若 select 調用失敗,都會傳回 SOCKET_ERROR,應該調用 WSAGetLastError 擷取錯誤碼!

用 select 對套接字進行監視之前,必須将套接字句柄配置設定給一個fdset的結構集合,之後再來調用 select,便可知道一個套接字上是否正在發生上述的 I/O 活動。

Winsock 提供了下列宏操作,可用來針對 I/O活動,對 fdset 進行處理與檢查:

● FD_CLR(s, *set):從set中删除套接字s。

● FD_ISSET(s, *set):檢查s是否set集合的一名成員;如答案是肯定的是,則傳回TRUE。

● FD_SET(s, *set):将套接字s加入集合set。

● FD_ZERO( * set):将set初始化成空集合。

3.select模式的優勢和不足

優勢:

1.可以同時對多個建立起來的套接字進行有序的管理。

不足:

1.每次調用Select之前都需要将所有Socket放入fd_set結構集合中,這樣每次都需要周遊一次數組,但可能每次這個數組都沒有變,或隻有少部分變化;

2.Select函數傳回時,并不知道是哪個Socket發生了事件,是以需要再次周遊Socket數組,找到發生事件的Socket,這個過程中會對沒有發生事件的Socket也進行判斷,而這個判斷是無效的;

(是以,一次Select過程需要輪詢周遊兩次Socket數組,這樣大大降低了CPU的有效效率。)

PS:以上内容如有不足,請多多指正! –netosoul

繼續閱讀