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

标签 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);
    }
}

最佳答案

这些类型的异步方法使用线程池中的线程来调用您的回调,一旦底层事件发生,无论它是什么。在您的情况下,基础事件可能是建立了连接,或者您收到了一些数据。

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

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

事实证明,这些 Begin..() 方法中的许多基本上都做同样的事情。 BeginAccept() 使用 WaitHandle 来了解套接字何时收到连接 - 它向 ThreadPool 注册 WaitHandle,然后在连接发生时调用 ThreadPool 线程上的回调。

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

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

实际上,同时调用回调的次数将受到 ThreadPool 中设置的策略的限制,例如,它可以多快地创建新线程,它保持多少线程处于事件状态,准备好运行,等等。

这样,您应该明白在 50 个不同的套接字上调用 BeginReceive(),但传入相同的缓冲区 - _globalBuffer - 意味着 50 个套接字将要写入到同一个缓冲区,然后把它弄得一团糟,导致任意/损坏的数据。

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

...

仅供引用,在 C# 中执行异步编程的现代方法是使用 async/await 关键字并匹配 API 中的 async 方法。该设计比这些 Begin...() 方法更复杂,并且与执行环境的集成更深入,并且对诸如“我的回调何时被调用”、“什么线程”等问题的回答(s) 是我调用的回调”,以及“可能同时运行多少个回调”完全取决于 C#/.Net 中的异步/等待设计导致的程序执行环境。

关于C# 异步套接字 - 线程逻辑,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46724792/

相关文章:

c - 为用户空间线程库编写调度程序

python - 无限递归 vs while True?

java - 页面刷新时"Address already in use"

c# - 在 C# 中从 FLV 流中提取音频

c# - 如何在 .NET Core 上正确实现 kafka 消费者作为后台服务

c# - 找出图像之间的差异

c# - 无法读取 Excel 文件 - File.open 无效

java - 如何用 RXJava 替换 Thread.sleep()

Java客户端从C服务器接收,但C服务器不从Java客户端接收

java - java中的TCP服务器和TCP客户端