管理.Net应用程序中单个Socket的并发读取

时间:2010-07-30 01:49:34

标签: .net multithreading sockets concurrency

我正在编写一个多线程.Net应用程序,其中许多调用者需要同时从单个Socket读取。

从套接字读取的数据被拆分为数据的记录/块 - 每个记录都定位在标头中由CallerID标识的特定调用者处,因此例如从套接字读取的字节流可能是沿线的的:

Header: CallerId = 1, Length = 5
Data:   "Hello"
Header: CallerId = 2, Length = 6
Data:   "Stack "
Header: CallerId = 1, Length = 7
Data:   " World!"
Header: CallerId = 2, Length = 8
Data:   "Overflow"

在这种情况下,调用者1读取的数据流应为“Hello World!”,而调用者2读取的数据流应为“Stack Overflow”。

  • 我的想法是每个调用者数据流都应该通过继承自Stream的某个类来公开,这样调用者就可以使用同步(Read)和异步(BeginRead)编程模型。 / LI>
  • 我想避免每个插槽都有一个专用的读取线程,因为我的应用程序将在任何时候处理许多这样的套接字。
  • 调用者不应该互相阻塞,所以例如在上面的示例中,以下内容不应该死锁:

caller1Stream.Read(12) // Hello world!
caller2Stream.Read(14) // Stack Overflow

我已经考虑过这样做,但我发现事情很快变得复杂,特别是;因为数据需要按顺序读取,因此在读取呼叫者数据时需要缓冲 - 这使得为给定呼叫者读取流的行为更加复杂:

  • 读者必须首先检查他们的缓冲区,看看其他人是否已经阅读了足够的数据
    • 我很难找到合适的FIFO内存缓冲区/流(已经存在一个或者我需要写一个吗?)
  • 如果没有读取足够的数据,则读取器需要从套接字读取数据,直到读取了给定呼叫者的足够数据,缓冲用于其他呼叫者的数据。
    • 缓存数据很好(我可以维护一个读者列表,并通过来电显示查找正确的缓冲区)但是,
    • 如何防止多个线程同时尝试从套接字读取死锁,但是读取其他数据?
    • 如何通知正在等待其数据已被其他来电者读取的数据的来电者?
  • 如何确保消息的标题和数据块一起读取? (数据块长度可变,因此必须首先读取固定长度的标头块以确定要读取的数据量)

当然这肯定是一个相当普遍的问题 - 是不是有一个.Net类可以使整个事情变得更简单? (请注意,我无法更改传输机制/协议)。有没有办法使用其中一个侦听器样式的类来执行上述操作?

如果没有,我该如何解决这些问题?

2 个答案:

答案 0 :(得分:2)

基于你的帖子,我假设你使用TCP / IP,因为你指的是流媒体,但套接字也可以用于UDP无连接协议(以及其他)。 UDP非常简单,因为您只发送固定消息(无流媒体)。

无论如何,既然你指的是接收消息流,我必须假设你正在使用TCP / IP,如果你直接使用套接字,那么你正在寻找的是什么主要问题

TCP / IP是一个流解决方案,所以我在一端丢弃的东西从另一端出来,它总是按顺序排列,但不一定在一起。因此,如果你在一端写下“Hello World”,那么在阅读时你可能会得到“Hello World”,或“Hello”和“World”,甚至“H”“ello World”。关键是,没有办法调用read并期望接收整个消息。

我过去这样做的方法是只有一个中间线程(或者在我的情况下,我只是使用.net线程池),快速读取套接字,重新组装消息,然后传递将它们放到要处理的队列中。诀窍是重新组装,因为你不知道你收到了什么。在你的标题中说,消息长度是一个int32,即4个字节。当你调用read时,你甚至可能需要重新组装这个长度,因为你只收到了需要告诉长度的4个字节中的第一个字节。

听起来你的标题是固定的长度,所以基本上你需要做的,假设callerid和length是uint32的。尝试并读取8个字节,如果小于8,则放入缓冲区,直到我们读取8个字节。一旦我们读取了8个字节,取4个字节并获得长度,为消息的长度分配一个缓冲区。尝试并阅读,直到我们填充缓冲区,一旦我们有,将其放入特定callerid的队列,并发信号通知线程有新消息,以防它正在休眠。然后拿掉剩下的东西,然后重新开始,或等待更多数据。

我过去曾成功使用过它,但它增加了相对较少的开销。

答案 1 :(得分:0)

我不确定你有多少自由选择替代品,但这里有两个:

  1. 使用管道代替套接字。在模式PIPE_READMODE_MESSAGE中打开Windows Named Pipe。消息仅以完整的块形式到达。
  2. 使用更高级别的套接字抽象,如ZeroMQ。对于复杂的网络消息传递,您最终会编写数千行代码。