我正在使用一个客户端应用程序,它使用原始TCP套接字与中央服务器进行通信。应用程序消息被序列化,然后以长度为前缀,以创建传递到TCP流的帧。
处理此问题的一种经典方法是直接调用套接字类上的Receive或BeginReceive,反序列化回调上的消息,将消息传递到单独的队列以供另一个线程处理,然后让回调开始另一个接收插座再次。
这种方法的天真实现对我来说并不理想 - 它将消息序列化和反序列化紧密地耦合到套接字,并且需要相当多的“管道”以使队列在不同的线程/回调中发挥良好性能。它也有点抽象 - 它需要调用代码知道底层套接字,而不是输入和输出消息的“数据流”。
鉴于我完全在.NET 4.5中工作,使用TPL(TaskFactory.FromAsync)包装Socket的Begin和End异步方法是一个显而易见的选择。但是,由于种种原因,我不清楚如何从这一点出发:
我没有看到很多这种策略的例子,只有“直接”的套接字I / O或简单的实现。我的直觉告诉我TPL Dataflow非常适合,因为序列化和反序列化可以流水线化。
我不清楚如何将有效无穷无尽的接收任务链接到TPL Dataflow或类似的东西。
有什么想法吗?
答案 0 :(得分:2)
目前,还没有很好的方法。一旦你得到一个解决方案,我建议你把它发布到某个地方。
1。我需要一个永远不会完成接收数据的异步“任务”...
我不同意“套接字接收从未在传统意义上完成”的陈述。较低级别的“接收”操作(例如FromAsync
/ BeginReceive
或Toub EndReceive
周围的ReceiveAsync
包装)会返回Task
,此时会在那里完成是可用的数据,套接字是关闭的,还是有错误的。当套接字关闭或出现错误时,更高级别的“接收器”操作(例如,ReadAsync
from Toub's post)将完成。
你可以拥有可能需要无限期完成时间的Task
,但只要他们做就完成了。 Toub在this post中指出的是,您的Task
应该完成最终(特别是处理错误情况)。这与Rx的方法不同,在这种方法中,拥有一个永远不会产生数据并且永不结束的可观察量是完全有效的。
2。我需要一种同步“排队”数据的方法......
从技术上讲,它是“序列化”(“按顺序”),而不是“同步”。理想的解决方案是异步序列化。有几种方法可以解决这个问题。
我想说TPL Dataflow非常适合。请注意,每个套接字实际上有两个逻辑“流”(读取和写入是独立的)。我使用基于Dataflow的套接字包装器取得了一些成功,但没有时间使其具有生产质量。由于两个流(每个可插拔的“块”必须有两个TPL数据流块,一个用于输入,一个用于输出),API相当笨拙。
另一种方法是Rx。 Rx具有比普通Task
或TPL数据流更高的学习曲线,但提供了相当多的功率(和效率)。几年前我玩过基于Rx的插座,但从未有过任何工作。现在Rx的文档和示例要好得多,所以今天我认为它是一个可行的选择。
还有直接使用async
方法的方法。您将使用同步而不是队列。例如,您可以使用SemaphoreSlim
来确保只有一个SendAsync
方法可以一次运行给定套接字。然而,这确实改变了语义,将一些“序列化”推到了你的调用代码:而不是简单的enqueue-and-complete-task(除非你点击你的发送限制,它总是会同步完成),你有异步等待发送然后完成任务(几乎总是异步等待)。您可以通过构建async
生产者/消费者队列(like the one I wrote)来缓解此问题,但是您需要跟踪单独的消费者Task
,并且那时你只是重写TPL数据流。
我不清楚如何将一个有效的“无限”接收任务链“桥接”到TPL Dataflow或类似的东西。
没有一种好的内置方法可以做到这一点。
一个简单的解决方案是拥有一个“Receiver”Task
,它独自负责将数据推送到TPL Dataflow管道。但是,您必须监视Task
以确保在发生某些错误时不会放弃管道,并且您需要一种方法来干净地关闭它。
我写了一个FuncBlock
type来处理这种情况(想法是将它用于套接字读取和其他基于I / O的数据流输入)。花了一段时间来解决async
方法和Dataflow块如何交互的语义(特别是在取消/错误/完成周围),但我认为它会很有用。我很感激任何反馈。