Windows I/O模型之一:Select模型

來源:互聯網
上載者:User

標籤:io   ar   os   使用   sp   for   strong   on   檔案   

1.概念理解

       在進行網路編程時,我們常常見到同步(Sync)/非同步(Async),阻塞(Block)/非阻塞(Unblock)

四種調用模式:

同步:所謂同步,就是在發出一個功能調用時,在沒有得到結果前,該調用就不返回。也就是必須一件

一件做事,等前一件做完了才能做另一件。

例如在C/S模式的某個流程中,你伺服器提交了某個請求,在伺服器處理完畢返回結果期間用戶端什麼

也不能做。

 

非同步:非同步概念和同步相對。當一個非同步程序呼叫發出後,調用者不會立刻得到結果。調用者在發出

調用後可以繼續做自己的事,被調用者通過狀態、通知來通知調用者,或者通過回呼函數處理這個調用。

 

阻塞:阻塞調用是指調用結果返回前,當前線程會被掛起(當前線程處於非可執行狀態,在這個狀態下,

CPU不會給線程分配時間片,即線程暫停運行),函數只有在得到結果後才回返回。

 

非阻塞:非阻塞和阻塞的概念相對,是指不能立刻得到借過前,該函數不會阻塞當前進程,而回立刻返回。

 

區別:有人會把同步和阻塞調用等同起來,實際上他們是不同的,對於同步調用來說,很多時候當前調用

還是啟用的,只是從邏輯上當前函數沒有返回而已。阻塞的話當前線程會被掛起。

 

2.Select模型的原理和使用步驟

select(選擇)模型是Winsock中最常見的I/O模型。之所以稱其為“ select模型”,是由於它的“中心思想”

便是利用select函數,實現對 I/O的管理!利用select函數,我們判斷通訊端上是否存在資料,或者能否向一

個通訊端寫入資料。之所以要設計這個函數,唯一的目的便是防止應用程式在通訊端處於鎖定模式中時,在

一次I/O綁定調用(如send或recv)過程中,被迫進入“鎖定”狀態;同時防止在通訊端處於非鎖定模式中時,

產生WSAEWOULDBLOCK錯誤。除非滿足事先用參數規定的條件,否則select函數會在進行I/O操作時鎖定。

select的函數原型如下:

int select (  int nfds,                             fd_set FAR * readfds,                 fd_set FAR * writefds,                fd_set FAR * exceptfds,               const struct timeval FAR * timeout  );

其中,第一個參數nfds會被忽略。之所以仍然要提供這個參數,只是為了保持與早期的Berkeley通訊端應用程

序的相容。大家可注意到三個 fd_set參數:一個用於檢查可讀性(readfds),一個用於檢查可寫性(writefds),

另一個用於例外資料( excepfds)。從根本上說,fdset資料類型代表著一系列特定通訊端的集合。其中,

readfds集合包括符合下述任何一個條件的通訊端:

■ 有資料可以讀入。
■ 串連已經關閉、重設或中止。
■ 假如已調用了listen,而且一個串連正在建立,那麼accept函數調用會成功。
writefds集合包括符合下述任何一個條件的通訊端:
■ 有資料可以發出。
■ 如果已完成了對一個非鎖定串連調用的處理,串連就會成功。
最後,exceptfds集合包括符合下述任何一個條件的通訊端:
■ 假如已完成了對一個非鎖定串連調用的處理,串連嘗試就會失敗。
■ 有帶外(out-of-band,OOB)資料可供讀取。

例如,假定我們想測試一個通訊端是否“可讀”,必須將自己的通訊端增添到readfds集合,再等待select函數

完成。select完成之後,必須判斷自己的通訊端是否仍為readfds集合的一部分。若答案是肯定的,便表明該套

接字“可讀”,可立即著手從它上面讀取資料。在三個參數中(readfds、writedfss和exceptfds),任何兩個都

可以是空值(NULL);但是,至少有一個不可為空值!在任何不為空白的集合中,必須包含至少一個通訊端控制代碼;

否則, select函數便沒有任何東西可以等待。最後一個參數timeout對應的是一個指標,它指向一個timeval結構,

用於決定select最多等待 I / O操作完成多久的時間。如 timeout是一個null 指標,那麼select調用會無限期地“鎖定”

或停頓下去,直到至少有一個描述符符合指定的條件後結束。對timeval結構的定義如下:

struct timeval {
long tv_sec;
long tv_usec;

} ;

若將逾時值設定為(0,0),表明select會立即返回,允許應用程式對 select操作進行“輪詢”。出於對效能方面

的考慮,應避免這樣的設定。select成功完成後,會在 fd_set結構中,返回剛好有未完成的I/O操作的所有通訊端

控制代碼的總量。若超過timeval設定的時間,便會返回0。不管由於什麼原因,假如select調用失敗,都會返回SOCKET_ERROR。

用select對通訊端進行監視之前,在自己的應用程式中,必須將通訊端控制代碼分配給一個集合,設定好一個或全部

讀、寫以及例外 fd_set結構。將一個通訊端分配給任何一個集合後,再來調用select,便可知道一個通訊端上是

否正在發生上述的I/O活動。Winsock提供了下列宏操作,可用來針對I/O活動,對 fd_set進行處理與檢查:

■ 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初始化成空集合。

例如,假定我們想知道是否可從一個通訊端中安全地讀取資料,同時不會陷於無休止的“鎖定”狀態,便可使用

FD_SET宏,將自己的通訊端分配給fd_set集合,再來調用select。要想檢測自己的通訊端是否仍屬 fd_read集合

的一部分,可使用FD_ISSET宏。採用下述步驟,便可完成用select操作一個或多個通訊端控制代碼的全過程:

1) 使用FD_ZERO宏,初始化自己感興趣的每一個fd_set。
2) 使用FD_SET宏,將通訊端控制代碼分配給自己感興趣的每個fd_set。
3) 調用select函數,然後等待在指定的fd_set集合中,I/O活動設定好一個或多個通訊端控制代碼。
select完成後,會返回在所有fd_set集合中設定的通訊端控制代碼總數,並對每個集合進行相應的更新。
4) 根據select的傳回值,我們的應用程式便可判斷出哪些通訊端存在著尚未完成(待決)
的I/O操作—具體的方法是使用FD_ISSET宏,對每個fd_set集合進行檢查。
5) 知道了每個集合中“待決”的I/O操作之後,對I/O進行處理,然後返回步驟 1 ),繼續進

select返回後,它會修改每個fd_set結構,刪除那些不存在待決 I / O操作的通訊端控制代碼。這正是我們在上述的步

驟 ( 4 )中,為何要使用FD_ISSET宏來判斷一個特定的通訊端是否仍在集合中的原因。

 

3.參考代碼

// Select.cpp : 定義控制台應用程式的進入點。
//

#include "stdafx.h"
#include <WinSock2.h>
#include <iostream>
using namespace std;

#include <stdio.h>

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

#define PORT 8000
#define MSGSIZE 255
#define SRV_IP "127.0.0.1"

int g_nSockConn = 0;//請求串連的數目

//FD_SETSIZE是在winsocket2.h標頭檔裡定義的,這裡windows預設最大為64
//在包含winsocket2.h標頭檔前使用宏定義可以修改這個值


struct ClientInfo
{
    SOCKET sockClient;
    SOCKADDR_IN addrClient;
};

ClientInfo g_Client[FD_SETSIZE];

DWORD WINAPI WorkThread(LPVOID lpParameter);

int _tmain(int argc, _TCHAR* argv[])
{//基本步驟就不解釋了,網路編程基礎那篇部落格裡講的很詳細了
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2,2),&wsaData);

    SOCKET sockListen = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);

    SOCKADDR_IN addrSrv;
    addrSrv.sin_addr.S_un.S_addr = inet_addr(SRV_IP);
    addrSrv.sin_family = AF_INET;
    addrSrv.sin_port = htons(PORT);

    bind(sockListen,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));

    listen(sockListen,64);

    DWORD dwThreadIDRecv = 0;
    DWORD dwThreadIDWrite = 0;

    HANDLE hand = CreateThread(NULL,0, WorkThread,NULL,0,&dwThreadIDRecv);//用來處理手法訊息的進程
    if (hand == NULL)
    {
        cout<<"Create work thread failed\n";
        getchar();
        return -1;
    }

    SOCKET sockClient;
    SOCKADDR_IN addrClient;
    int nLenAddrClient = sizeof(SOCKADDR);//這裡用0初試化找了半天才找出錯誤

    while (true)
    {
        sockClient = accept(sockListen,(SOCKADDR*)&addrClient,&nLenAddrClient);//第三個參數一定要按照addrClient大小初始化
        //輸出串連者的地址資訊
        //cout<<inet_ntoa(addrClient.sin_addr)<<":"<<ntohs(addrClient.sin_port)<<"has connect !"<<endl;

        if (sockClient != INVALID_SOCKET)
        {
            g_Client[g_nSockConn].addrClient = addrClient;//儲存串連端地址資訊
            g_Client[g_nSockConn].sockClient = sockClient;//加入串連者隊列
            g_nSockConn++;
        }


    }

    closesocket(sockListen);
    WSACleanup();

    return 0;
}

DWORD WINAPI WorkThread(LPVOID lpParameter)
{
    FD_SET fdRead;
    int nRet = 0;//記錄發送或者接受的位元組數
    TIMEVAL tv;//設定逾時等待時間
    tv.tv_sec = 1;
    tv.tv_usec = 0;
    char buf[MSGSIZE] = "";

    while (true)
    {
        FD_ZERO(&fdRead);
        for (int i = 0;i < g_nSockConn;i++)
        {
            FD_SET(g_Client[i].sockClient,&fdRead);
        }

        //只處理read事件,不過後面還是會有讀寫訊息發送的
        nRet = select(0,&fdRead,NULL,NULL,&tv);

        if (nRet == 0)
        {//沒有串連或者沒有讀事件
            continue;
        }

        for (int i = 0;i < g_nSockConn;i++)
        {
            if (FD_ISSET(g_Client[i].sockClient,&fdRead))
            {
                nRet = recv(g_Client[i].sockClient,buf,sizeof(buf),0);

                if (nRet == 0 || (nRet == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))
                {
                    cout<<"Client "<<inet_ntoa(g_Client[i].addrClient.sin_addr)<<"closed"<<endl;
                    closesocket(g_Client[i].sockClient);

                    if (i < g_nSockConn-1)
                    {
                        //將失效的sockClient剔除,用數組的最後一個補上去
                        g_Client[i--].sockClient = g_Client[--g_nSockConn].sockClient;
                    }
                }
                else
                {
                    cout<<inet_ntoa(g_Client[i].addrClient.sin_addr)<<": "<<endl;
                    cout<<buf<<endl;
                    cout<<"Server:"<<endl;
                    //gets(buf);
                    strcpy(buf,"Hello!");
                    nRet = send(g_Client[i].sockClient,buf,strlen(buf)+1,0);
                }
            }
        }
    }
    return 0;
}

Windows I/O模型之一:Select模型

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.