NetworkStream ReadAsync和Read以相同的方法

时间:2015-02-22 20:57:56

标签: c# multithreading async-await server networkstream

我正在尝试使用持久TCP连接构建可伸缩服务器应用程序。我使用的序列化库是同步的,将其转换为APM会导致很大的开销(我已经测试过了)。

数据包的格式始终是数据包ID的一个字节,后跟几个标头字段和有效负载。我想知道,如果我创建了一个异步方法,如:

public async Task<Packet> Deserialize(NetworkStream stream)
{
    //Omitting parameters for the methods below for simplicity.
    var id = await stream.ReadAsync();
    var size = stream.Read();
    //Read the rest of the fields synchronously and deserialize.
}
  • 如果同步,我是否有可能导致其他套接字饥饿? 其中一个Read占用太多(由于TCP碎片造成的) 例子)?
  • 我想通过ReadAsync读取数据包的所有字节(大小是标题中的第二个字段),然后同步反序列化它们 - 因为它是一个非阻塞操作 - 但这迫使我分开头文件中的有效负载反序列化上下文,这将导致我编写大量重复的代码。如果上述问题的答案是肯定的,是否有更可行的解决方案?

1 个答案:

答案 0 :(得分:3)

  

我是否有可能因其他插座而导致饥饿

如果对于“socket”,我们读“线程”,答案是否定的。任务调度是自适应的;如果您的任务是调度程序确定为“长时间运行”的任务,则将根据需要将更多工作线程起草到服务中。服务任务的线程池经过精心设计,可以动态响应环境。

主要的问题不是你的线程用完了(我的意思是,你创建的可能很多你的应用程序停止响应,但是非常不可能)但它会抛出整个想法通过窗口异步I / O实现可伸缩性。如果同步部分占主导地位,这可能比仅仅在线程上执行整个事情更糟糕。

  

是否有更可行的解决方案

一般的想法是通过缓冲将反序列化与读取数据分离。读取一些字节,缓冲它们,确定缓冲区是否包含一个或多个完整的数据包,从缓冲区中删除它们并对它们进行反序列化(留下一些未反序列化的数据)。这要求你要么知道数据包的大小而根本不反序列化,要么你可以向你的反序列化逻辑提供一些字节,然后给你一个错误“需要更多的数据”。除非解串器具有副作用,否则总是可以工作。

  

问题在于,例如即使只是尺寸也不简单   数值,甚至它的大小不是固定的字节数。一世   可以专门创建一个反序列化器的实例来读取   同步大小(通常只有3-4个字节),然后读取   有效负载异步,然后最终反序列化后者但是   这给GC增加了一些压力,同时也增加了代码   更加分裂。

这里有两件事:

  • 您不需要反序列化大小。只需给反序列化器提供一大块数据,包括大小,然后查看它吐出的内容。唯一不起作用的方法是,如果坚持你提交完全个字节数(也就是说,不是“太多”)。但由于您的解串器是基于流的,我不认为这是一个问题。
  • 如果使用包裹数组段而不是复制数据的the appropriate constructor直接在缓冲区上构造MemoryStream,则GC压力应该相当有限。现在您需要担心的是MemoryStream个对象本身,但是您通常只会创建在第1代中清理过的短暂的对象,所以没什么大不了的。

一般的想法是这样的:

byte[] buffer;
int offset = 0;
int bytesRead = await Stream.ReadAsync(buffer, offset, buffer.Length - offset);
int bytesRemaining = bytesRead;
while (bytesRemaining != 0 && haveCompletishPacket(buffer, offset, bytesRemaining)) {
    using (var memoryStream = new MemoryStream(buffer, offset, bytesRead)) {
        int size = deserializer.Deserialize(memoryStream);
        // deserialize as much as possible, if you run out of data, 
        // just reinit the deserializer and return

        // if we got here we have a packet, produce it
        offset += memoryStream.Position;
        bytesRemaining -= memoryStream.Position;
    }
 }

确保正确维护缓冲区的细节很容易出错,所以我可能在上面的代码中弄错了。我希望这个想法很清楚。

显然,如果haveCompletishPacket能够100%准确地告诉您,如果在我们尝试解串器之前缓冲区中有完整的数据包,那么这种方法效果最好(如果您的数据包总是以常量为框架,那么这是可能的)大小长度类型),但只要我们读取了足够的数据并且数据包不是太大,它就会“足够好”,只要我们读得足够多。