c# - .NET Socket ReadAsync 在写循环 Async/Await 期间被阻塞

标签 c# sockets asynchronous async-await

我正在使用 Async/Await 编写一个 TCP 服务器,该服务器需要根据从每个客户端接收到的内容向连接的客户端发送消息列表。在发送给客户端的每条消息之间,我需要:

  • 等待确认/响应,然后发送下一条消息
  • 如果 5 秒后没有确认,则重新发送命令

  • 为此,我设置了 ResponseReceived当预期的响应到来时,我的 ConnClient 类上的属性。然后,在 ConnClient.SendListAsync例程,我正在检查发送每个命令后属性是否已更改。但是,直到 SendListAsync 才会读取传入的响应。发送所有消息,如下面的调试语句所示:
    Sending Initial Message.
    Received response, generate list of 3 initial commands and send them.
    SendListAsync 5 second timeout w/o response.
    SendListAsync 5 second timeout w/o response.
    SendListAsync 5 second timeout w/o response.
    Received response.
    Received response.
    Received response.
    

    问题:如何正确预防ConnClient.SendListAsync阻止传入的读取?
    public class Svr
    {
        TcpListener listener;
        public async Task Listen(IPAddress iP, int port)
        {
            listener = new TcpListener(iP, port);
            listener.Start();
            while (true)
            {
                TcpClient client = await listener.AcceptTcpClientAsync();
                ConnClient cc = new ConnClient(client);
                await Receive(ConnClient);
            }
        }
    
        async Task Receive(ConnClient cc)
        {
            var headerSize = sizeof(short);
            byte[] buffer = new byte[4000];
    
            //Send initial msg
            await cc.socket.GetStream().WriteAsync(Strings.InitialMsg, 0, Strings.InitialMsg.Length); 
    
            while (true)
            {
                buffer = new byte[headerSize];
                if (!await ReadToBuffer(cc.socket.GetStream(), buffer, headerSize))
                    return;
    
                var length = BitConverter.ToUInt16(new byte[2] { buffer[1], buffer[0] }, 0 );
                buffer = new byte[length];
    
                if (!await ReadToBuffer(cc.socket.GetStream(), buffer, length))
                    return;
    
                await DoSomethingBasedOnReceived(messageBuffer, cc);
            }
        }
    
        async Task<Boolean> ReadToBuffer(NetworkStream stream, byte[] buffer, int bytesToRead)
        {
            int offset = 0;
            while (offset < bytesToRead)
            {
                var length = await stream.ReadAsync(buffer, offset, bytesToRead - offset);
                if (length == 0)
                    return false;
                offset += length;
            }
            return true;
        }
    
        public async Task DoSomethingBasedOnReceived(byte[]  messageBuffer, ConnClient cc)
        {
            await SomeLogicToSetTheRRFlagIfMessageApplicable(messageBuffer, cc);
            List<byte[]> ListOfMessagesToSend = SomeLogicToDetermineListOfMessages(messageBuffer);
            await cc.SendListAsync(ListOfMessagesToSend);
        }
    }
    

    ConnClient 类,代表单个连接的客户端。
    public class ConnClient
    {
        public TcpClient socket { get; set; }
        public Boolean ResponseReceived { get; set; }
        public ConnClient (TcpClient cc)
        {socket = cc}
    
        public async Task SendListAsync(List<byte[]> messageList)
        {
            foreach (byte[] msg in messageList)
            {
                this.ResponseReceived = false;
                await stream.WriteAsync(msg, 0, msg.Length);
    
                int waitedSoFar = 0;
                while (waitedSoFar < 5000)
                {
                    if (this.ResponseReceived == true)
                    {
                        break;
                    }
                    waitedSoFar += 100;
                    await Task.Delay(100);
                }
            }
        }
    }
    

    最佳答案

    您的第一个问题是您将无法接受新客户。

    while (true)
    {
            // accept the next connection
            TcpClient client = await listener.AcceptTcpClientAsync();
    
            // receive and send list
            ConnClient cc = new ConnClient(client);
            await Receive(ConnClient);
    
            // the loop cannot continue to receive the next connection 
            // until you have done with your receive
    }
    

    您需要执行 Receive独立,因此您可以等待下一个连接,您可以在没有 await 的情况下调用它( 作为异步 void 运行),或将其卸载到新任务。

    删除等待
    Receive(ConnClient);
    

    卸载
    Task.Run(() => Receive(ConnClient));
    

    您的第二个问题是您的客户在发送时被阻止并且无法接收。再一次,您将要么卸载,要么在没有等待的情况下运行。

    @PeterDuniho提到

    Given that the OP is already using async/await, and given that Receive() is already async, there's no reason to use Task.Run(). It's fire-and-forget either way (unless they change their code to store the returned task), so they might as well just fire-and-forget the call to Receive() as wrap it in a call to Task.Run().



    备注 :创建可扩展的客户端/服务器套接字解决方案并非易事,我也不想展示这一点。但是,它将解决您当前的问题。

    无论哪种方式,都要非常注意错误。由于两种建议的解决方案都将在未观察到的情况下运行,因此会出现 异常。需要处理

    关于c# - .NET Socket ReadAsync 在写循环 Async/Await 期间被阻塞,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59042316/

    相关文章:

    c++ - 如何使用 IOCP 发送文件?

    java - 如何运行一个允许传入连接的简单套接字服务器?

    Android从网络加载图像到viewpager

    events - InDesign CC 2014 扩展脚本 : How to reliably close document after asynchronous PDF export

    c# - 使用 .pfx 证书封装、签名和创建 PKCS#7 DER 消息

    python - 什么反ddos安全系统python用于套接字TCP连接?

    c# - Novell LDAP C# - Novell.Directory.Ldap - 有人让它工作了吗?

    javascript - 这段 AngularJS 代码的目的是什么?

    c# - Unity3D - 通过单击对象将其移动到相反方向

    c# - 使用包含相同 Entity MVC Core 的 1Entity 创建 Dbo