并发客户端在C#套接字中发布

时间:2013-04-30 09:14:59

标签: c# sockets client-server winsock

我正在研究客户端服务器应用程序,Windows Server和Linux Client。我正在使用多个并发客户端测试我的服务器。我尝试了来自客户端的20个并发连接,我注意到尽管所有20个请求都是相同的,但仍未处理一些请求。他们进入队列,由于某种原因轮到他们客户端被关闭(客户端连接超时为5秒)。

然后我添加了一个Thread.Sleep(1000),以检查它是否真的是异步但后来我意识到它在超时之前不会处理其他请求。尽管事实

  1. 是异步的
  2. 在进入睡眠状态之前设置了ManualResetEvent。
  3. 现在我想知道我在这里缺少什么,因为这主要发生在并发连接上?

    public static void StartServer(IPAddress ipAddr, int port)
    {
        //IPEndPoint serverEndPoint = new IPEndPoint(ipAddr, port);
        IPEndPoint serverEndPoint = new IPEndPoint(IPAddress.Any, port);
        Socket clientListener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        try
        {
            clientListener.Bind(serverEndPoint);
            clientListener.Listen(500);
            Console.WriteLine("-- Server Listening: {0}:{1}",ipAddr,port);
            while (true)
            {
                resetEvent.Reset();
                Console.WriteLine("|| Waiting for connection");
                clientListener.BeginAccept(new AsyncCallback(AcceptConnection), clientListener);
                resetEvent.WaitOne();
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
    }
    
    
    public static void AcceptConnection(IAsyncResult ar)
    {
        // Get the socket that handles the client request.
        Socket listener = (Socket)ar.AsyncState;
        Socket handler = listener.EndAccept(ar);
        // Signal the main thread to continue.
        resetEvent.Set();
        // Create the state object.
        JSStateObject state = new JSStateObject();
        state.workSocket = handler;
        if (handler.Connected)
        {
            Console.WriteLine("** Connected to: {0}", handler.RemoteEndPoint.ToString());
            state.workingDirectory = JSUtilityClass.CreatetTemporaryDirectry();
            try
            {
                Thread.Sleep(1000);
                Receive(state);
            }
            catch (Exception e)
            {
                handler.Shutdown(SocketShutdown.Both);
                handler.Close();
                Console.WriteLine(e.Message);
            }
        }
    }
    

1 个答案:

答案 0 :(得分:5)

我创建了一个测试,发送了100次连接尝试,并发现了一些减慢它的速度。

为什么这么慢?

我在AcceptConnection中放置一个断点来查看callstack,就是这个

ConsoleApplication1.exe!ConsoleApplication1.Program.AcceptConnection(System.IAsyncResult ar) Line 62    C#
        System.dll!System.Net.LazyAsyncResult.Complete(System.IntPtr userToken) + 0x69 bytes    
        System.dll!System.Net.ContextAwareResult.CaptureOrComplete(ref System.Threading.ExecutionContext cachedContext, bool returnContext) + 0xab bytes    
        System.dll!System.Net.ContextAwareResult.FinishPostingAsyncOp(ref System.Net.CallbackClosure closure) + 0x3c bytes  
        System.dll!System.Net.Sockets.Socket.BeginAccept(System.AsyncCallback callback, object state) + 0xe3 bytes  
        ConsoleApplication1.exe!ConsoleApplication1.Program.StartServer(System.Net.IPAddress ipAddr, int port) Line 48 + 0x32 bytes C#

因此回调AcceptConnection从调用BeginAccept的同一个线程运行。我看了FinishPostingAsyncOp带反射器,并且它使用了异步模式,如果队列中已经有一个等待处理的套接字操作,它将在当前线程上执行,否则如果没有任何待处理的东西,它将在稍后的不同线程中处理,例如

SocketAsyncEventArgs sae = new SocketAsyncEventArgs();
sae.Completed += new EventHandler<SocketAsyncEventArgs>(SocketOperation_Completed);
if (!clientListener.AcceptAsync(sae))
    AcceptConnection(clientListener, sae); // operation completed synchronously, process the result
else
    // operation will complete on a IO completion port (different thread) which we'll handle in the Completed event

因此,当你观察到程序在这种情况下实际上是完全同步的,并且使用1秒Thread.Sleep它将需要至少100秒来接受所有连接,到那时大多数连接将超时。

解决方案

即使BeginAccept方法摘要说明

  

开始异步操作以接受传入连接   尝试。

事实证明,故事还有更多内容

来自MSDN http://msdn.microsoft.com/en-AU/library/system.net.sockets.socket.beginaccept.aspx

  

BeginAccept(Int32,AsyncCallback,Object)   开始一个异步操作来接受一个   传入连接尝试并接收发送的第一个数据块   由客户端应用程序。

因此它在触发回调之前执行了具有短暂超时的读取操作。您可以通过指定receiveSize为0.更改

来禁用此功能
clientListener.BeginAccept(new AsyncCallback(AcceptConnection), clientListener);

clientListener.BeginAccept(0, new AsyncCallback(AcceptConnection), clientListener);

加快速度,如果我们从Thread.Sleep(1000)删除AcceptConnection,那么所有连接都会被接受。

如果您将Thread.Sleep(1000)留在那里以模拟工作负载或仅用于测试,那么您可能希望准备服务器以通过执行来处理此类负载

int minWorkerThreads = 0;
int minCompletionPortThreads = 0;
ThreadPool.GetMinThreads(out minWorkerThreads, out minCompletionPortThreads);
ThreadPool.SetMinThreads(minWorkerThreads, 100);

其中100是您希望随时可用于处理套接字操作的线程数。

另外一件事,这是个人偏好的问题但是你知道你可能想从AcceptConnection中调用BeginAccept,这就不需要那个while循环了。 即改变这个

while (true)
{
    resetEvent.Reset();
    Console.WriteLine("|| Waiting for connection");
    clientListener.BeginAccept(new AsyncCallback(AcceptConnection), clientListener);
    resetEvent.WaitOne();
}

到这个

Console.WriteLine("|| Waiting for connection");
clientListener.BeginAccept(new AsyncCallback(AcceptConnection), clientListener);

并将另一个BeginAccept放入AcceptConnection

public static void AcceptConnection(IAsyncResult ar)
{
    // Get the socket that handles the client request.
    Socket listener = (Socket)ar.AsyncState;
    // start another listening operation
    listener.BeginAccept(new AsyncCallback(AcceptConnection), listener);
    ... the rest of the method
}