深入讨论.NET Socket的Accept方法
??? 深入討論.NET? Socket的Accept方法
考慮一個問題,假如同時有50個連接請求進入一個服務器(這種情況對于普通負載的Web服務器都是很常見的)會怎么樣?阻塞式I/O只能循環調用Accept,一個一個對50個連接進行Accept操作,而選擇模型也是一樣。異步模型呢?假如我們預先發起了100個BeginAccept操作,異步模型能夠同時處理50個連接么?MSDN沒有回答這個問題,我們只有向.NET framework的代碼來尋求解答了。
打開Reflector工具,找到Socket類型的BeginAccept方法,這個方法有三種overload,我們首先來看比較簡單的一種(由于是用Reflector工具反編譯的,代碼的局部變量名無意義):
??????? public IAsyncResult BeginAccept(AsyncCallback callback, object state)
??????? {
??????????? if (this.CanUseAcceptEx)
??????????? {
??????????????? return this.BeginAccept(0, callback, state);
?? ?????????}
??????????? if (Logging.On)
??????????? {
??????????????? Logging.Enter(Logging.Sockets, this, "BeginAccept", "");
??????????? }
??????????? if (this.CleanedUp)
??????????? {
??????????????? throw new ObjectDisposedException(base.GetType().FullName);
??????????? }
??????????? AcceptAsyncResult result1 = new AcceptAsyncResult(this, state, callback);
??????????? result1.StartPostingAsyncOp(false);
??????????? this.DoBeginAccept(result1);
??????????? result1.FinishPostingAsyncOp(ref this.Caches.AcceptClosureCache);
??????????? if (Logging.On)
??????????? {
??????????????? Logging.Exit(Logging.Sockets, this, "BeginAccept", result1);
??????????? }
??????????? return result1;
??????? }
?
函數的前三行首先判斷,是否可以直接使用AcceptEx,如果可以則調用BeginAccept的第三種overload方式。這里要說明兩點,1.為什么要用AcceptEx;2.為什么不總是用AcceptEx而是要有條件的使用。第一個問題是因為在Winsock2種,AcceptEx的速度快過accept方法(MSDN是這么說的:A program can make a connection to a socket more quickly using AcceptEx instead of the accept function.)。第二個問題是因為AcceptEx是Winsock2的擴展函數,Win98和以下的版本都不支持這個函數。
放下直接調用第三種overload方式的函數不提,我們先繼續往下看。接下來的三行代碼是做log的,再接下來的三行代碼是檢查Socket對象是否已經被Disposed了。
接下來的兩行代碼首先構造一個AsyncResult的實例,然后設定AsyncResult的StartPostingAsyncOp(開始投遞異步操作)屬性為false。終于,我們到了最關鍵的地方執行實際的DoBeginAccept操作。用Reflector打開這個方法,我們看到這個方法最關鍵的思路是把Socket設置到非阻塞模式,然后將應用程序投遞的Accept請求放入Accept隊列中,為了避免阻塞,.NET framework使用WSAEventSelect和線程池共同配合,一旦有新連接進入,WSAEventSelect會觸發Socket.AcceptCallback方法的執行,逐個處理AcceptQueue中的Accept請求。因為此時Socket處于非阻塞模式,無論是否有未決的連接,Accept都會會立即返回,在沒有未決連接的情況下,Accept會返回一個WouldBlock的錯誤代碼。如果出現了這樣的錯誤,就繼續等待下一次的WSAEventSelect事件觸發。討論了這么長,大家應該對public IAsyncResult BeginAccept(AsyncCallback callback, object state)的原理清楚了吧,這種overload方式依然未能解決逐個調用Accept來處理未決連接的問題,雖然比阻塞式和選擇式I/O有所改善,但是在不能調用AcceptEx的情況下,性能依然會有所損失。那么讓我們繼續來看看另外兩種BeginAccept的情況吧。
?public IAsyncResult BeginAccept(int receiveSize, AsyncCallback callback, object state)
?{
?????? return this.BeginAccept(null, receiveSize, callback, state);
?}
這個overload形式直接調用了第三種,我們也直接來分析第三種BeginAccept。
public IAsyncResult BeginAccept(Socket acceptSocket,
?int receiveSize,
?AsyncCallback callback,
?object state)
{
??? if (Logging.On)
??? {
???? ???Logging.Enter(Logging.Sockets, this, "BeginAccept", "");
??? }
??? if (this.CleanedUp)
??? {
??????? throw new ObjectDisposedException(base.GetType().FullName);
??? }
??? if (receiveSize < 0)
??? {
??????? throw new ArgumentOutOfRangeException("size");
??? }
AcceptOverlappedAsyncResult result1 =
new AcceptOverlappedAsyncResult(this, state, callback);
??? result1.StartPostingAsyncOp(false);
??? this.DoBeginAccept(acceptSocket, receiveSize, result1);
??? result1.FinishPostingAsyncOp(ref this.Caches.AcceptClosureCache);
??? if (Logging.On)
??? {
??????? Logging.Exit(Logging.Sockets, this, "BeginAccept", result1);
??? }
??? return result1;
}
?
前面的幾行代碼依舊是log、檢查Socket狀態和參數有效性,我們直接來分析最核心的DoBeginAccept(acceptSocket, receiveSize, result1)方法。使用Reflector查看這個DoBeginAccept,它比較簡單我把代碼貼出來,
????????????????????????? ?AcceptOverlappedAsyncResult?asyncResult)
?2?{
?3???????int?num2;
?4???????if?(!ComNetOS.IsWinNt)
?5???????{
?6?????????????throw?new?PlatformNotSupportedException(SR.GetString("WinNTRequired"));
?7???????}
?8???????if?(this.m_RightEndPoint?==?null)
?9???????{
10?????????????throw?new?InvalidOperationException(SR.GetString("net_sockets_mustbind"));
11???????}
12???????if?(!this.isListening)
13???????{
14?????????????throw?new?InvalidOperationException(SR.GetString("net_sockets_mustlisten"));
15???????}
16???????if?(acceptSocket?==?null)
17???????{
18?????????????acceptSocket?=?new?Socket(this.addressFamily,?this.socketType,?this.protocolType);
19???????}
20???????else?if?(acceptSocket.m_RightEndPoint?!=?null)
21???????{
22?????????????throw?new?InvalidOperationException(SR.GetString("net_sockets_namedmustnotbebound",?
??????????????????????? new?object[]?{?"acceptSocket"?}));
23???????}
24???????asyncResult.AcceptSocket?=?acceptSocket;
25???????int?num1?=?this.m_RightEndPoint.Serialize().Size?+?0x10;
26???????byte[]?buffer1?=?new?byte[receiveSize?+?(num1?*?2)];
27???????asyncResult.SetUnmanagedStructures(buffer1,?num1);
28???????SocketError?error1?=?SocketError.Success;
29???????if?(!UnsafeNclNativeMethods.OSSOCK.AcceptEx(this.m_Handle,?
????????????????????? acceptSocket.m_Handle,
???????????????????? ?Marshal.UnsafeAddrOfPinnedArrayElement(asyncResult.Buffer,?0),
???????????????????? ?receiveSize,
????????????????????? num1,?num1,?out?num2,?asyncResult.OverlappedHandle))
30???????{
31?????????????error1?=?(SocketError)?Marshal.GetLastWin32Error();
32???????}
33???????error1?=?asyncResult.CheckAsyncCallOverlappedResult(error1);
34???????if?(error1?!=?SocketError.Success)
35???????{
36?????????????SocketException?exception1?=?new?SocketException(error1);
37?????????????this.UpdateStatusAfterSocketError(exception1);
38?????????????if?(Logging.On)
39?????????????{
40???????????????????Logging.Exception(Logging.Sockets,?this,?"BeginAccept",?exception1);
41?????????????}
42?????????????throw?exception1;
43???????}
44?}
45?
46??
47?
前面是一堆參數有效性檢查、對象狀態檢查、操作系統環境有效性檢查的代碼。接著初始化了接收數據緩沖區,調用AcceptEx方法,處理錯誤,直截了當。也就是說,對于這個overload方式,BeginAccept不是自己逐個處理未決連接,而是交給了操作系統內核來完成,會有更好的效率。并且,這個方法的第一個參數,使我們有可能重用Socket對象,由于創建Socket對象是一個相對比較耗時的操作,所以在需要處理大量連接的服務器程序中,能夠重用Socket對象更加降低了系統消耗。
?
轉載于:https://www.cnblogs.com/ncindy/archive/2006/11/01/546828.html
總結
以上是生活随笔為你收集整理的深入讨论.NET Socket的Accept方法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 沦落就沦落是什么歌?
- 下一篇: C#读取XML点滴