【问题标题】:Ho to create an async TCP server in a better way than this?如何以比这更好的方式创建异步 TCP 服务器?
【发布时间】:2021-07-15 14:47:59
【问题描述】:

我有一个服务器应用程序来接受来自一组大约 10000 个客户端(实际上是具有 GPRS 接口的现场设备)的 TCP 连接。

接受连接的部分是这样的:

public async System.Threading.Tasks.Task AcceptConnections()
{
    _listenerProxy.StartListen();
    var taskList = new List<System.Threading.Tasks.Task>();
    var application = _configManager.Configuration.GetSection("application");
    var maxTasks = int.Parse(application["MaxTasksPerListener"]);
    while (true)
    {
        if (taskList.Count > maxTasks)
        {
            _logger.Info($"Waiting for handling task to be completed {_listenerProxy.Port}.");
            await System.Threading.Tasks.Task.WhenAny(taskList.ToArray()).ConfigureAwait(false);
            taskList.RemoveAll(t => t.IsCompleted); // at least one task affected, but maybe more
        }
        _logger.Info($"Waiting for client to be accepted on port {_listenerProxy.Port}.");
        (var readStream, var writeStream, var sourceIP, var sourcePort) = await _listenerProxy.AcceptClient().ConfigureAwait(false);
        _logger.Info($"Client accepted on port {_listenerProxy.Port}: IP:{sourceIP}, Port:{sourcePort}");

        var clientContext = new ClientContext<TSendMessage, TReceiveMessage>
        {
            SourceAddress = sourceIP,
            SourcePort = sourcePort,
            DataAdapter = DataAdapterFactory<TSendMessage, TReceiveMessage>.Create(readStream, writeStream)
        };
        taskList.Add(HandleClient(clientContext));
    }
}

HandleClient 方法定义为:

public async System.Threading.Tasks.Task HandleClient(ClientContext<TSendMessage, TReceiveMessage> clientContext);

我希望能够并行处理最多预定义数量的请求(处理函数是HandleClient)。客户端将连接,发送少量数据,然后关闭连接。 由于整个项目都是异步的,我很想尝试在这部分也使用异步方法。

我很确定不建议使用此解决方案,但我不知道如何以更好的方式完成。我在这里找到了一个非常相关的话题:How to run a Task on a new thread and immediately return to the caller?

Stephen Cleary 在那里发表了评论:

嗯,我建议的第一件事是尝试一个更简单的练习。 说真的,异步 TCP 服务器是最复杂的服务器之一 您可以选择的应用程序。

那么,在这种情况下,“正确”的方法是什么?我知道,我的方法“有效”,但我有一种不好的感觉,尤其是 HandleClient 或多或少是“一劳永逸”。我对生成的任务感兴趣的唯一原因是“限制”吞吐量(最初它是一个异步 void)。事实上,我的问题与链接中的 TO 完全相同。但是,我仍然不知道这种方法的最大问题是什么。

我将不胜感激建设性的提示...谢谢。

【问题讨论】:

标签: c# sockets asynchronous async-await tcplistener


【解决方案1】:

一句话:红隼。将所有这些顾虑转移到同时获得许多其他好处,例如高级缓冲区生命周期管理,以及面向异步的设计。这里的关键API是UseConnectionHandler&lt;T&gt;,例如:

public static IWebHostBuilder CreateHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args).UseKestrel(options =>
    {   // listen on port 1000, using YourHandlerHere for the handler
        options.ListenLocalhost(1000, o => o.UseConnectionHandler<YourHandlerHere>());
    }).UseStartup<Startup>();

您可以看到一个微不足道但可运行的示例here(嘿,如果您将功能最强大的类似 redis 的服务器称为“微不足道”),或者我在私人 github 存储库中有其他示例,我可能会发送给您,包括一个示例,您需要做的就是实现帧解析器(库代码处理其他所有内容,并且适用于 TCP 和 UDP)。

至于“油门”——这听起来像是一个异步信号量; SemaphoreSlimWaitAsync

【讨论】:

  • 直到!我不知道 Kestrel 可以处理原始 TCP。
  • @StephenCleary 是的;并且因为它将所有内容都公开为“管道”(即PipeReaderPipeWriter),所以您不需要处理任何后台缓冲区管理等 - 只需从入站管道中获取您想要的内容
  • @Marc 这是我正在寻找的解决方案。但不幸的是,Kestrel 非常难以驾驭,听起来需要数年时间才能适应它。你可以推荐什么样的文档开始?
  • @MichaelW 高容量套接字服务器从根本上说是复杂的——这是不可避免的。 IMO Kestrel 比我知道的任何其他 API 都简单得多。也就是说,我在 Pipelines.Sockets.Unofficial 中确实有另一个服务器主机,它的工作方式类似但没有 Kestrel。我已链接到工作服务器;我还可以在这里向您指出我的 3 部分系列,其中涵盖了大部分管道细节:blog.marcgravell.com/2018/07/pipe-dreams-part-1.html,其中讨论了非 Kestrel 主机
  • @Marc:根据您的示例,我将 Kestrel 集成为 TCP 套接字服务器。有用。我使用基于管道的 Stream,并且能够几乎原封不动地重用我的旧代码。我们已经将 Kestrel 用于其他事情(gRPC、Metrics...),我不知道我们也可以将它用于 TCP 套接字通信。非常感谢!!!
猜你喜欢
  • 2014-02-21
  • 1970-01-01
  • 2013-04-06
  • 1970-01-01
  • 2010-10-22
  • 1970-01-01
  • 2014-12-22
  • 2013-08-24
相关资源
最近更新 更多