欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費電子書(shū)等14項超值服

開(kāi)通VIP
IOCP服務(wù)器設計(via Modern C++)

鳴謝
感謝PiggyXP兄的雄文《手把手叫你玩轉網(wǎng)絡(luò )編程系列之三——完成端口(Completion Port)詳解》提供的思路

目錄

前言

C++11標準提出來(lái)有些年頭了,十一放假沒(méi)事研究了一下IOCP,想著(zhù)能不能用C++11實(shí)現一個(gè)高性能的服務(wù)器。當然,目前有許多十分成熟的C++網(wǎng)絡(luò )庫,比如ACE,asio等等。但是如果想深入了解其本質(zhì),在Windows平臺下就必須了解Socket結合IOCP的使用原理。

本文盡可能把筆者在使用C++11實(shí)現IOCP服務(wù)器的過(guò)程中遇到的困難和問(wèn)題展現給大家,讓大家學(xué)習起來(lái)少走些彎路。由于代碼比較底層,所以有些細節希望大家在看本文和代碼的時(shí)候能夠揣摩和理解。本文假定讀者總體把握了PiggyXP原文的相關(guān)內容并具有相當的Window編程的相關(guān)知識(熟悉WinSock2庫基本函數的使用,Windows多線(xiàn)程的基本概念等)、C++11/03編程基礎(STL,仿函數等)。

在每一節標題后都有箭頭指向目錄,文檔某些位置可能會(huì )有返回箭頭(返回到可能你在閱讀的地方),希望能幫助大家更好的理解本文。

本文代碼遵循Apache License 2.0協(xié)議,歡迎各位大神拍磚。分享帶來(lái)進(jìn)步,如需轉載請標明作者和出處,謝謝!

溫馨提示:由于筆者水平有限,雖經(jīng)過(guò)仔細調試,但本文代碼仍然可能存在筆者未知的Bug或者性能缺陷。請大家發(fā)現問(wèn)題后能夠及時(shí)聯(lián)系我,讓我們共同進(jìn)步。


開(kāi)發(fā)環(huán)境

表1 開(kāi)發(fā)環(huán)境

軟件/系統 版本
操作系統 Windows 10 v1607 x64
IDE/編譯器 Visual Studio 2015/CL 19
Win SDK 10.0.10240
編程語(yǔ)言 C++11

IOCP相關(guān)知識

本節參考文獻
Nasarre C, Richter J. Windows? via C/C++[M]. Pearson Education, 2007: 291-316.

引入

在生活中,異步的概念是很常見(jiàn)的。比如你洗衣服時(shí)突然女朋友(程序員有女朋友?)來(lái)了,你從洗衣間出去招待,而洗衣機則按照你的指令繼續在工作。當你招呼完女朋友回到洗衣間的時(shí)候,衣服已經(jīng)洗好了。也就是在女朋友來(lái)的時(shí)間點(diǎn),你與洗衣機分離,它按照你的指令在完成工作,而你卻可以處理其他更需要處理的事情。當你處理完回來(lái)后,洗衣機可能早已經(jīng)完成了它的工作,你只需要將衣服取出晾起來(lái)就可以了。而同步就是你家沒(méi)有洗衣機,當女朋友來(lái)的時(shí)候要么中斷洗衣服去招待女朋友,要么讓女朋友等待自己把衣服洗完,一件事情只能在另一件事情之后發(fā)生。這樣,大家就能明顯看出來(lái)有臺洗衣機的好處了。

不過(guò)如何知道衣服洗完了呢?Windows牌洗衣機給我們提供了這么四種方式:

表2 Windows 提供的4種異步方式

方式 解釋 相關(guān)技術(shù)
LED燈 洗完一件衣服就亮燈,但只有一個(gè)燈,其他人可以幫忙處理 觸發(fā)設備內核對象
高級LED燈 洗完一件衣服就亮燈,可以有多個(gè)燈,其他人可以幫忙處理 觸發(fā)事件內核對象
發(fā)送短信 洗完一件衣服就發(fā)送一條短信,有一個(gè)短信列表,但只有你能夠處理 可提醒IO(APC)
群發(fā)短信 洗完一件衣服就發(fā)送一條短信,有一個(gè)短信列表,其他人可以幫忙處理 IO完成端口(IOCP)

這樣,大家就很明白IOCP的好處了:不需要去時(shí)刻看著(zhù)燈亮不亮;短信到了可以去處理也可以不去處理;不僅你能處理,還有家人也能幫你處理。

觸發(fā)設備內核對象、觸發(fā)事件內核對象和可提醒IO就不展開(kāi)討論了,有興趣的朋友可以查閱本節列出的參考文獻,下面進(jìn)入正題。

IOCP狀態(tài)機

這一小節可能比較難,希望大家能夠耐心看下去,因為要真正掌握IOCP就必須弄清楚它內在的原理。先給出IOCP的狀態(tài)機,如圖1所示:



圖1 IOCP狀態(tài)機

下面給出圖中各組件的相關(guān)說(shuō)明:

表3 IOCP相關(guān)組件說(shuō)明

組件 簡(jiǎn)要解釋
等待隊列 當線(xiàn)程池中的某線(xiàn)程在等待IO操作時(shí)(調用GetQueuedCompletionStatus函數),IOCP將線(xiàn)程加入等待隊列。
IOCP在IO操作完成后將返回結果加入完成隊列,由等待隊列中的最后一個(gè)加入的線(xiàn)程處理。
已釋放列表 當等待的線(xiàn)程處理完IO操作后或是從暫停狀態(tài)被喚醒都會(huì )加入此列表。
當線(xiàn)程再次調用GetQueuedCompletionStatus函數將使自己再次加入等待隊列;將自身掛起將加入已暫停列表。
已暫停列表 當已釋放列表中的線(xiàn)程掛起時(shí)將加入已暫停列表;當掛起線(xiàn)程被激活時(shí)線(xiàn)程加入已釋放列表。
完成隊列 IOCP完成指定IO操作后將執行結果插入完成隊列。這個(gè)隊列時(shí)先進(jìn)先出的。
IOCP設備列表 即要進(jìn)行異步IO操作的設備列表(可以是文件,也可以是套接字),所有的IO操作都圍繞這些設備進(jìn)行。


這樣,整個(gè)IOCP服務(wù)器創(chuàng )建的流程就很明了了:?

  1. 創(chuàng )建一個(gè)新的完成端口,處理所有的IO請求。
  2. 創(chuàng )建一個(gè)線(xiàn)程池,此時(shí)線(xiàn)程處于已釋放列表。
  3. 創(chuàng )建一個(gè)Socket并將其綁定在創(chuàng )建的完成端口上,作為IO操作的實(shí)體。利用這個(gè)套接字進(jìn)行Listen操作,并向第1步創(chuàng )建的完成端口中投遞Accept消息,將第2步創(chuàng )建線(xiàn)程置于等待隊列中等待客戶(hù)端連接。
  4. 當客戶(hù)端連接后,IOCP將在IO完成隊列插入Accept,等待隊列中的線(xiàn)程將得到Accept,并創(chuàng )建新的Socket作為與客戶(hù)端通信的套接字,并將其綁定在第1步創(chuàng )建好的完成端口上。
  5. 此后,無(wú)論是Recv,Send都照此步驟進(jìn)行即可。

這里有幾個(gè)細節需要注意:

1. 最合適的線(xiàn)程數應當是多于處理器核心數的

多線(xiàn)程優(yōu)化理論告誡我們,為了避免ring0ring3之間的上下文切換,我們應當將線(xiàn)程數設置為處理器核數。但是微軟在設計IOCP的時(shí)候想到了這樣一個(gè)問(wèn)題:考慮到線(xiàn)程掛起,如果按照理論值設置線(xiàn)程數,將有可能出現實(shí)際工作線(xiàn)程數小于CPU所能接受的最大工作線(xiàn)程數,這樣就無(wú)法有效發(fā)揮多線(xiàn)程的優(yōu)勢。因此,最理想的線(xiàn)程數量應當多于處理器核心數的,經(jīng)驗值為兩倍核心數。

2. 等待隊列是后入先出的

之所以這樣設計也是出于性能調優(yōu)的考慮。當某線(xiàn)程處理完某批IO數據后重新加入等待隊列,由于LIFO機制,當完成隊列中又存在有新的IO數據時(shí),該線(xiàn)程將會(huì )優(yōu)先處理數據。這樣可能會(huì )導致某些線(xiàn)程一直處于等待狀態(tài),這樣Windows就可以將其換出內存節約空間。

3. 投遞

所謂投遞其實(shí)就是利用AcceptEx,WSARecvWSASend等函數在IO完成端口中進(jìn)行異步操作。形象來(lái)說(shuō)就是你向洗衣機輸入參數的過(guò)程,后續工作由洗衣機(WinSock2)完成。


Windows API相關(guān)知識

本節參考文獻
Microsoft. I/O Completion Ports[EB/OL]. https://msdn.microsoft.com/en-us/library/aa365198(VS.85).aspx
Microsoft. Windows Sockets 2[EB/OL]. https://msdn.microsoft.com/en-us/library/windows/desktop/ms740673(v=vs.85).aspx
Russinovich M E, Solomon D A, Ionescu A. Windows internals[M]. Pearson Education, 2012: 56-58.

IOCP APIs

關(guān)于常規的IO完成端口A(yíng)PI主要有以下三個(gè):

創(chuàng )建和關(guān)聯(lián)IO完成端口函數CreateIoCompletionPort,該函數在創(chuàng )建完成端口和關(guān)聯(lián)設備(文件設備,套接字等)時(shí)使用。

HANDLE WINAPI CreateIoCompletionPort(    _In_ HANDLE FileHandle,    _In_opt_ HANDLE ExistingCompletionPort,    _In_ ULONG_PTR CompletionKey,    _In_ DWORD NumberOfConcurrentThreads    );
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6


獲取完成隊列狀態(tài)函數GetQueuedCompletionStatus,該函數在線(xiàn)程池線(xiàn)程函數中使用。?

BOOL WINAPI GetQueuedCompletionStatus(    _In_ HANDLE CompletionPort,    _Out_ LPDWORD lpNumberOfBytesTransferred,    _Out_ PULONG_PTR lpCompletionKey,    _Out_ LPOVERLAPPED * lpOverlapped,    _In_ DWORD dwMilliseconds    );
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7


在完成隊列中插入消息函數PostQueuedCompletionStatus,該函數在給線(xiàn)程傳遞退出參數時(shí)使用。?

BOOL WINAPI PostQueuedCompletionStatus(    _In_ HANDLE CompletionPort,    _In_ DWORD dwNumberOfBytesTransferred,    _In_ ULONG_PTR dwCompletionKey,    _In_opt_ LPOVERLAPPED lpOverlapped    );
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

以上函數的詳細用法在參考文獻及piggyXP的文章中可以找到,故不再贅述。

在編程過(guò)程中主要考慮以下幾個(gè)問(wèn)題:

1. CreateIoCompletionPort函數的設計問(wèn)題

按照設計模式最基礎的原則即單一職責原則,這個(gè)函數設計是存在缺陷的。事實(shí)上很多Windows API都或多或少存在此問(wèn)題,筆者印象比較深刻的是NetBIOS的系列函數。理想的設計是自己再抽象兩個(gè)函數,即創(chuàng )建完成端口一個(gè)函數,綁定完成端口一個(gè)函數??梢赃@樣設計:

創(chuàng )建一個(gè)新的完成端口函數CreateNewIoCompletionPort,該函數在初始化時(shí)使用。

/*** Create completion port*/inline auto CreateNewIoCompletionPort( DWORD NumberOfConcurrentThreads = 0 ) {    return CreateIoCompletionPort( INVALID_HANDLE_VALUE, nullptr, 0, NumberOfConcurrentThreads );}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7


設備與完成端口綁定函數AssociateDeviceWithCompletionPort,該函數在完成端口建立后與IO設備綁定時(shí)使用。?

/*** Associate device with completion port*/inline auto AssociateDeviceWithCompletionPort( HANDLE hCompPort, HANDLE hDevice, DWORD dwCompKey ) {    return CreateIoCompletionPort( hDevice, hCompPort, dwCompKey, 0 ) == hCompPort;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

2. 線(xiàn)程池線(xiàn)程退出問(wèn)題

由于在程序中使用了線(xiàn)程池,對于每一個(gè)線(xiàn)程而言如何不留痕跡地結束是一個(gè)很有技巧性的問(wèn)題。一種優(yōu)雅的方法是使用PostQueuedCompletionStatus函數給完成端口傳遞退出完成鍵(CompletionKey)。由于線(xiàn)程只有可能在等待隊列、已釋放列表和已暫停列表中,且設計線(xiàn)程函數時(shí)均會(huì )循環(huán)調用GetQueuedCompletionStatus函數,因此最終所有線(xiàn)程都會(huì )轉移到等待隊列中去。

有的讀者會(huì )考慮到等待隊列的LIFO特性,其實(shí)只要我們設計線(xiàn)程函數時(shí)首先判斷傳入的完成鍵是否為退出的特定信號,檢測到自行退出即可。我們在主線(xiàn)程退出時(shí)在完成端口中傳入創(chuàng )建線(xiàn)程數量個(gè)推出信號,由于是完成隊列是順序存取,只要線(xiàn)程函數設計合理,可以保證每一個(gè)線(xiàn)程函數都可以收到退出消息。不會(huì )發(fā)生piggyXP考慮的收不到信息的情況。

更深入的討論高級程序員參考
筆者深入分析了GetQueuedCompletionStatus函數(由Kernel32.dll轉發(fā),在KernelBase.dll中實(shí)現),發(fā)現其內部準備好各項參數后調用了NtRemoveIoCompletion函數(由ntdll.dll轉發(fā),在內核ntoskrnl.exe中實(shí)現)。這樣就很明白了,其實(shí)就是在完成隊列中取出一個(gè)數據。

繼續對NtRemoveIoCompletion函數進(jìn)行分析,發(fā)現在內部調用了IoRemoveIoCompletion,繼續深究下去發(fā)現其主要功能調用了KeRemoveQueueEx函數,而在該函數內部進(jìn)行了無(wú)鎖同步

if ( _interlockedbittestandset( ... ) ) {    do {        do            KeYieldProcessorEx( ... );        while ( ... );    } while ( _interlockedbittestandset( ... ) );}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

這樣就能保證APC交付時(shí),只有一個(gè)線(xiàn)程可以訪(fǎng)問(wèn)到完成隊列。因此,只要在設計過(guò)程中一次只取出一個(gè)完成的數據,就不會(huì )出現問(wèn)題。當然,如果想更高效的處理數據(比如調用GetQueuedCompletionStatusEx)又想通過(guò)PostQueuedCompletionStatus方式退出的話(huà),就可能需要特殊處理。比如像piggyXP一樣設計一個(gè)信號量,或者接收到退出信號后在退出之前向完成隊列中再Post一個(gè)退出信號等等。

如果想要更加深入的了解其中的運作機理,大家可以去看看WRK或者是React OS的源碼。當然,這些代碼時(shí)代都比較久遠了,可能細節上和現在的Windows實(shí)現不太一樣,但是也能說(shuō)明問(wèn)題。

P.S.
在Windows Vista以上操作系統,將完成端口的句柄直接關(guān)閉將取消所有關(guān)聯(lián)的IO操作,關(guān)聯(lián)IO端口的所有線(xiàn)程調用GetQueuedCompletionStatus會(huì )放棄等待并立即返回FALSE,這時(shí)調用GetLastError獲取錯誤碼時(shí),會(huì )返回ERROR_INVALID_HANDLE。檢測到這一情況就可以退出了。

小插曲
在分析Windows 10內核的時(shí)候在Explorer中可以看到ntoskrnl,而在IDA中看不到。最后只得將其復制到其他地方才進(jìn)行了分析,感嘆一句微軟套路深。

3. 完成鍵(CompletionKey)和重疊結構(Overlapped)的設置問(wèn)題?

這里可能是理解完成端口的一個(gè)難點(diǎn),至少筆者在學(xué)習的時(shí)候在這里停頓了一段時(shí)間。

首先說(shuō)說(shuō)完成鍵。這個(gè)參數是為了給線(xiàn)程池中的線(xiàn)程通信而設計的,也就是說(shuō)當調用前文所述AssociateDeviceWithCompletionPort時(shí)傳入的完成鍵將會(huì )傳給調用GetQueuedCompletionStatus的線(xiàn)程。這樣,主線(xiàn)程就可以通過(guò)這兩個(gè)函數與線(xiàn)程池中的線(xiàn)程進(jìn)行通信。同樣注意到完成鍵是一個(gè)DWORD類(lèi)型,也可以給它傳入一個(gè)結構體的地址。

而重疊結構是在IO處理時(shí)傳遞給相應IO函數的數據載體。這個(gè)結構很有用,但本文不再展開(kāi)說(shuō)明,有興趣的朋友可以查看參考文獻相應部分。C/C++程序員應該都知道這樣一個(gè)事實(shí):結構體的第一個(gè)成員的地址和結構體的地址是相同的。所以,我們可以定義一個(gè)結構體(或者是一個(gè)C++類(lèi)),將重疊結構作為第一個(gè)成員,在IO處理時(shí),將我們定義的結構傳入。這樣,IO函數處理它自身需要的重疊結構信息,而我們可以在其中夾帶私貨。為什么要這么做呢?因為在我們在線(xiàn)程函數中可能需要一些其他的數據,這樣就可以通過(guò)這種辦法傳進(jìn)去。

于是我們就明白了:完成鍵與線(xiàn)程有關(guān)而重疊結構與IO有關(guān)。我們需要完成鍵給線(xiàn)程傳遞參數,需要重疊結構(以及夾帶的私貨)來(lái)完成IO操作。

至于這些怎樣與Socket結合,請瀏覽下一節內容。

更深入的討論高級程序員參考
在piggyXP的博文中提到了一個(gè)“神奇的宏”:CONTAINING_RECORD。這個(gè)宏廣泛應用于驅動(dòng)編程中,用于獲取在知道結構體某成員地址的情況下推知整個(gè)結構體地址的場(chǎng)景中。具體定義如下:

/*** Calculate the address of the base of the structure given its type, and an* address of a field within the structure.*/#define CONTAINING_RECORD(address, type, field) ((type *)(                                                  (PCHAR)(address) -                                                  (ULONG_PTR)(&((type *)0)->field)))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

這個(gè)是帶有濃郁C風(fēng)格、充滿(mǎn)trick的一個(gè)宏。能進(jìn)行深入討論的朋友一看就明白,就不班門(mén)弄斧了。值得注意的是,使用這個(gè)宏的時(shí)候對成員是否是結構體的第一個(gè)成員沒(méi)有限制。

WinSock2 APIs

主要使用的API有如下6個(gè):

創(chuàng )建套接字函數WSASocket,在創(chuàng )建OVERLAPPED套接字時(shí)使用。

注意
WSASocket是一個(gè)宏定義,在MBCS環(huán)境下定義為WSASocketA,在UNICODE環(huán)境下定義為WSASocketW。

SOCKET WSAAPI WSASocketW ( // WSASocketA for MBCS    _In_ int af,    _In_ int type,    _In_ int protocol,    _In_opt_ LPWSAPROTOCOL_INFOW lpProtocolInfo,  // LPWSAPROTOCOL_INFOA for MBCS    _In_ GROUP g,    _In_ DWORD dwFlags    );
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

綁定函數bind,在服務(wù)器初始化時(shí)使用。

int WSAAPI bind(    _In_ SOCKET s,    _In_reads_bytes_(namelen) const struct sockaddr FAR * name,    _In_ int namelen    );
  • 1
  • 2
  • 3
  • 4
  • 5

監聽(tīng)函數listen,在等待客戶(hù)端連接監聽(tīng)時(shí)使用。

int WSAAPI listen(    _In_ SOCKET s,    _In_ int backlog    );
  • 1
  • 2
  • 3
  • 4

控制套接字函數WSAIoctl,在獲取函數指針時(shí)使用。

int WSAAPI WSAIoctl(    _In_ SOCKET s,    _In_ DWORD dwIoControlCode,    _In_reads_bytes_opt_(cbInBuffer) LPVOID lpvInBuffer,    _In_ DWORD cbInBuffer,    LPVOID lpvOutBuffer,    _In_ DWORD cbOutBuffer,    _Out_ LPDWORD lpcbBytesReturned,    _Inout_opt_ LPWSAOVERLAPPED lpOverlapped,    _In_opt_ LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine    );
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11


微軟擴展的accept函數AcceptEx,用于接受用戶(hù)接入并獲取第一組傳輸的數據,代替accept使用。?

BOOL PASCAL AcceptEx (    _In_ SOCKET sListenSocket,    _In_ SOCKET sAcceptSocket,    PVOID lpOutputBuffer,    _In_ DWORD dwReceiveDataLength,    _In_ DWORD dwLocalAddressLength,    _In_ DWORD dwRemoteAddressLength,    _Out_ LPDWORD lpdwBytesReceived,    _Inout_ LPOVERLAPPED lpOverlapped    );
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

微軟擴展的配合解析AcceptEx函數返回值使用的函數GetAcceptExSockaddrs,需要獲取第一組數據的時(shí)候使用。

void GetAcceptExSockaddrs (    _In_  PVOID      lpOutputBuffer,    _In_  DWORD      dwReceiveDataLength,    _In_  DWORD      dwLocalAddressLength,    _In_  DWORD      dwRemoteAddressLength,    _Out_ LPSOCKADDR *LocalSockaddr,    _Out_ LPINT      LocalSockaddrLength,    _Out_ LPSOCKADDR *RemoteSockaddr,    _Out_ LPINT      RemoteSockaddrLength);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10


異步接受數據函數WSARecv,在接收數據時(shí)使用。?

int WSAAPI WSARecv(    _In_ SOCKET s,    _In_reads_(dwBufferCount) __out_data_source(NETWORK) LPWSABUF lpBuffers,    _In_ DWORD dwBufferCount,    _Out_opt_ LPDWORD lpNumberOfBytesRecvd,    _Inout_ LPDWORD lpFlags,    _Inout_opt_ LPWSAOVERLAPPED lpOverlapped,    _In_opt_ LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine    );
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9


異步接受數據函數WSASend,在接收數據時(shí)使用。?

int WSAAPI WSASend(    _In_ SOCKET s,    _In_reads_(dwBufferCount) LPWSABUF lpBuffers,    _In_ DWORD dwBufferCount,    _Out_opt_ LPDWORD lpNumberOfBytesSent,    _In_ DWORD dwFlags,    _Inout_opt_ LPWSAOVERLAPPED lpOverlapped,    _In_opt_ LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine    );
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

以上函數的詳細用法在參考文獻及piggyXP的文章中可以找到,故不再贅述。

在編程過(guò)程中主要考慮以下幾個(gè)問(wèn)題:

1. AcceptExGetAcceptExSockaddrs函數的調用問(wèn)題

在實(shí)際使用中我們可以發(fā)現,調用這兩個(gè)函數無(wú)一不是利用了WSAIoctl返回的函數指針。筆者在MSDN中也找到了這樣的說(shuō)法:

“The function pointer for the AcceptEx / GetAcceptExSockaddrs function must be obtained at run time by making a call to the WSAIoctl function with the SIO_GET_EXTENSION_FUNCTION_POINTER opcode specified. “

因此,我們在使用這兩個(gè)函數之前必須通過(guò)WSAIoctl來(lái)獲取這兩個(gè)函數的指針加以調用。

更深入的討論高級程序員參考
事實(shí)上筆者發(fā)現,在mswsock.dll中是導出了這兩個(gè)函數的。那為什么微軟在MSDN中沒(méi)有說(shuō)到呢,非要用如此麻煩的方式去調用AcceptExGetAcceptExSockaddrs這兩個(gè)函數?

mswsock.dll其實(shí)也只是一個(gè)轉發(fā)器,真實(shí)的函數在另外的地方。在AcceptEx函數內部也會(huì )調用WSAIoctl(在ws2_32.dll中實(shí)現)來(lái)獲取真實(shí)的函數地址。

有一個(gè)非常有意思的地方,AcceptEx函數除了尋找自己的真實(shí)函數地址以外,還回去尋找GetAcceptExSockaddrs函數的地址,同時(shí)進(jìn)行設置;在導出的GetAcceptExSockaddrs函數內部不會(huì )再去尋找自身實(shí)現的地址,而是使用AcceptEx函數設置的地址,如果地址為空則將后四個(gè)傳入的參數全部置零,有興趣的朋友可以嘗試一下。

所以使用導出的AcceptEx而不通過(guò)指針從理論上也是可以的,在使用導出的GetAcceptExSockaddrs之前務(wù)必要使用導出的AcceptEx來(lái)設置內部指針,而且并不是說(shuō)使用導出的函數效率低才使用函數指針獲取函數實(shí)現地址??赡艿脑蚴敲總€(gè)Windows版本的實(shí)現驅動(dòng)可能不同,對上的接口需要mswsock.dll來(lái)保持一致。

另外,在使用這兩個(gè)函數時(shí)要注意在傳遞SOCKADDR_IN結構體大小時(shí)要加上16,與具體實(shí)現相關(guān),原因不明。

2. 設置各函數完成鍵和重疊結構體的問(wèn)題

完成鍵(CompletionKey)和重疊結構(Overlapped)的設置問(wèn)題討論。在本文程序中,要設置完成鍵和重疊結構體的主要有以下6個(gè)函數,如表4所示:

表4 需要設置的函數及相關(guān)解釋

函數 需要設置的內容 相關(guān)解釋
AssociateDeviceWithCompletionPort 完成鍵 初始化時(shí)將完成端口和線(xiàn)程綁定時(shí)需要使用
GetQueuedCompletionStatus 完成鍵和重疊結構 線(xiàn)程獲取參數與IO狀態(tài)時(shí)使用
PostQueuedCompletionStatus 完成鍵和重疊結構 傳遞線(xiàn)程參數與設置IO狀態(tài)時(shí)使用
AcceptEx 重疊結構 在異步接受客戶(hù)端接入時(shí)使用
WSARecv 重疊結構 在異步接收消息時(shí)使用
WSASend 重疊結構 在異步發(fā)送消息時(shí)使用

大家一看就明白了,AssociateDeviceWithCompletionPort是主線(xiàn)程將創(chuàng )建好的完成端口與IO設備綁定時(shí)調用的,只需要完成鍵;GetQueuedCompletionStatus函數是線(xiàn)程池中工作線(xiàn)程調用的,因此要獲取完成鍵和重疊結構;PostQueuedCompletionStatus函數要傳遞參數和設置IO狀態(tài)到完成隊列中去,因此也需要兩個(gè);AcceptEx、WSARecvWSASend函數是用來(lái)進(jìn)行IO操作(網(wǎng)絡(luò )操作)的,因此只需要和網(wǎng)絡(luò )IO設備打交道,只需設置重疊結構。

注意到前述討論中的問(wèn)題,可以設計這樣一個(gè)結構體充當重疊結構夾帶私貨:

using IO_CONTEXT = struct _IO_CONTEXT {        /**        * data section        */        OVERLAPPED  m_olOverLapped;             /**< Windows overlapped structure */        SOCKET      m_sAssociatedSocket;        /**< context associated socket */        WSABUF      m_wsaBuffer;                /**< the buffer to recieve WSASocket data */        CHAR        m_cBuffer[MAX_BUFFER_SIZE]; /**< message buffer */        enum class Flag : unsigned char {            Read,                               /**< read( recv ) */            Write,                              /**< write( send ) */            Accept                              /**< accept socket( for AcceptEx API ) */        } m_bFlag;                              /**< rw flag */        /**        * operation section        */        ...}using PIO_CONTEXT = IO_CONTEXT*;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

注意到完成鍵可以傳入某結構體或類(lèi)的地址,因此可以設計這樣一個(gè)結構體充當完成鍵傳遞給線(xiàn)程池中線(xiàn)程:

using HANDLE_CONTEXT = struct _HANDLE_CONTEXT {        /**        * data section        */        SOCKET      m_hClientSocket;            /**< socket in thread to handle */        SOCKADDR_IN m_sClientAddr;              /**< sockaddr_in in thread to handle */        std::vector<PIO_CONTEXT> m_vIoContext;  /**< vector of IoContext pointer */        bool        m_bFinished;                /**< is process finished */        /**        * operation section        */        ...}using PHANDLE_CONTEXT = HANDLE_CONTEXT*;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

結構定義和piggyXP大同小異,主要差別就在于HANDLE_CONTEXT::m_bFinished項,在PostQueuedCompletionStatus傳遞時(shí)將其置為true,讓線(xiàn)程池中線(xiàn)程退出即可。

上下文大致運行流程如圖2所示,聰明的你一定一下就明白,就不贅述了??梢詤⒖?a rel="nofollow" target="_self">上一節所述流程,也可以參照代碼理解:


圖2 各上下文運行大致流程


幾個(gè)問(wèn)題*

本節參考文獻
ISO. IEC14882:2011 Information technology – Programming languages – C++ [S]. Geneva, Switzerland: International Organization for Standardization, 2011.
Meyers S. Effective modern C++: 42 specific ways to improve your use of C++ 11 and C++ 14[M]. ” O’Reilly Media, Inc.”, 2014.

提示
這一節內容和本文主體關(guān)系不大,內容也不深,對本節不感興趣的朋友可以跳過(guò)。

function-like macro與inline function的選擇

例如piggyXP給出了如下的函數樣式的宏:

// 釋放指針宏#define RELEASE(x)                      {if(x != NULL ){delete x;x=NULL;}}
  • 1
  • 2

而筆者在定義時(shí)選擇了內聯(lián)函數:

/*** Release memory*/template<typename _T>inline void ReleaseMemory( _T*& pMemory ) {    if ( pMemory != nullptr ) {        delete pMemory;        pMemory = nullptr;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

主要代碼是差不多的,但是能夠完成的操作是不一樣的,聰明的你應該可以看出來(lái)。這個(gè)例子不一定好,那就再舉一個(gè)常見(jiàn)的:

#define MAX(a, b)       ( (a) > (b) ? (a) : (b) ) // oopsint result_oops = MAX(i++, j); 
  • 1
  • 2
  • 3

選用function-like macro的好處只有一條:簡(jiǎn)單方便,效率高(空間換時(shí)間),缺點(diǎn)就不多說(shuō)了,看著(zhù)就明白。選用inline function最主要的好處就是:類(lèi)型檢查,效率高(可能空間換時(shí)間)。

在編程過(guò)程中請盡可能減少預處理器的使用(尤其是函數樣式的宏)。

macro constant與compile-time constant(constant expression)

我們可能習慣于這樣定義“常量”:

#define MAX_BUFFER_SIZE 8192
  • 1

當然,這是一個(gè)宏,在使用的時(shí)候替換為8192這一個(gè)字面量??紤]這樣的代碼:

#define N 2 + 3// oopsint oops = N / 2;   // 3
  • 1
  • 2
  • 3

當然你也可以這樣定義,不過(guò)總覺(jué)得這樣定義很別扭:

#define N ( 2 + 3 )
  • 1

結果不用多說(shuō)。采用宏常量的理由還是:方便、效率高(字面值,在代碼中成為立即數),但是沒(méi)有類(lèi)型檢查(預處理器管理),有時(shí)候用著(zhù)很麻煩。

而以往的常量const又占用了存儲空間,而且畢竟存儲在內存中,也是可以變化的??紤]以下代碼:

const int constant = 0;int* evil_ptr = ( int* )&constant;*evil_ptr = 1;...
  • 1
  • 2
  • 3
  • 4

這樣,一個(gè)常量就變化了。

更深入的討論高級程序員參考
事實(shí)上筆者在測試的時(shí)候發(fā)現如果對constant進(jìn)行輸出,會(huì )得到結果為0。反匯編后發(fā)現VS直接給輸出函數賦的是0,沒(méi)有從地址取值,優(yōu)化的還是可以。

在C++11中引入了常量表達式constexpr概念,它是一個(gè)編譯期的常量(字面量),由編譯器負責執行。這樣,又可以進(jìn)行類(lèi)型檢查,又可以提高效率,減少資源占用,好處還是很多的。其中一個(gè):

constexpr std::size_t N = 2 + 3;// no oopsauto normal = N / 2;    // 2
  • 1
  • 2
  • 3
本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請點(diǎn)擊舉報。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
PostQueuedCompletionStatus函數與GetQueuedCompletionStatus函數
完成端口詳解
IOCP模型總結
WinSock2編程之打造完整的SOCKET池
淺談線(xiàn)程池(中):獨立線(xiàn)程池的作用及IO線(xiàn)程池 - 老趙點(diǎn)滴 - 追求編程之美 - 博客園
libevent編程疑難解答
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導長(cháng)圖 關(guān)注 下載文章
綁定賬號成功
后續可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服

欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久