我正在尝试使用持久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
读取数据包的所有字节(大小是标题中的第二个字段),然后同步反序列化它们 - 因为它是一个非阻塞操作 - 但这迫使我分开头文件中的有效负载反序列化上下文,这将导致我编写大量重复的代码。如果上述问题的答案是肯定的,是否有更可行的解决方案?答案 0 :(得分:3)
我是否有可能因其他插座而导致饥饿
如果对于“socket”,我们读“线程”,答案是否定的。任务调度是自适应的;如果您的任务是调度程序确定为“长时间运行”的任务,则将根据需要将更多工作线程起草到服务中。服务任务的线程池经过精心设计,可以动态响应环境。
主要的问题不是你的线程用完了(我的意思是,你创建的可能很多你的应用程序停止响应,但是非常不可能)但它会抛出整个想法通过窗口异步I / O实现可伸缩性。如果同步部分占主导地位,这可能比仅仅在线程上执行整个事情更糟糕。
是否有更可行的解决方案
一般的想法是通过缓冲将反序列化与读取数据分离。读取一些字节,缓冲它们,确定缓冲区是否包含一个或多个完整的数据包,从缓冲区中删除它们并对它们进行反序列化(留下一些未反序列化的数据)。这要求你要么知道数据包的大小而根本不反序列化,要么你可以向你的反序列化逻辑提供一些字节,然后给你一个错误“需要更多的数据”。除非解串器具有副作用,否则总是可以工作。
问题在于,例如即使只是尺寸也不简单 数值,甚至它的大小不是固定的字节数。一世 可以专门创建一个反序列化器的实例来读取 同步大小(通常只有3-4个字节),然后读取 有效负载异步,然后最终反序列化后者但是 这给GC增加了一些压力,同时也增加了代码 更加分裂。
这里有两件事:
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%准确地告诉您,如果在我们尝试解串器之前缓冲区中有完整的数据包,那么这种方法效果最好(如果您的数据包总是以常量为框架,那么这是可能的)大小长度类型),但只要我们读取了足够的数据并且数据包不是太大,它就会“足够好”,只要我们读得足够多。