C#异步套接字 - 线程逻辑

时间:2017-10-13 07:34:12

标签: c# multithreading sockets asynchronous

Socket.BeginSend,Socket.BeginReceive,Socket.BeginAccept等背后的线程创建逻辑如何工作?

是为连接到我的服务器来处理代码的每个客户端创建一个新线程,还是只为每个函数创建一个线程(接受,接收,发送......)而不管有多少客户端是否连接到服务器?这样,只有在客户端1接受代码完成后才执行客户端2接受代码,等等。

这是我制作的代码,我试图更好地理解它背后的逻辑:

public class SocketServer
{
    Socket _serverSocket;
    List<Socket> _clientSocket = new List<Socket>();
    byte[] _globalBuffer = new byte[1024];

    public SocketServer()
    {
        _serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    }

    public void Bind(int Port)
    {
        Console.WriteLine("Setting up server...");
        _serverSocket.Bind(new IPEndPoint(IPAddress.Loopback, Port));
    }

    public void Listen(int BackLog)
    {
        _serverSocket.Listen(BackLog);
    }

    public void Accept()
    {
        _serverSocket.BeginAccept(AcceptCallback, null);
    }

    private void AcceptCallback(IAsyncResult AR)
    {
        Socket socket = _serverSocket.EndAccept(AR);
        _clientSocket.Add(socket);
        Console.WriteLine("Client Connected");
        socket.BeginReceive(_globalBuffer, 0, _globalBuffer.Length, SocketFlags.None, ReceiveCallback, socket);
        Accept();
    }

    private void ReceiveCallback(IAsyncResult AR)
    {
        Socket socket = AR.AsyncState as Socket;
        int bufferSize = socket.EndReceive(AR);

        string text = Encoding.ASCII.GetString(_globalBuffer, 0, bufferSize);
        Console.WriteLine("Text Received: {0}", text);

        string response = string.Empty;

        if (text.ToLower() != "get time")
            response = $"\"{text}\" is a Invalid Request";
        else
            response = DateTime.Now.ToLongTimeString();

        byte[] data = Encoding.ASCII.GetBytes(response);
        socket.BeginSend(data, 0, data.Length, SocketFlags.None, SendCallback, socket);

        socket.BeginReceive(_globalBuffer, 0, _globalBuffer.Length, SocketFlags.None, ReceiveCallback, socket);
    }

    private void SendCallback(IAsyncResult AR)
    {
        (AR.AsyncState as Socket).EndSend(AR);
    }
}

1 个答案:

答案 0 :(得分:2)

这些异步方法使用线程池中的线程来调用您的回调,一旦发生了基础事件,就会发生这种情况。在您的情况下,基础事件可能是建立了连接,或者您收到了一些数据。

当您将套接字设置为“接受”时,不需要存在任何线程。旧的同步处理方式是让一个线程在socket.Accept()上阻塞,直到连接进入,但这些Begin..()方法的目的是废除它。

这里有一个技巧,一个.Net使用的和你使用的一个:你可以注册任何WaitHandle对象(一个锁,如Semaphore,SemaphoreSlim,Mutex等)和一个带线程池的回调方法,如当WaitHandle设置好后,线程池将选择一个线程,运行你的回调,并将线程返回给线程池。请参阅ThreadPool.RegisterWaitForSingleObject()

原来许多Begin..()方法基本上做同样的事情。 BeginAccept()使用WaitHandle来知道套接字何时收到连接 - 它使用ThreadPool注册WaitHandle,然后在发生连接时调用ThreadPool线程上的回调。

每次调用Begin...()并提供回调时,您应该假设您的回调方法可以在新线程上调用,同时与您曾经做过的所有其他Begin...()调用一起调用#39; s仍然很出色。

在50个不同的套接字上拨打BeginReceive() 50次?您应该假设50个线程可以尝试同时调用您的回调方法。调用50种BeginReceive()BeginAccept()方法混合使用? 50个线程。

实际上,回调的同时调用次数将受到ThreadPool中策略集的限制,例如,它可以创建新线程的速度,它保持准备好的线程数等等。

有了这个,你应该明白在50个不同的套接字上调用BeginReceive(),但传入相同的缓冲区 - _globalBuffer - 意味着50个套接字将写入同一个缓冲区而只是创建一个它的混乱,导致任意/损坏的数据。

相反,您应该在同时BeginReceive()调用时使用唯一缓冲区。我建议做的是创建一个新类来存储单个连接的上下文 - 连接的套接字,用于读取的缓冲区,其状态等。每个新连接都会获得一个新的上下文实例。

...

仅供参考,在C#中执行异步编程的现代方法是使用API​​中的async/await关键字和匹配的async方法。与这些Begin...()方法相比,这种设计更加复杂,并且与执行环境更加深入地集成,并且回答了诸如&#34;何时调用我的回调&#34;,&#34;什么线程(s)是我的回调调用&#34;和&#34;可以同时运行多少个回调&#34;完全依赖于C#/ .Net中async / await设计的程序执行环境。