6.2.1 接受連接的方法
Winsock 擴(kuò)展函數(shù) AcceptEx 是唯一能夠使用重疊 I/O 接受客戶連接的函數(shù)。下面主要深入探討使用該函數(shù)接收連接的問(wèn)題。
前面已經(jīng)討論過(guò),當(dāng)客戶連接進(jìn)來(lái)時(shí),服務(wù)器需要?jiǎng)?chuàng)建一個(gè)套接字來(lái)負(fù)責(zé)維護(hù)與一個(gè)客戶端的會(huì)話。使用 AcceptEx 函數(shù)之前必須創(chuàng)建一些套接字,并且這些套接字必須是未綁定、未連接的,即使它們可能在調(diào)用 TransmitFile, TransmitPackets, 或 DisconnectEx 后可以重用。
響應(yīng)服務(wù)器必須總是具有足夠的 AcceptEx 在站崗,以便在有客戶連接請(qǐng)求時(shí)調(diào)用。但是,并沒(méi)有具體的數(shù)量能夠保證服務(wù)器能夠立即響應(yīng)連接。我們知道在調(diào)用 listen 將監(jiān)聽(tīng)套接字置于監(jiān)聽(tīng)狀態(tài)后, TCP/IP 堆棧會(huì)自動(dòng)接受到來(lái)的連接,直到達(dá)到 listen 的 backlog 參數(shù)設(shè)定的限制。對(duì)于 Windows NT 服務(wù)器而言,支持的 backlog 的最大值為 200 。如果服務(wù)器投遞了 15 個(gè) AcceptEx 調(diào)用,然后突然有 50 個(gè)客戶請(qǐng)求連接服務(wù)器,它們的連接請(qǐng)求都不會(huì)遭到拒絕。服務(wù)器投遞的 AcceptEx I/O 會(huì)滿足前面的 15 個(gè)連接,剩下的 35 個(gè)連接都被系統(tǒng)默認(rèn)連接了。檢查一下 backlog 的值發(fā)現(xiàn),系統(tǒng)還有能力默認(rèn)接受 165 個(gè)連接。之后,如果服務(wù)器投遞 AcceptEx 調(diào)用,它們會(huì)立即成功返回,因?yàn)橄到y(tǒng)會(huì)將默認(rèn)接收的連接放入 “ 等待連接隊(duì)列 ” 中。
服務(wù)器的特性是決定要投遞多少個(gè) AcceptEx 操作的重要因素。例如,希望處理大量短時(shí)間即時(shí)連接的客戶要比處理少量長(zhǎng)時(shí)間連接的客戶投遞更多的 AcceptEx I/O 。一個(gè)好的策略是允許 AcceptEx 的調(diào)用數(shù)量在最小值和最大值之間變化。具體做法是,應(yīng)用程序跟蹤未決的 AcceptEx I/O 的數(shù)量,當(dāng)一個(gè)或多個(gè) I/O 完成使這個(gè)未決 I/O 數(shù)量變得比最小值還小時(shí),就再投遞額外的 AcceptEx I/O 。
在 Windows 2000 和以后的 Windows 操作系統(tǒng)版本中, Winsock 提供了一種機(jī)制,用來(lái)確定應(yīng)用程序是否投遞了足夠的 AcceptEx 調(diào)用。創(chuàng)建監(jiān)聽(tīng)套接字時(shí),使用 WSAEventSelect 函數(shù)為監(jiān)聽(tīng)套接字關(guān)聯(lián)一個(gè)事件對(duì)象,注冊(cè) FD_ACCEPT 事件。如果投遞的 AcceptEx 操作用完,但是仍有客戶請(qǐng)求接入(系統(tǒng)根據(jù) backlog 值決定是否接受這些連接),事件對(duì)象就是受信,說(shuō)明應(yīng)該投遞額外的 AcceptEx 操作了。這實(shí)際上還是利用事件對(duì)象來(lái)使調(diào)用線程處于一種 “ 可警告狀態(tài) ” ,當(dāng)有客戶連接請(qǐng)求時(shí),就根據(jù)當(dāng)前 AcceptEx 操作是否用完來(lái)警告(通知)是否需要投遞新的 AcceptEx 操作來(lái)處理新的客戶連接。
使用 AcceptEx 處理連接的另外一個(gè)功能就是在處理連接時(shí)還可以接收用戶發(fā)來(lái)的第一塊數(shù)據(jù)(前提是為 AcceptEx 提供了接收緩沖區(qū)),這對(duì)于那些請(qǐng)求連接的同時(shí)發(fā)送了一些數(shù)據(jù)過(guò)來(lái)的客戶來(lái)說(shuō)很適用。但是,此時(shí),除非接收連接的同時(shí)接收到了客戶發(fā)送過(guò)來(lái)的一些數(shù)據(jù),否則 AcceptEx 是不會(huì)返回的。
為了滿足客戶的需求,服務(wù)器不得不投遞更多的接受 I/O ,這會(huì)占用大量的系統(tǒng)資源。如果客戶僅調(diào)用 connect 函數(shù)連接服務(wù)器,長(zhǎng)時(shí)間既不發(fā)送數(shù)據(jù),也不關(guān)閉連接,就可能造成 AcceptEx 投遞的大量重疊 I/O 操作不能返回。這就是 “ 惡意連接 ” 。為此,服務(wù)器應(yīng)該記錄每個(gè) AcceptEx 投遞的未決 I/O, 定時(shí)掃描它們,設(shè)置 SO_CONNECT_TIME 參數(shù)調(diào)用 getsockopt 檢查它們連接的時(shí)間,如果超時(shí),就將連接關(guān)閉。如果使用 WSAEventSelect 模型來(lái)通知有連接事件,則當(dāng)事件受信時(shí),是檢查客戶套接字( AcceptSocket )是否真正連接了。
每當(dāng)調(diào)用 AcceptEx 接受客戶端連接時(shí),它也在等待接受客戶發(fā)送過(guò)來(lái)的第一個(gè)數(shù)據(jù)塊,這時(shí)不允許投遞另外一個(gè) AcceptEx 。當(dāng) AcceptEx 返回后,如果事件對(duì)象再次受信則表明有新的連接到來(lái)。需要注意的是,無(wú)論何時(shí),千萬(wàn)不要關(guān)閉一個(gè)調(diào)用 AcceptEx 還沒(méi)有返回的套接字( AcceptSocket ),因?yàn)檫@會(huì)導(dǎo)致內(nèi)存泄露。因?yàn)閺膬?nèi)部執(zhí)行邏輯看,當(dāng)沒(méi)有連接的套接字句柄被關(guān)閉時(shí),調(diào)用 AcceptEx 所涉及到的內(nèi)核模式的數(shù)據(jù)結(jié)構(gòu)并不會(huì)清除掉,直到有新的連接建立或者監(jiān)聽(tīng)套接字被關(guān)閉。
盡管在一個(gè)等待完成通知的工作者線程中,投遞一個(gè) AcceptEx 操作,看起來(lái)既簡(jiǎn)單又合情合理,但是應(yīng)盡量避免這樣做,因?yàn)閯?chuàng)建套接字還是很耗費(fèi)資源的。另外,也不要在工作者線程中進(jìn)行任何復(fù)雜的計(jì)算,以便處理器可以盡快的在接到完成通知后進(jìn)行后續(xù)處理。創(chuàng)建套接字耗費(fèi)資源的一個(gè)原因在于 Winsock 2.0 本身的架構(gòu)很復(fù)雜,成功地創(chuàng)建一個(gè)套接字可能需要調(diào)用很多內(nèi)核服務(wù)。因此,服務(wù)器應(yīng)該在單獨(dú)線程中創(chuàng)建套接字,投遞 AcceptEx 操作。當(dāng)調(diào)用線程投遞的 AcceptEx 重疊操作完成時(shí),一個(gè)受信的事件將會(huì)通知處理線程。
6.2.2 數(shù)據(jù)傳輸問(wèn)題
數(shù)據(jù)傳輸是通信程序執(zhí)行的核心操作。當(dāng)一個(gè)客戶與服務(wù)器建立連接后,它們的主要工作就是傳輸數(shù)據(jù),因?yàn)閿?shù)據(jù)是信息的表示。由上一節(jié)幾種 I/O 模型的性能測(cè)試分析可知,當(dāng)連接數(shù)量很大時(shí),數(shù)據(jù)吞吐量是一個(gè)重要的性能考核指標(biāo)。
從性能角度考慮,所有的數(shù)據(jù)傳輸最好都應(yīng)采用重疊 I/O 處理。默認(rèn)情況下,系統(tǒng)為每個(gè) socket 分配一個(gè)的接受緩沖區(qū)和一個(gè)發(fā)送緩沖區(qū),用來(lái)緩存接收和發(fā)送的數(shù)據(jù)。但在重疊 I/O 中,這些緩沖區(qū)往往不用,可以傳遞參數(shù) SO_SNDBUF 或 SO_RCVBUF 調(diào)用 setsockopt ,來(lái)將它們?cè)O(shè)置為 0 。
讓我們來(lái)看看,當(dāng)發(fā)送緩沖區(qū)沒(méi)有設(shè)置為 0 時(shí),系統(tǒng)是怎么處理一個(gè)典型的 send 操作的。當(dāng)一個(gè)應(yīng)用程序調(diào)用 send 函數(shù)時(shí),如果有充足的緩沖空間,需要發(fā)送的數(shù)據(jù)將被拷貝到套接字的發(fā)送緩沖區(qū), send 函數(shù)立即成功返回,并且一個(gè)完成通知被拋出。另外一個(gè)方面,如果套接字的發(fā)送緩沖區(qū)已滿,則應(yīng)用程序提供的發(fā)送緩沖區(qū)被鎖定,再次對(duì) send 函數(shù)的調(diào)用將會(huì)返回 WSA_IO_PENDING 錯(cuò)誤。當(dāng)發(fā)送緩沖區(qū)中的數(shù)據(jù)被處理(例如,提交給傳輸層處理)時(shí), Winsock 實(shí)際上直接處理鎖定在緩沖區(qū)中的數(shù)據(jù),也即繞過(guò)套接字的發(fā)送緩沖區(qū),直接從應(yīng)用程序緩沖區(qū)中提交數(shù)據(jù)給傳輸層。
接收數(shù)據(jù)的情況恰好相反。當(dāng)一個(gè)重疊的 receive 請(qǐng)求拋出后,如果數(shù)據(jù)已經(jīng)接收成功,它會(huì)被緩存在套接字接收緩沖區(qū)。數(shù)據(jù)會(huì)拷貝到應(yīng)用程序緩沖區(qū)(直到飽和)。 receive 調(diào)用返回,并且一個(gè)完成通知被拋出。當(dāng)套接字緩沖區(qū)被設(shè)置為空時(shí),如果調(diào)用重疊的 receive 操作將返回 WSA_IO_PENDING 錯(cuò)誤。當(dāng)有數(shù)據(jù)到達(dá)時(shí),它將繞過(guò)套接字緩沖區(qū)而直接被拷貝到應(yīng)用程序緩沖區(qū)。
設(shè)置單套接字緩沖區(qū)為 0 ,并不能提高性能,因?yàn)橹灰恢庇写罅康闹丿B接發(fā)請(qǐng)求被拋出,就不會(huì)有額外的內(nèi)存拷貝。設(shè)置套接字發(fā)送緩沖區(qū)為空比設(shè)置套接字接收緩沖區(qū)為空對(duì)系統(tǒng)的性能影響要小。因?yàn)閼?yīng)用程序的發(fā)送緩沖區(qū)會(huì)被經(jīng)常鎖定直到它被提交給傳輸層處理。然而,若將接收緩沖區(qū)設(shè)置為 0 ,并且沒(méi)有重疊的 receive 調(diào)用,任何傳進(jìn)來(lái)的數(shù)據(jù)只能緩存在傳輸層。傳輸層驅(qū)動(dòng)程序只會(huì)緩存滑動(dòng)窗口尺寸的數(shù)據(jù),即 17KB— 傳輸層可以分配的緩沖區(qū)大小的上限。實(shí)際的緩沖區(qū)要比 17KB 小。傳輸層緩沖區(qū)(針對(duì)一次連接)是在非分頁(yè)池之外分配的,這意味著,當(dāng)服務(wù)建立了 1000 個(gè)連接時(shí),即使沒(méi)有拋出 receive 請(qǐng)求,非分頁(yè)池中也會(huì)分配 17MB 的內(nèi)存。而非分頁(yè)池是很珍貴的資源,除非服務(wù)器可以保證總是有接收請(qǐng)求拋出,否則套接字接收緩沖區(qū)應(yīng)該不需設(shè)置。
只有在一些特殊情況下,對(duì)套接字接收緩沖區(qū)不予設(shè)置將會(huì)導(dǎo)致性能降低??紤]服務(wù)器需要處理成千上萬(wàn)個(gè)客戶連接,而每個(gè)連接上又都沒(méi)有投遞 receive 請(qǐng)求的情況,如果客戶端零星地發(fā)送數(shù)據(jù)過(guò)來(lái),傳輸進(jìn)來(lái)的數(shù)據(jù)將被緩存在套接字接收緩沖區(qū)中。當(dāng)服務(wù)器處理一個(gè) receive 重疊 I/O 時(shí),它會(huì)做一些不必要的工作。當(dāng)完成通知到達(dá)時(shí),重疊操作會(huì)處理一個(gè) I/O 請(qǐng)求包( IRP )。在這種情形下,服務(wù)器不能保留很多拋出的 receive 請(qǐng)求。因此,最好使用簡(jiǎn)單的非阻塞接收函數(shù)。
6.3 內(nèi)存資源管理問(wèn)題
由于機(jī)器硬件條件所限,系統(tǒng)資源是有限的,因此不得不考慮內(nèi)存資源的管理問(wèn)題。從上一節(jié)對(duì)不同 I/O 模型進(jìn)行的性能測(cè)試結(jié)果分析可知,維持大規(guī)模的通信連接,不僅會(huì)耗費(fèi)掉大量?jī)?nèi)存,而且對(duì) CPU 的占用也是很高的。
對(duì)于配置比較高的服務(wù)器而言,處理成千上萬(wàn)個(gè)連接并不成問(wèn)題。但是隨著連接量的劇增,內(nèi)存資源的限制將逐漸凸現(xiàn)。最有可能遇到的兩個(gè)限制因素就是鎖定頁(yè)和非分頁(yè)池。鎖定頁(yè)的限制不是太嚴(yán)重,更應(yīng)該避免的是非分頁(yè)池被耗盡。每一次調(diào)用重疊的 send 或 receive 請(qǐng)求,提交的緩沖區(qū)都可能被鎖住。當(dāng)內(nèi)存被鎖定時(shí),它就不能從物理內(nèi)存換出。操作系統(tǒng)對(duì)鎖定內(nèi)存的數(shù)量是有限制的,當(dāng)達(dá)到極限時(shí),重疊操作將會(huì)返回 WSAENOBUFS 錯(cuò)誤。如果服務(wù)器在每個(gè)連接上投遞多個(gè)重疊接收操作,隨著客戶連接數(shù)量的增多,極限就會(huì)達(dá)到。如果期望服務(wù)器能夠處理高并發(fā)通信,服務(wù)器可以在每個(gè)連接上投遞一個(gè) 0 字節(jié)的接受操作,這樣就不會(huì)有內(nèi)存鎖定。 0 字節(jié)的接受完成以后,服務(wù)器可以簡(jiǎn)單地執(zhí)行一個(gè)非阻塞的接收函數(shù)來(lái)獲取緩存在套接字接收緩沖區(qū)中的所有數(shù)據(jù)。當(dāng)非阻塞接收調(diào)用返回 WSAEWOULDBLOCK 時(shí),就表示不再有未決的數(shù)據(jù)了。這種方法非常適合用來(lái)設(shè)計(jì)那些希望通過(guò)犧牲每個(gè)套接字上的吞吐率來(lái)獲取更大規(guī)模并發(fā)連接的服務(wù)器。
當(dāng)然,最好還要了解客戶端與服務(wù)器通信的方式。在上面的例子中,當(dāng) 0 字節(jié)的接收完成后,再投遞一個(gè)異步接收操作,將接收到所有緩存在套接字接收緩沖區(qū)中的數(shù)據(jù)。如果服務(wù)器知道客戶端將會(huì)連續(xù)不斷發(fā)送數(shù)據(jù),那么當(dāng) 0 字節(jié)的接收完成后,假如客戶端將發(fā)送大數(shù)據(jù)塊(超過(guò)單套接字緩沖區(qū) 8KB 的容量)過(guò)來(lái),服務(wù)器將拋出一個(gè)或多個(gè)重疊的接收操作。
另外一個(gè)需要重點(diǎn)考慮的問(wèn)題就是系統(tǒng)所需頁(yè)的數(shù)量。當(dāng)系統(tǒng)鎖定傳遞給重疊操作的內(nèi)存時(shí),它是在頁(yè)邊界上進(jìn)行的。在 x86 體系結(jié)構(gòu)上,內(nèi)存頁(yè)的大小為 4KB 。如果一個(gè)操作投遞了 1KB 的緩沖區(qū),系統(tǒng)實(shí)際上會(huì)為它鎖定 4KB 大小的內(nèi)存塊。為避免這種浪費(fèi),重疊發(fā)送和接收緩沖區(qū)的大小應(yīng)該是頁(yè)大小的倍數(shù)。可以使用 GetSystemInfo 這個(gè) API 來(lái)獲知當(dāng)前系統(tǒng)頁(yè)的大小。
如果突破非分頁(yè)池極限,將會(huì)導(dǎo)致更嚴(yán)重的錯(cuò)誤,并且很難恢復(fù)。非分頁(yè)池是內(nèi)存的一部分,它常駐內(nèi)存,并且永遠(yuǎn)不會(huì)被交換出去。內(nèi)核模式的系統(tǒng)組件,如驅(qū)動(dòng)程序,通常使用非分頁(yè)池,其中包括 Winsock 和協(xié)議驅(qū)動(dòng)程序,例如 tcpip.sys 。每個(gè)套接字的創(chuàng)建將消耗一小部分非分頁(yè)池,用于維持套接字狀態(tài)信息。當(dāng)套接字綁定到一個(gè)地址后, TCP/IP 堆棧將分配額外的非分頁(yè)池來(lái)保存本地地址的信息。當(dāng)一個(gè)對(duì)等套接字接入后, TCP/IP 堆棧也將分配部分非分頁(yè)池來(lái)保存遠(yuǎn)程地址信息?;旧希粋€(gè)建立連接的套接字占用 2KB 非分頁(yè)池內(nèi)存 , 而 accept 或 AcceptEx 返回的套接字則占用 1.5KB 非分頁(yè)池內(nèi)存。之所以出現(xiàn)這個(gè)區(qū)別,是因?yàn)榉?wù)器本地地址信息已經(jīng)存儲(chǔ)在監(jiān)聽(tīng)套接字中,故 accept 或 AcceptEx 返回的套接字只需保存遠(yuǎn)程主機(jī)地址信息。此外,每個(gè)在套接字上投遞的重疊操作都需要給 I/O 請(qǐng)求包( IRP )分配內(nèi)存,一個(gè) IRP 使用大約 500B 非分頁(yè)池內(nèi)存。
從以上分析可以看出,為每個(gè)連接分配的非分頁(yè)池內(nèi)存并不是很大。然而,隨著客戶連接量逐增,服務(wù)器對(duì)非分頁(yè)池的使用將是非常大的。考慮運(yùn)行在只有 1GB 物理內(nèi)存的 Windows 2000 或以后版本 Windows 系統(tǒng)上的服務(wù)器,將有 256MB 的內(nèi)存非配給非分頁(yè)池。通常,非分頁(yè)池大小是機(jī)器物理內(nèi)存的 1/4 , Windows 2000 及以后版本的 Windows 系統(tǒng)上,非分頁(yè)池大小為 256MB ( /1GB ),而 Windows NT 4.0 限制為 128MB ( 1GB )。擁有 256MB 的非分頁(yè)池的服務(wù)器可以支持 50,000 或更大的連接量。但是必須限制重疊的 accept 數(shù)量,以及在已經(jīng)建立連接的重疊收發(fā)操作。在這個(gè)例子中,如果已經(jīng)建立連接的套接字,按每個(gè) 1.5KB 計(jì)算,將耗費(fèi) 75MB 的非分頁(yè)池內(nèi)存。如果采用了上面提及的投遞 0 字節(jié)接收的方法,這樣為每個(gè)連接分配的 IRP 將占用 25MB 的非分頁(yè)池內(nèi)存。
如果系統(tǒng)耗盡了非分頁(yè)池,會(huì)有兩種可能的后果。在最好的情況下, Winsock 調(diào)用將返回 WSAENOBUFS 錯(cuò)誤。最糟糕的情況是系統(tǒng)崩潰,這種情況通常是系統(tǒng)沒(méi)能正確處理內(nèi)存非配的問(wèn)題造成的。沒(méi)有一種可行的方案能夠恢復(fù)非分頁(yè)池耗盡的錯(cuò)誤,并且也沒(méi)有可行的方案來(lái)監(jiān)視非分頁(yè)池可分配的大小,因?yàn)榉欠猪?yè)池耗盡導(dǎo)致系統(tǒng)崩潰。
由以上探討,可以得出結(jié)論,沒(méi)有一種方法可以確定服務(wù)器到底支持多大的并發(fā)連接和重疊操作,并且也不可能準(zhǔn)確地獲知非分頁(yè)池是否耗盡或者鎖定內(nèi)存頁(yè)數(shù)超過(guò)極限。因?yàn)樗鼈兌紝?dǎo)致 Winsock 調(diào)用都返回相同的錯(cuò)誤 —WSAENOBUFS 。因?yàn)橐陨弦蛩?,針?duì)服務(wù)器的測(cè)試必須測(cè)試不同數(shù)量的連接情況以及重疊操作完成情況,以便在并發(fā)通信規(guī)模和數(shù)據(jù)吞吐率這兩個(gè)指標(biāo)之間選擇一種折中的方案。如果在方案中強(qiáng)加限制,以防止服務(wù)器耗盡非分頁(yè)池,則返回 WSAENOBUFS 錯(cuò)誤時(shí),我們就知道是因?yàn)槌^(guò)了鎖定頁(yè)的限制。并且可以以一種更優(yōu)化的處理方式編寫(xiě)程序,如進(jìn)一步限制一些待決的操作或關(guān)閉某些連接。
包重新排序問(wèn)題
這個(gè)問(wèn)題與伸縮性沒(méi)有多大關(guān)聯(lián),但是卻是實(shí)際通信中不得不考慮的一個(gè)問(wèn)題,因?yàn)樗婕暗侥芊裾_通信的問(wèn)題。
雖然使用完成端口的 I/O 操作總是會(huì)按照它們被提交的順序完成,但是線程調(diào)度問(wèn)題可能會(huì)導(dǎo)致關(guān)聯(lián)到完成端口上的工作不能按正常順序完成。例如,有兩個(gè) I/O 工作線程,應(yīng)該接收 “ 字節(jié)塊 1 ,字節(jié)塊 2 ,字節(jié)塊 3” ,但是你可能以錯(cuò)誤順序接收這 3 個(gè)字節(jié)塊: “ 字節(jié)塊 2 ,字節(jié)塊 1 ,字節(jié)塊 3” 。這也意味著在完成端口上投遞發(fā)送請(qǐng)求發(fā)送數(shù)據(jù)時(shí),數(shù)據(jù)實(shí)際也會(huì)以錯(cuò)誤順序被發(fā)送出去。
當(dāng)然,如果只使用一個(gè)工作線程,僅提交一個(gè) I/O 調(diào)用,是不存在順序問(wèn)題的。因?yàn)橥粫r(shí)刻,一個(gè)工作線程只能處理一個(gè) I/O 操作。但是,這樣就沒(méi)有發(fā)揮出完成端口的真正優(yōu)點(diǎn)。
如第 3 章《自定義應(yīng)用層通信協(xié)議》所述,一個(gè)簡(jiǎn)單的解決方法就是為每個(gè)封包添加一個(gè)協(xié)議頭。協(xié)議頭主要是一個(gè)封包的實(shí)際字節(jié)數(shù),如自定義 Package 包的第一個(gè)字段 m_nCmdLen 就是這個(gè)包占用的字節(jié)數(shù)。通信的接受方通過(guò)分析協(xié)議頭分析本次通信有多少數(shù)據(jù)要接收,然后繼續(xù)讀后面的數(shù)據(jù),直到一個(gè)封包被完整接收完才接收下一個(gè)封包。
當(dāng)服務(wù)器一次僅做一個(gè)異步調(diào)用時(shí),上述封包協(xié)議頭的解決方案是很有效的。但是,如果要充分發(fā)揮 IOCP 服務(wù)器的潛力,肯定有多個(gè)未決的異步讀操作等待數(shù)據(jù)的到來(lái)。這意味著,多個(gè)一步操作不能按順序完成,未決讀 I/O 返回的字節(jié)流不能按順序處理,接收到的字節(jié)流可能組合成正確的封包,也有可能組合成錯(cuò)誤的封包。因此,要解決這個(gè)問(wèn)題,還必須為提交的讀 I/O 分配序列號(hào)。
?
說(shuō)明:
本文主要譯自《 Network programming for microsoft windows 》一書(shū)的 6.2 節(jié)《可伸縮的服務(wù)器體系結(jié)構(gòu)》和 6.3 節(jié)《資源管理》。
其中包重新排序問(wèn)題,參考 王艷平著 《 Windows 網(wǎng)絡(luò)與通信程序設(shè)計(jì) 》 4.3.4 節(jié)《包重新排序問(wèn)題》 。