NCindy

.net平台上的高性能网络程序开发框架

深入讨论.NET Socket的Accept方法

    深入讨论.NET  SocketAccept方法

考虑一个问题,假如同时有50个连接请求进入一个服务器(这种情况对于普通负载的Web服务器都是很常见的)会怎么样?阻塞式I/O只能循环调用Accept,一个一个对50个连接进行Accept操作,而选择模型也是一样。异步模型呢?假如我们预先发起了100BeginAccept操作,异步模型能够同时处理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.为什么要用AcceptEx2.为什么不总是用AcceptEx而是要有条件的使用。第一个问题是因为在Winsock2种,AcceptEx的速度快过accept方法(MSDN是这么说的:A program can make a connection to a socket more quickly using AcceptEx instead of the accept function.)。第二个问题是因为AcceptExWinsock2的扩展函数,Win98和以下的版本都不支持这个函数。

放下直接调用第三种overload方式的函数不提,我们先继续往下看。接下来的三行代码是做log的,再接下来的三行代码是检查Socket对象是否已经被Disposed了。

接下来的两行代码首先构造一个AsyncResult的实例,然后设定AsyncResultStartPostingAsyncOp(开始投递异步操作)属性为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,它比较简单我把代码贴出来,

 1 private void DoBeginAccept(Socket acceptSocket, int receiveSize,
                           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对象更加降低了系统消耗。

 

posted on 2006-11-01 15:20  iceboundrock  阅读(4128)  评论(4编辑  收藏  举报

导航