实施异步Socket客户端的当前实践标准是什么?

9

我正在寻找最佳实践以满足以下要求:

  • 一个异步处理多个客户端 socket 的框架
  • 每个传入的消息协议规定每个消息都是字符串格式,并用换行符“\n”标记为完成。
  • 我对客户端有完全控制权,但对服务器端没有。服务器接受并发送基于字符串的消息,并使用换行符标记消息的完成。
  • 我希望能够在任何给定时间通过每个连接的 socket 发送消息(每个 socket 都可以接收和发送消息)。
  • 传入的消息应通过回调函数转发。
  • 我希望能够在我的实现中选择所有连接的完整传入消息是否路由到一个单一的回调函数,或者每个 socket 客户端是否实现其自己的回调函数。
  • 我最多连接 4 个客户端/sockets。因此,我寻找利用这样有限数量的 sockets 的建议,但仍能同时管理所有这些 sockets。

我想知道,在针对 .Net 4.5 的情况下,我是否应该使用实现了 IAsyncResult 回调的 BeginReceiveEndReceive 的框架。是否有更好的解决方案,例如使用 NetworkStream 或其他 API 选项?对于 BeginReceive/EndReceive 实现真正困扰我的是,结束接收后我必须再次调用 BeginReceive 并重新注册回调函数。这对我来说听起来像是一种可怕的开销。为什么不能随时异步添加新数据,并且同时另一个上下文构建完整的消息,然后通过引发事件路由这些消息呢?

使用 IAsyncResult 的论点经常是处理线程已经被照顾好了,但是以下方案有何不妥呢:使用 NetworkStream 并简单地从流中读取和写入。如前所述,只交换字符串消息,并且每个协议的每个消息都由换行符字符标记为完成。一个单独的任务/线程将通过 ReadLine() 轮询 streamreader(基于 networkstream)。这可能是最简单的方法,不是吗?

我基本上想问的是,如何使以下代码真正实现异步?

public class SocketClient
{
    private TcpClient client;
    private StreamReader reader;
    private StreamWriter writer;

    public event Action<string> MessageCallback;

    public SocketClient(string hostname, int port)
    {
        client = new TcpClient(hostname, port);

        try
        {
            Stream stream = client.GetStream();
            reader = new StreamReader(stream);
            writer = new StreamWriter(stream);
            writer.AutoFlush = true;

            //Start Listener on port
            StartListener();
        }
        catch (Exception e)
        {
            throw new Exception(e.ToString());
        }
    }

    public void StartListener()
    {
        Task task = Task.Factory.StartNew(() =>
            {
                while (true)
                {
                    if (MessageCallback != null)
                    {
                        MessageCallback(reader.ReadLine());

                    }

                    //Thread.Sleep(200);
                }
            });
    }

}

如果客户端和服务器端的代码完全由您控制,并且使用的是.NET,您可以考虑使用SignalR来处理消息传递。缺点是对于WebSockets,您需要将服务器升级到Windows 8或2012版本。 - Aron
另一个可能性是您可以使用 Rx.Net 以及 Task.Factory.FromAsync 来生成 IObservable<string>,然后进行订阅。 - Aron
@Freddy - 请问你是在寻找一个具有所需功能的 .net 库,还是想要通过仅使用 .net 4.5 框架来实现必要的功能?请澄清一下。 - MarcF
@MarcF,虽然使用BeginReceive/EndReceive的方式似乎更好,但仅使用NetworkStream并从流中读取和写入即可完成工作。正如所提到的,仅交换字符串消息,并且每个协议的每条消息都由换行符字符标记为完成。一个库就足够了,我不知道出了什么问题(或以下方法如何不如BeginReceive / EndReceive)。 - Matt
@Aron,如果我表达不清楚,很抱歉。我不是在寻找ASP/Web解决方案。我需要在C#控制台应用程序中处理多达100个传入的字符串消息通过TCP。我无法控制服务器,协议是固定的。 - Matt
2个回答

14

目前没有标准或常见做法。您有许多选择,每种选择都有优点和缺点:

  1. TAP方法封装到Task中,并使用async/await
    • 优点:操作相对简单。
    • 缺点:它是低级别的;您必须在“无限”循环中自己管理所有各种async操作,以及处理每个连接的状态管理。
  2. Socket *Async方法封装到Task中,并使用async/await
    • 优点:速度快。这是最快和最可扩展的选项。
    • 缺点:您仍然有低级别的“无限”循环和状态管理,在这里代码比选项(1)更复杂。
  3. 将套接字完成转换为Rx事件
    • 优点:您可以封装“无限”循环,并将完成视为事件流。
    • 缺点:Rx的学习曲线很大,并且这不是一个简单的用例。管理每个连接的状态可能会变得复杂。
  4. 将套接字完成转换为TPL Dataflow
    • 优点:(与Rx相同):封装循环并获得数据流。
    • 缺点:学习曲线比Rx容易,但您仍然需要为每个连接进行一些复杂的状态管理。
  5. 使用现有库,例如我的Nito.Async库,它提供EAP套接字类。
    • 优点:非常容易使用;一切都是事件,没有多线程问题。此外,状态管理的复杂部分已经为您完成。
    • 缺点:不如较低级别的解决方案具有可扩展性。
对于您的情况(每秒几百条消息,少于一百个套接字),我建议使用我的Nito.Async库。它是这些选项中最容易使用的。
关于您的协议,您将不得不手动解析出\n并自己进行缓冲。(对于上述所有选择都是如此)。

很棒的概述和总结。正是我在寻找的。非常感谢。 - Matt
还有一个问题,我的代码在你们的不同方法中适用于哪里呢?我尝试使用 async/await 工作,但是当异步发送消息时遇到了运行时错误,我想是因为前一个发送仍然在处理时,新的发送就到达了套接字。一个在自己的任务循环中运行但同步发送和接收的解决方案,在哪里能够适用呢? - Matt
@stephen-cleary 我知道这是一个较旧的问题,但我想知道是否可以使用TaskCompletionSource包装*Async方法而不是创建awaitable对象? - Josh
@Josh:我不是很明白;这与我的答案中的选项(2)有什么不同? - Stephen Cleary
@StephenCleary 不用在意。我搞混了一些东西。但如果你有时间,可以看看我更具体的问题:https://stackoverflow.com/questions/59209996/c-sharp-how-to-cancel-custom-awaitable。非常感谢您的建议。顺便说一下,我很喜欢您的书。实用的例子和简洁明了的解释。 - Josh
显示剩余3条评论

1
根据我的建议,请使用新的异步表单XXXReceive和'XXXSend'(其中XXX代表BeginEnd),可用的新方法是ReceiveAsyncSendAsync方法,它们使用SocketAsyncEventArgs在回调事件处理程序中传递套接字和其他信息。
我曾在msdn存档中看到一个良好的socket客户端和服务器工作示例,可扩展到500个连接(正如我在其中一个项目中进行的测试),但目前我无法通过谷歌找到该链接。但这里有另一个来自msdn存档的链接,涉及相同的主题,希望能对您有所帮助-Get Closer to the Wire with High-Performance Sockets in .NET

更新

首先,我只能提供你从想法到最终实现的帮助,并尽可能提供一些简短的示例代码片段。好的,下面我将更详细地说明。
让我再强调一次,因为我想要这样说,我使用了SendAsyncReceiveAsync而不是BeginReceive/EndReceiveBeginSend/EndSend,即基于事件的异步模式(EAP)。
使用Socket方法的异步形式的好处是它们是无异常的套接字编程方法,可以证明比BeginSend/EndSend方法更快。
这是一个示例链接,我发现它对于最多500个并行连接非常有用-Networking Samples for .NET v4.0
您需要使用.NET 4.5的等待/异步功能。这是一个.NET 4.5代码片段,展示了WebSocket类的用法,可以适应Socket实现 - 支持WebSockets协议(我猜WebSocket的AspNetWebSocketContext将是Socket的SocketAsyncEventArgs)。 我在MSDN-Parallel Programming team blog中找到了等待套接字操作示例代码,可以用于从.NET 4.5框架实现等待/异步。 希望这对您有所帮助。

感谢您的回答,但是您的建议并不新颖,它恰好就是我之前所描述的。我正在寻找其他方案,比如通过任务继续工作的FromAsync,TPL数据流,运行基于字符串消息的同步读写(考虑到我上面所述的简单协议),但在其自己的任务中使用while循环,仅举几个例子。我以前已经看过服务器和客户端的异步套接字示例,无需再发帖。正如我所提到的,我正在寻找“替代方案”。 - Matt
说实话,没有太多。虽然我很感激。正如所指出的,我正在寻找一种非ASP类型的解决方案,我必须管理客户端,对服务器没有控制权,也不处理Web应用程序。此外,我并没有看到你提供了什么除了可怕的Begin/End Receive和AsyncCallback选项。 - Matt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接