【问题标题】:async/await Tcp socket implement accepts only one connectionasync/await Tcp 套接字实现只接受一个连接
【发布时间】:2016-08-25 16:34:50
【问题描述】:

我习惯于开始/结束 APM 模式,我想将我的套接字服务器更新为 .Net 4.5/async/await。我从互联网资源中编写了一个示例代码,但它不起作用。

我希望所有已连接的客户端在被接受连接后都分离到自己的类(尚未实现..)。接受所有传入连接的循环在自己的线程中运行。

基本上 Main.cs 是我接受客户端、为连接创建新类 (Client.cs/Session.cs) 并将接受的客户端指向该类的地方。好吧,这就是我打算做的,它不在代码中,目前的主要问题是我知道如何处理这个接受序列以及为什么我不能同时连接多个客户端?希望你能指出我正确的答案。

提前谢谢你。

代码

Form1.cs

using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Server
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            // Own thread for accepting connections

            Main ConnectionLoop = new Main(10);
            Thread thread = new Thread(new ThreadStart(ConnectionLoop.PrepareThread));
            thread.IsBackground = true;
            thread.Start();
        }
    }
}

Main.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Server
{
    public class Main
    {
        private int m_backLog = 0;

        // constructor
        public Main(int Backlog)
        {
            m_backLog = Backlog;
            Console.WriteLine("Main class created, backlog: {0}", Backlog);
        }

        public void PrepareThread()
        {
            Console.WriteLine("Thread created");
            StartAccepting(CancellationToken.None).Wait();
        }

        private async Task StartAccepting(CancellationToken token)
        {
            Console.WriteLine("Started listening..");
            CancellationTokenSource cts = new CancellationTokenSource();
            TcpListener listener = new TcpListener(IPAddress.Any, 6112);
            listener.Start();
            await AcceptClientsAsync(listener, cts.Token);
            //  Thread.Sleep(600000);
        }

        private async Task AcceptClientsAsync(TcpListener listener, CancellationToken token)
        {
            var clientCounter = 0;
            while (!token.IsCancellationRequested)
            {
                TcpClient client = await listener.AcceptTcpClientAsync().ConfigureAwait(false);
                clientCounter++;
                Console.WriteLine("Client {0} accepted!", clientCounter);
                await EchoAsync(client, clientCounter, token);
            }
        }

        private async Task EchoAsync(TcpClient client, int clientCounter, CancellationToken token)
        {
            using (client)
            {
                var buf = new byte[4096];   // buffer for stream
                var stream = client.GetStream();    // stream itself

                while (!token.IsCancellationRequested)
                {
                    // some conditions we don't know is client connected, lets have timeout
                    var timeoutTask = Task.Delay(TimeSpan.FromSeconds(15));
                    var amountReadTask = stream.ReadAsync(buf, 0, buf.Length, token);
                    var completedTask = await Task.WhenAny(timeoutTask, amountReadTask).ConfigureAwait(false);

                    if (completedTask == timeoutTask)
                    {
                        var msg = Encoding.ASCII.GetBytes("Client timed out");
                        await stream.WriteAsync(msg, 0, msg.Length);
                        break;
                    }

                    var amountRead = amountReadTask.Result;
                    if (amountRead == 0) break; // end of stream
                    await stream.WriteAsync(buf, 0, amountRead, token).ConfigureAwait(false);
                }
            }
            Console.WriteLine("Client {0} disconnected", clientCounter);
        }
    }
}

【问题讨论】:

    标签: c# sockets asynchronous tcp async-await


    【解决方案1】:

    异步适用于长时间运行的进程,可防止对长时间运行的 I/O 绑定进程进行不必要的等待。它提供任何类型的并发。它只是释放 CPU,所以它不会坐在那里等待。

    因此,您需要利用 TPL(任务并行库)的其余部分来提供允许并发客户端所需的并发性。可能这意味着一旦连接发生就为客户端分离一个任务,并使用该任务来管理客户端。

    异步可以通过确保每个客户端不会阻塞一个完整的线程来补充这一点,但异步本身只能帮助您不阻塞,它不提供并发性。

    首先,我强烈建议您阅读 MSDN 上有关 TPL 的所有信息。它很多,但它是很好的阅读,将有很大的帮助。 https://msdn.microsoft.com/en-us/library/dd460717(v=vs.110).aspx

    至于更具体的例子,我可以试试。 在您接受客户端的循环中,您将希望在连接客户端后立即获得并行性。这将使您的运行循环能够返回到接受客户端,并让客户端仍然交互。比如:

        private async Task AcceptClientsAsync(TcpListener listener, CancellationToken token)
        {
            var clientCounter = 0;
            while (!token.IsCancellationRequested)
            {
                TcpClient client = await listener.AcceptTcpClientAsync().ConfigureAwait(false);
                clientCounter++;
                Console.WriteLine("Client {0} accepted!", clientCounter);
                Task.Run(async () => await EchoAsync(client, clientCounter, token), token);
            }
        }
    

    注意这是伪代码。我没有尝试编译它。但这个概念是可靠的。

    如果您有大量客户端,则需要比仅使用 Task.Run 更具体,但对于示例,它可以正常工作。它将利用线程池线程进行并行处理。至少可以运行 100 次,但之后性能会下降。

    【讨论】:

    • 感谢您的回复!你想给我一个粗略的例子,甚至是并发异步套接字的伪代码吗?
    【解决方案2】:

    async 函数返回在该函数完成时完成的任务。

    在您的 AcceptClientsAsync 函数中,您正在 awaiting EchoAsync 函数。这意味着直到EchoAsync 完成之后(即,在发出取消令牌之后),AcceptTcpClientAsync 才会再次被调用。

    对于异步套接字服务器,您应该有一个仅在循环中接受的任务,并且对于每个连接,您应该有一个“处理”任务。这些必须是独立的——你不能await接受任务的处理任务。

    更新:根据要求添加示例:

    private async Task AcceptClientsAsync(TcpListener listener, CancellationToken token)
    {
        var clientCounter = 0;
        while (!token.IsCancellationRequested)
        {
            TcpClient client = await listener.AcceptTcpClientAsync().ConfigureAwait(false);
            clientCounter++;
            Console.WriteLine("Client {0} accepted!", clientCounter);
            var echoTask = EchoAsync(client, clientCounter, token);
            // TODO: save the echoTask in some kind of per-client data structure.
        }
    }
    

    【讨论】:

    • 谢谢你的回复,你能举个例子吗?这个任务和异步/等待让我很困惑
    猜你喜欢
    • 1970-01-01
    • 2013-10-10
    • 2011-08-31
    • 2023-03-21
    • 2021-09-10
    • 1970-01-01
    • 2021-12-16
    • 1970-01-01
    相关资源
    最近更新 更多