WinSock重疊IO

完成例程和完成端口

注:重疊IO有三種實(shí)現(xiàn)方式:完成事件瞧剖、完成例程、完成端口可免,完成事件和WSAEventSelect模型差不多抓于,也都限制于64個(gè)事件(可用多個(gè)線程擴(kuò)展),這里就不再敘述

項(xiàng)目地址
1.完成例程和完成端口都是重疊IO模型浇借,它們都是異步模型捉撮。
用一個(gè)形象的比喻:打印店打印紙張。同步非阻塞模型(例如Select)是輪到你了就叫你過來妇垢,其他時(shí)間你可以去干別的事情巾遭。異步模型是店員幫你打印好了再叫你過來,其他時(shí)間任你安排闯估。

2.不同點(diǎn):
2.1完成例程需要自己控制線程的執(zhí)行順序灼舍,而完成端口是由操作系統(tǒng)幫你完成。

完成例程的流程一般是:
2.1.1使用一個(gè)線程專門用于接收連接(WSAAccept)涨薪,WSAAccept是阻塞函數(shù)骑素。

2.1.2接收到Socket之后開啟一個(gè)線程開始接收數(shù)據(jù)(WSARecv)。完成例程一般使用SleepEx函數(shù)休眠刚夺,以等待OS執(zhí)行完畢献丑,完成事件使用WSAWaitForMultipleEvents函數(shù)。這里使用線程池來減少創(chuàng)建銷毀線程的次數(shù)侠姑。

2.2完成端口不需要自己管理線程的執(zhí)行順序创橄。
2.2.1完成端口首先需要使用CreateIoCompletionPort函數(shù)創(chuàng)建一個(gè)CompletionPort,F(xiàn)ileHandle參數(shù)可以為文件句柄或者Socket莽红,這里首次創(chuàng)建時(shí)使用INVALID_HANDLE_VALUE妥畏。調(diào)用該函數(shù)操作系統(tǒng)會(huì)將該設(shè)備句柄添加到設(shè)備列表。返回的句柄需要保存,接下來會(huì)使用到咖熟。

2.2.2接下來創(chuàng)建一些工作線程圃酵,一般為cput的兩倍即可,CreateIoCompletionPort最后一個(gè)參數(shù)可以指定線程數(shù)馍管,默認(rèn)為cpu數(shù)郭赐。每個(gè)線程都是一個(gè)死循環(huán),在循環(huán)內(nèi)第一條語句調(diào)用GetQueuedCompletionStatus函數(shù)确沸,該函數(shù)會(huì)讓操作系統(tǒng)將該線程壓入到等待線程隊(duì)列中捌锭。該隊(duì)列是LIFO。當(dāng)I/O完成隊(duì)列非空罗捎,且工作線程并未超出總的并發(fā)數(shù)時(shí)观谦,系統(tǒng)從等待線程隊(duì)列中取出線程。該線程將從CreateIoCompletionPort函數(shù)后繼續(xù)執(zhí)行桨菜。

2.2.3接受連接可以使用WSAAccept或者AcceptEx(異步)豁状,和其他IO模型一樣,接受到連接后使用WSARecv接受數(shù)據(jù)倒得,這里就不需要使用SleepEx休眠了泻红。當(dāng)某個(gè)IO事件完成時(shí)OS會(huì)選擇一個(gè)線程執(zhí)行,所以使用WSARecv時(shí)需要傳入一些參數(shù)表示這個(gè)是Read類型霞掺。

2.2.4GetQueuedCompletionStatus函數(shù)的lpCompletionKey參數(shù)可供用戶自身使用谊路,LPOVERLAPPED作為重疊IO必須使用的結(jié)構(gòu)體也可利用傳參。LPOVERLAPPED結(jié)構(gòu)體的hEvent參數(shù)是事件模型所需要的菩彬,可作為一個(gè)指針傳參缠劝。這里還有一個(gè)技巧,使用CONTAINING_RECORD可以根據(jù)一個(gè)結(jié)構(gòu)體的第一個(gè)參數(shù)參數(shù)找到這個(gè)結(jié)構(gòu)體骗灶,但這個(gè)參數(shù)必須是該結(jié)構(gòu)體的第一個(gè)參數(shù)惨恭。

2.2.5除了當(dāng)IO操作完成時(shí)OS時(shí)投遞完成包,用戶還可使用PostQueuedCompletionStatus在IOCP上投寄一個(gè)完成包耙旦。當(dāng)需要釋放所有線程時(shí)只需要使用PostQueuedCompletionStatus函數(shù)傳入一個(gè)約定好的信號(hào)喉恋,即可讓線程退出。

完成端口核心代碼

void TService::InitCompletionPort()
{
    SYSTEM_INFO sys;
    GetSystemInfo(&sys);
    int process = sys.dwNumberOfProcessors;
    threadNum = 2 * process;
    m_portWorks = new HANDLE[threadNum];    //兩倍cpu數(shù)線程

    //將該設(shè)備句柄添加到設(shè)備列表中
    m_completionPortHandle = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, threadNum);
    if (m_completionPortHandle == nullptr)
    {
        OnError("create completion port error");
        return;
    }

    for (int i = 0; i < threadNum; i++)
    {
        m_portWorks[i] = ::CreateThread(0, 0, CompletionPortWord, m_completionPortHandle, 0, nullptr);//端口作為線程參數(shù)
    }


    ThreadPool::GetInstance().commit([](void* p)
    {
        while (true)  //接收連接線程母廷,可使用AcceptEx異步接收連接
        {
            TService& service = *Word::GetInstance().GetManager<TService>();
            service.clientSock = WSAAccept(service.listenSocket, (sockaddr*)&service.clientAddr, &service.addrSize, nullptr, NULL);

            IOContext* ioContext = new IOContext(service.clientSock);
            service.AcceptComplete(*ioContext); 
        }
    },nullptr);
}

DWORD __stdcall TService::CompletionPortWord(LPVOID IpParm)  //工作線程
{
    HANDLE portHandle = IpParm; //重疊端口

    DWORD lpNumberOfBytesRecvd; //
    OVERLAPPED*  IpOverlapped;

    TService* service;
    IOContext* iOContext;

    while (true)
    {
        BOOL bReturn =GetQueuedCompletionStatus(portHandle, &lpNumberOfBytesRecvd, (LPDWORD)&service, &IpOverlapped, INFINITE); //加入就緒棧轻黑,內(nèi)核會(huì)選擇一個(gè)線程執(zhí)行(FIFO)

        //先計(jì)算出IpOverlapped地址的偏移量,然后根據(jù)特定的變量地址找到結(jié)構(gòu)體地址,IpOverlapped需要作為第一個(gè)參數(shù)
        iOContext = (IOContext*)CONTAINING_RECORD(IpOverlapped, IOContext, m_overlapped);
        if (iOContext->m_Type==OperationType::ExitPosted)   //線程退出標(biāo)志
        {
            break;
        }
        if (bReturn == false)   //出現(xiàn)錯(cuò)誤
        {
            break;
        }

        if (lpNumberOfBytesRecvd == 0)
        {
            CloseHandle((HANDLE)iOContext->m_sockAccept);
            service->RemoveChannel(iOContext->m_sockAccept);
            continue;
        }

        switch (iOContext->m_Type)
        {
        case OperationType::AcceptPosted:
            service->AcceptComplete(*iOContext);
        case OperationType::RecvPosted:
            service->RecvComplete(*iOContext);
            break;
        case OperationType::SendPosted:
            service->SendComplete(*iOContext);
            break;
        }
    }

    return 0;
}

void TService::PostRecv(IOContext & ioContext)
{
    ioContext.m_Type = OperationType::RecvPosted;
    WSARecv(ioContext.m_sockAccept, &ioContext.m_wsaBuf, 1, &ioContext.m_numOfBytes, &ioContext.m_flags, &ioContext.m_overlapped, nullptr);
}

void TService::PostExit()
{
    IOContext context(listenSocket);
    context.m_Type = OperationType::ExitPosted;

    for (int i = 0; i < threadNum; i++) 
    {
        //每個(gè)線程都會(huì)收到一個(gè)釋放信號(hào)
        PostQueuedCompletionStatus(m_completionPortHandle, 1, (ULONG_PTR)this, (OVERLAPPED*)&context);
    }
}

void TService::AcceptComplete(IOContext & ioContext)
{
    SOCKET sock = this->clientSock;
    HANDLE handle = CreateIoCompletionPort((HANDLE)sock, this->m_completionPortHandle, (DWORD)this, 0); //加入端口
    TChannel* channel = (TChannel*)this->AddChannel(this->clientSock);

    PostRecv(ioContext);
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市琴昆,隨后出現(xiàn)的幾起案子氓鄙,更是在濱河造成了極大的恐慌,老刑警劉巖业舍,帶你破解...
    沈念sama閱讀 221,695評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抖拦,死亡現(xiàn)場(chǎng)離奇詭異升酣,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)态罪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門噩茄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人复颈,你說我怎么就攤上這事绩聘。” “怎么了耗啦?”我有些...
    開封第一講書人閱讀 168,130評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵凿菩,是天一觀的道長。 經(jīng)常有香客問我帜讲,道長衅谷,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,648評(píng)論 1 297
  • 正文 為了忘掉前任似将,我火速辦了婚禮获黔,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘在验。我一直安慰自己肢执,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評(píng)論 6 397
  • 文/花漫 我一把揭開白布译红。 她就那樣靜靜地躺著,像睡著了一般兴溜。 火紅的嫁衣襯著肌膚如雪侦厚。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,268評(píng)論 1 309
  • 那天拙徽,我揣著相機(jī)與錄音刨沦,去河邊找鬼。 笑死膘怕,一個(gè)胖子當(dāng)著我的面吹牛想诅,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播岛心,決...
    沈念sama閱讀 40,835評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼来破,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了忘古?” 一聲冷哼從身側(cè)響起徘禁,我...
    開封第一講書人閱讀 39,740評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎髓堪,沒想到半個(gè)月后送朱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體娘荡,經(jīng)...
    沈念sama閱讀 46,286評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評(píng)論 3 340
  • 正文 我和宋清朗相戀三年驶沼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了炮沐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,505評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡回怜,死狀恐怖大年,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鹉戚,我是刑警寧澤鲜戒,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站抹凳,受9級(jí)特大地震影響遏餐,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜赢底,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評(píng)論 3 333
  • 文/蒙蒙 一失都、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧幸冻,春花似錦粹庞、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至碑定,卻和暖如春流码,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背延刘。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評(píng)論 1 272
  • 我被黑心中介騙來泰國打工漫试, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人碘赖。 一個(gè)月前我還...
    沈念sama閱讀 48,921評(píng)論 3 376
  • 正文 我出身青樓驾荣,卻偏偏與公主長得像,于是被迫代替她去往敵國和親普泡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子播掷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容