日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Winsock服务器设计的四个关键问题

發布時間:2025/5/22 编程问答 19 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Winsock服务器设计的四个关键问题 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

6.2.1 接受連接的方法

Winsock擴展函數AcceptEx是唯一能夠使用重疊I/O接受客戶連接的函數。下面主要深入探討使用該函數接收連接的問題。

前面已經討論過,當客戶連接進來時,服務器需要創建一個套接字來負責維護與一個客戶端的會話。使用AcceptEx函數之前必須創建一些套接字,并且這些套接字必須是未綁定、未連接的,即使它們可能在調用TransmitFile, TransmitPackets, DisconnectEx后可以重用。

響應服務器必須總是具有足夠的AcceptEx在站崗,以便在有客戶連接請求時調用。但是,并沒有具體的數量能夠保證服務器能夠立即響應連接。我們知道在調用listen將監聽套接字置于監聽狀態后,TCP/IP堆棧會自動接受到來的連接,直到達到listenbacklog參數設定的限制。對于Windows NT服務器而言,支持的backlog的最大值為200。如果服務器投遞了15AcceptEx調用,然后突然有50個客戶請求連接服務器,它們的連接請求都不會遭到拒絕。服務器投遞的AcceptEx I/O會滿足前面的15個連接,剩下的35個連接都被系統默認連接了。檢查一下backlog的值發現,系統還有能力默認接受165個連接。之后,如果服務器投遞AcceptEx調用,它們會立即成功返回,因為系統會將默認接收的連接放入等待連接隊列中。

服務器的特性是決定要投遞多少個AcceptEx操作的重要因素。例如,希望處理大量短時間即時連接的客戶要比處理少量長時間連接的客戶投遞更多的AcceptEx I/O。一個好的策略是允許AcceptEx的調用數量在最小值和最大值之間變化。具體做法是,應用程序跟蹤未決的AcceptEx I/O的數量,當一個或多個I/O完成使這個未決I/O數量變得比最小值還小時,就再投遞額外的AcceptEx I/O

Windows 2000和以后的Windows操作系統版本中,Winsock提供了一種機制,用來確定應用程序是否投遞了足夠的AcceptEx調用。創建監聽套接字時,使用WSAEventSelect函數為監聽套接字關聯一個事件對象,注冊FD_ACCEPT事件。如果投遞的AcceptEx操作用完,但是仍有客戶請求接入(系統根據backlog值決定是否接受這些連接),事件對象就是受信,說明應該投遞額外的AcceptEx操作了。這實際上還是利用事件對象來使調用線程處于一種可警告狀態,當有客戶連接請求時,就根據當前AcceptEx操作是否用完來警告(通知)是否需要投遞新的AcceptEx操作來處理新的客戶連接。

使用AcceptEx處理連接的另外一個功能就是在處理連接時還可以接收用戶發來的第一塊數據(前提是為AcceptEx提供了接收緩沖區),這對于那些請求連接的同時發送了一些數據過來的客戶來說很適用。但是,此時,除非接收連接的同時接收到了客戶發送過來的一些數據,否則AcceptEx是不會返回的。

為了滿足客戶的需求,服務器不得不投遞更多的接受I/O,這會占用大量的系統資源。如果客戶僅調用connect函數連接服務器,長時間既不發送數據,也不關閉連接,就可能造成AcceptEx投遞的大量重疊I/O操作不能返回。這就是惡意連接。為此,服務器應該記錄每個AcceptEx投遞的未決I/O,定時掃描它們,設置SO_CONNECT_TIME參數調用getsockopt檢查它們連接的時間,如果超時,就將連接關閉。如果使用WSAEventSelect模型來通知有連接事件,則當事件受信時,是檢查客戶套接字(AcceptSocket)是否真正連接了。

每當調用AcceptEx接受客戶端連接時,它也在等待接受客戶發送過來的第一個數據塊,這時不允許投遞另外一個AcceptEx。當AcceptEx返回后,如果事件對象再次受信則表明有新的連接到來。需要注意的是,無論何時,千萬不要關閉一個調用AcceptEx還沒有返回的套接字(AcceptSocket),因為這會導致內存泄露。因為從內部執行邏輯看,當沒有連接的套接字句柄被關閉時,調用AcceptEx所涉及到的內核模式的數據結構并不會清除掉,直到有新的連接建立或者監聽套接字被關閉。

盡管在一個等待完成通知的工作者線程中,投遞一個AcceptEx操作,看起來既簡單又合情合理,但是應盡量避免這樣做,因為創建套接字還是很耗費資源的。另外,也不要在工作者線程中進行任何復雜的計算,以便處理器可以盡快的在接到完成通知后進行后續處理。創建套接字耗費資源的一個原因在于Winsock 2.0本身的架構很復雜,成功地創建一個套接字可能需要調用很多內核服務。因此,服務器應該在單獨線程中創建套接字,投遞AcceptEx操作。當調用線程投遞的AcceptEx重疊操作完成時,一個受信的事件將會通知處理線程。

6.2.2 數據傳輸問題

數據傳輸是通信程序執行的核心操作。當一個客戶與服務器建立連接后,它們的主要工作就是傳輸數據,因為數據是信息的表示。由上一節幾種I/O模型的性能測試分析可知,當連接數量很大時,數據吞吐量是一個重要的性能考核指標。

從性能角度考慮,所有的數據傳輸最好都應采用重疊I/O處理。默認情況下,系統為每個socket分配一個的接受緩沖區和一個發送緩沖區,用來緩存接收和發送的數據。但在重疊I/O中,這些緩沖區往往不用,可以傳遞參數SO_SNDBUFSO_RCVBUF調用setsockopt,來將它們設置為0

讓我們來看看,當發送緩沖區沒有設置為0時,系統是怎么處理一個典型的send操作的。當一個應用程序調用send函數時,如果有充足的緩沖空間,需要發送的數據將被拷貝到套接字的發送緩沖區,send函數立即成功返回,并且一個完成通知被拋出。另外一個方面,如果套接字的發送緩沖區已滿,則應用程序提供的發送緩沖區被鎖定,再次對send函數的調用將會返回WSA_IO_PENDING錯誤。當發送緩沖區中的數據被處理(例如,提交給傳輸層處理)時,Winsock實際上直接處理鎖定在緩沖區中的數據,也即繞過套接字的發送緩沖區,直接從應用程序緩沖區中提交數據給傳輸層。

接收數據的情況恰好相反。當一個重疊的receive請求拋出后,如果數據已經接收成功,它會被緩存在套接字接收緩沖區。數據會拷貝到應用程序緩沖區(直到飽和)。receive調用返回,并且一個完成通知被拋出。當套接字緩沖區被設置為空時,如果調用重疊的receive操作將返回WSA_IO_PENDING錯誤。當有數據到達時,它將繞過套接字緩沖區而直接被拷貝到應用程序緩沖區。

設置單套接字緩沖區為0,并不能提高性能,因為只要一直有大量的重疊接發請求被拋出,就不會有額外的內存拷貝。設置套接字發送緩沖區為空比設置套接字接收緩沖區為空對系統的性能影響要小。因為應用程序的發送緩沖區會被經常鎖定直到它被提交給傳輸層處理。然而,若將接收緩沖區設置為0,并且沒有重疊的receive調用,任何傳進來的數據只能緩存在傳輸層。傳輸層驅動程序只會緩存滑動窗口尺寸的數據,即17KB—傳輸層可以分配的緩沖區大小的上限。實際的緩沖區要比17KB小。傳輸層緩沖區(針對一次連接)是在非分頁池之外分配的,這意味著,當服務建立了1000個連接時,即使沒有拋出receive請求,非分頁池中也會分配17MB的內存。而非分頁池是很珍貴的資源,除非服務器可以保證總是有接收請求拋出,否則套接字接收緩沖區應該不需設置。

只有在一些特殊情況下,對套接字接收緩沖區不予設置將會導致性能降低。考慮服務器需要處理成千上萬個客戶連接,而每個連接上又都沒有投遞receive請求的情況,如果客戶端零星地發送數據過來,傳輸進來的數據將被緩存在套接字接收緩沖區中。當服務器處理一個receive重疊I/O時,它會做一些不必要的工作。當完成通知到達時,重疊操作會處理一個I/O請求包(IRP)。在這種情形下,服務器不能保留很多拋出的receive請求。因此,最好使用簡單的非阻塞接收函數。

6.3 內存資源管理問題

由于機器硬件條件所限,系統資源是有限的,因此不得不考慮內存資源的管理問題。從上一節對不同I/O模型進行的性能測試結果分析可知,維持大規模的通信連接,不僅會耗費掉大量內存,而且對CPU的占用也是很高的。

對于配置比較高的服務器而言,處理成千上萬個連接并不成問題。但是隨著連接量的劇增,內存資源的限制將逐漸凸現。最有可能遇到的兩個限制因素就是鎖定頁和非分頁池。鎖定頁的限制不是太嚴重,更應該避免的是非分頁池被耗盡。每一次調用重疊的sendreceive請求,提交的緩沖區都可能被鎖住。當內存被鎖定時,它就不能從物理內存換出。操作系統對鎖定內存的數量是有限制的,當達到極限時,重疊操作將會返回WSAENOBUFS錯誤。如果服務器在每個連接上投遞多個重疊接收操作,隨著客戶連接數量的增多,極限就會達到。如果期望服務器能夠處理高并發通信,服務器可以在每個連接上投遞一個0字節的接受操作,這樣就不會有內存鎖定。0字節的接受完成以后,服務器可以簡單地執行一個非阻塞的接收函數來獲取緩存在套接字接收緩沖區中的所有數據。當非阻塞接收調用返回WSAEWOULDBLOCK時,就表示不再有未決的數據了。這種方法非常適合用來設計那些希望通過犧牲每個套接字上的吞吐率來獲取更大規模并發連接的服務器。

當然,最好還要了解客戶端與服務器通信的方式。在上面的例子中,當0字節的接收完成后,再投遞一個異步接收操作,將接收到所有緩存在套接字接收緩沖區中的數據。如果服務器知道客戶端將會連續不斷發送數據,那么當0字節的接收完成后,假如客戶端將發送大數據塊(超過單套接字緩沖區8KB的容量)過來,服務器將拋出一個或多個重疊的接收操作。

另外一個需要重點考慮的問題就是系統所需頁的數量。當系統鎖定傳遞給重疊操作的內存時,它是在頁邊界上進行的。在x86體系結構上,內存頁的大小為4KB。如果一個操作投遞了1KB的緩沖區,系統實際上會為它鎖定4KB大小的內存塊。為避免這種浪費,重疊發送和接收緩沖區的大小應該是頁大小的倍數。可以使用GetSystemInfo這個API來獲知當前系統頁的大小。

如果突破非分頁池極限,將會導致更嚴重的錯誤,并且很難恢復。非分頁池是內存的一部分,它常駐內存,并且永遠不會被交換出去。內核模式的系統組件,如驅動程序,通常使用非分頁池,其中包括Winsock和協議驅動程序,例如tcpip.sys。每個套接字的創建將消耗一小部分非分頁池,用于維持套接字狀態信息。當套接字綁定到一個地址后,TCP/IP堆棧將分配額外的非分頁池來保存本地地址的信息。當一個對等套接字接入后,TCP/IP堆棧也將分配部分非分頁池來保存遠程地址信息。基本上,一個建立連接的套接字占用2KB非分頁池內存,acceptAcceptEx返回的套接字則占用1.5KB非分頁池內存。之所以出現這個區別,是因為服務器本地地址信息已經存儲在監聽套接字中,故acceptAcceptEx返回的套接字只需保存遠程主機地址信息。此外,每個在套接字上投遞的重疊操作都需要給I/O請求包(IRP)分配內存,一個IRP使用大約500B非分頁池內存。

從以上分析可以看出,為每個連接分配的非分頁池內存并不是很大。然而,隨著客戶連接量逐增,服務器對非分頁池的使用將是非常大的。考慮運行在只有1GB物理內存的Windows 2000或以后版本Windows系統上的服務器,將有256MB的內存非配給非分頁池。通常,非分頁池大小是機器物理內存的1/4Windows 2000及以后版本的Windows系統上,非分頁池大小為256MB/1GB),而Windows NT 4.0限制為128MB1GB)。擁有256MB的非分頁池的服務器可以支持50,000或更大的連接量。但是必須限制重疊的accept數量,以及在已經建立連接的重疊收發操作。在這個例子中,如果已經建立連接的套接字,按每個1.5KB計算,將耗費75MB的非分頁池內存。如果采用了上面提及的投遞0字節接收的方法,這樣為每個連接分配的IRP將占用25MB的非分頁池內存。

如果系統耗盡了非分頁池,會有兩種可能的后果。在最好的情況下,Winsock調用將返回WSAENOBUFS錯誤。最糟糕的情況是系統崩潰,這種情況通常是系統沒能正確處理內存非配的問題造成的。沒有一種可行的方案能夠恢復非分頁池耗盡的錯誤,并且也沒有可行的方案來監視非分頁池可分配的大小,因為非分頁池耗盡導致系統崩潰。

由以上探討,可以得出結論,沒有一種方法可以確定服務器到底支持多大的并發連接和重疊操作,并且也不可能準確地獲知非分頁池是否耗盡或者鎖定內存頁數超過極限。因為它們都將導致Winsock調用都返回相同的錯誤—WSAENOBUFS。因為以上因素,針對服務器的測試必須測試不同數量的連接情況以及重疊操作完成情況,以便在并發通信規模和數據吞吐率這兩個指標之間選擇一種折中的方案。如果在方案中強加限制,以防止服務器耗盡非分頁池,則返回WSAENOBUFS錯誤時,我們就知道是因為超過了鎖定頁的限制。并且可以以一種更優化的處理方式編寫程序,如進一步限制一些待決的操作或關閉某些連接。

包重新排序問題

這個問題與伸縮性沒有多大關聯,但是卻是實際通信中不得不考慮的一個問題,因為它涉及到能否正確通信的問題。

雖然使用完成端口的I/O操作總是會按照它們被提交的順序完成,但是線程調度問題可能會導致關聯到完成端口上的工作不能按正常順序完成。例如,有兩個I/O工作線程,應該接收字節塊1,字節塊2,字節塊3”,但是你可能以錯誤順序接收這3個字節塊:字節塊2,字節塊1,字節塊3”。這也意味著在完成端口上投遞發送請求發送數據時,數據實際也會以錯誤順序被發送出去。

當然,如果只使用一個工作線程,僅提交一個I/O調用,是不存在順序問題的。因為同一時刻,一個工作線程只能處理一個I/O操作。但是,這樣就沒有發揮出完成端口的真正優點。

如第3章《自定義應用層通信協議》所述,一個簡單的解決方法就是為每個封包添加一個協議頭。協議頭主要是一個封包的實際字節數,如自定義Package包的第一個字段m_nCmdLen就是這個包占用的字節數。通信的接受方通過分析協議頭分析本次通信有多少數據要接收,然后繼續讀后面的數據,直到一個封包被完整接收完才接收下一個封包。

當服務器一次僅做一個異步調用時,上述封包協議頭的解決方案是很有效的。但是,如果要充分發揮IOCP服務器的潛力,肯定有多個未決的異步讀操作等待數據的到來。這意味著,多個一步操作不能按順序完成,未決讀I/O返回的字節流不能按順序處理,接收到的字節流可能組合成正確的封包,也有可能組合成錯誤的封包。因此,要解決這個問題,還必須為提交的讀I/O分配序列號。

?

說明:

本文主要譯自《Network programming for microsoft windows》一書的6.2節《可伸縮的服務器體系結構》和6.3節《資源管理》。

其中包重新排序問題,參考王艷平著Windows網絡與通信程序設計4.3.4節《包重新排序問題》

轉載于:https://www.cnblogs.com/duzouzhe/archive/2009/11/11/1601022.html

總結

以上是生活随笔為你收集整理的Winsock服务器设计的四个关键问题的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。